U2647's blog 一个热爱学习的 Java 程序员,喜欢 Vue,喜欢深度学习 Dubbo Flutter SpringBoot Debug Notes Java LeetCode Python Redis Android DesignPattern mdi-home-outline 首页 mdi-cloud-outline 标签云 mdi-timeline-text-outline 时间轴 mdi-draw-pen 文章总数 62
Python 爬虫实战(一) 爬取自如网租房信息 Python 爬虫实战(一) 爬取自如网租房信息 Python 爬虫 自如网 mdi-cursor-default-click-outline 点击量 62

免责声明:本文仅供学习交流,如出现任何法律问题本人概不负责!

0. 前言

与自如网奋战了 2 个周,终于有点结果了。

1. 隐藏的价格信息

打开自如网(http://www.ziroom.com/z/nl/z2.html?qwd=),直接 F12 看了一下房屋列表的源码:

  <div class="priceDetail"> 
   <p value="" class="price"> 
   <span class="gray-6"> (每月) <span class="hui_icon"><img width="60" height="18" 
    src="//static8.ziroom.com/phoenix/pc/images/201810/img_label5.png" /></span> </span> </p>
    <p class="more">
    <a href="//www.ziroom.com/z/vr/62035032.html" target="_blank">查看更多</a></p> 
  </div>

找到了房屋详情的地址(www.ziroom.com/z/vr/62035032.html),但是价格信息没有找到,没办法,到详情里找一下,发现价格信息的源码如下:

  <span class="price"> <b></b> 
  <span class="room_price" id="room_price"></span>
  <span class="gray-6">/月(季付)</span> </span> 

源码里竟然没有价格信息,然后在审查元素,发现价格信息应该是动态加载的。而且用的还是背景图数字。

20190420-1

打开这个图片的连接看一下(http://static8.ziroom.com/phoenix/pc/images/price/81c4fe87c108f9515c4ee4bafff68529s.png)

20190420-2

不错,已经找到了价格的初始信息,下面打算提取图片中的数字,然后根据 html 里的偏移量计算出真正的价格。

2. 图片反 OCR 识别

图片信息提取,首先想到的 就是 OCR 识别,而且都是很标准的数字,本来以为难度级别属于 hello world 级别的,但是万万没想到低估了自如网的反爬虫工作

使用 OCR 识别,一顿操作猛如虎,发现竟然识别不了,直到我把图片转换成纯黑白的,然后才发现了问题。

    url = 'http://static8.ziroom.com/phoenix/pc/images/price/9bbd4bf71c11e7c8149485d9f1ec5adbs.png'
    response = requests.get(url)
    im = Image.open(io.BytesIO(response.content))
    im = im.convert('1')
    im.show()

发现图片竟然是下面这种镂空的,难怪 OCR 识别不出来

20190420-3

这就有点尴尬了,看到这种图片首先想到的就是 TensorFlow 搞一波图像识别,不过,一个小小的爬虫而已不至于用这么先进的武器吧(其实是我不会而已!^_-),然后又找了几张这种数字图片,对比之下发现,所有的图片,只要数字相同,镂空的规律是一样的,换句话说,所有图片中的数字 1 都是一种镂空的规律!!! 这就有点意思了,首先想到的就是把图片转成矩阵,然后把矩阵和具体的数字做好映射,持久化下来,之后再次请求到图片,拿图片里的矩阵与持久化后的矩阵做对比,这样就能够解析出图片里的数字了。

def analyze_img():
    url = 'http://static8.ziroom.com/phoenix/pc/images/price/9bbd4bf71c11e7c8149485d9f1ec5adbs.png'
    response = requests.get(url)
    im = Image.open(io.BytesIO(response.content))
    im = im.convert('1')
    im.show()
    num = [0,1,4,8,9,3,6,2,7,5]
    num_dict = {}
    for i in range(10):
        data = im.crop((i*30,0,(i+1)*30,30)).getdata()
        data = np.matrix(data,dtype='int')/255
        num_dict[num[i]] =data

    fp = open('num_dict.num', 'wb')
    pickle.dump(num_dict, fp, protocol=-1)
    fp.close()
    return num_dict

嗯,买迈进了一小步。不太明白为什么这个图片在镂空的时候没有加一些随机的元素。如果说是由于随机之后出现极端的情况导致不容易被人眼识别,但是可以随机出一个或者两个具体的位置,随机的镂空,这样既不会出现不容易被人眼识别的情况,也能很好的防止上面这种爬虫的出现。

总之,不管自如网是出于什么考虑,背景图片的数字提取是解决了。下面,就需要找一下这张图片是怎么跟房屋信息绑定的。

3. 动态加载的图片

在 F12 的 网络请求里确实找到了这张图片的请求信息,但是发现,图片的名称是随机串。而且同一个房屋信息,每次请求时的图片名称都不一样,这说明,后台是随机生成的这张图片,没有跟房屋绑定。

20190420-4

所以需要找一下,这张图片的请求是从什么地方发起的,请求的 URL 又是从什么地方获取的。

在 F12 里找到了这张图片的请求位置,但是对比 html 源码发现,并没有这个请求,这说明这个元素是通过 js 动态加载的。

20190420-5

20190420-6

下面就需要找一下,到底是哪个 js 加载的这些信息。重新看了一下,房屋详情页面的源码,发现源码底部,有这样一段 js 的引用。

<script type="text/javascript">
   var ZRCONFIG = {
    "URL_GET_LOGIN_STATE":"/user/check-login?url=",
    "URL_GET_LOGIN_STATE":"/user/check-login?url=",
    "URL_GET_DETAIL_STEWARD":"/detail/steward?resblock_id=",
    "URL_GET_DETAIL_INFO":"/detail/info?",
    "PAGE":"detail"
  };
</script>      
<script  type="text/javascript" src="//static8.ziroom.com/fecommon/library/jquery/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="//static9.ziroom.com/phoenix/pc/js/2017/common.min.js?1555741850"></script>
<script type="text/javascript" src="//static8.ziroom.com/phoenix/pc/js/detail.min.js?1555741850"></script>
<script type="text/javascript" src="//api.map.baidu.com/api?v=2.0&ak=CB9b776692623d30a148b5c5dc2b75a6"></script>
<script src="//static8.ziroom.com/phoenix/pc/js/mappage.min.js" type="text/javascript"></script>

看命名也可以猜到 ‘datail.min.js’ 里应该有玄机。请求下这个 js 的源码看一下。终于找到了。

$.ajax({
    type: "GET",
    url: ZRCONFIG.URL_GET_DETAIL_INFO 
    + "id=" + $("#room_id").val() 
    + "&house_id=" + $("#house_id").val(),
    success: function(data) {
        if (data.code == "200") {
            var dataObj = data.data;
            var priceTableList = dataObj.payment;
            var listArr = [];
            var trClass = "";
            var listStr = "";
            var priceStyle = "<style>.room_price i.num{background-image:url(" + dataObj.price[0] + ");}" 
            + "body.ratio2 .price i.num{background-image:url(" + dataObj.price[1] 
            + ");}.pay_price i.num,.text_r i.num{background-image:url(" + priceTableList[0].rent[0] + ");}" 
            + "body.ratio2 .pay_price i.num,body.ratio2 .text_r i.num{background-image:url(" 
            + priceTableList[0].rent[1] + ");background-size:auto 14px;}<style>";
            $("head").append(priceStyle);
            var priceListHtml = "";
            for (var j = 0; j < dataObj.price[2].length; j++) {
                priceListHtml += '<i class="num" style="background-position:-' 
                + (dataObj.price[2][j] * offset_unit) 
                + 'px"></i>'
            }
... ... (省略其他代码)

发现这个 js 发起了一个 ajax 请求,请求成功后,不仅设置了图片背景,而且还设置了切图的偏移量。

终于看到希望了,我们可以直接请求这个 ajax 的地址,不仅能拿到图片,还能拿到价格信息对应图片上的数字的位置。

ajax 的请求地址是 url: ZRCONFIG.URL_GET_DETAIL_INFO + "id=" + $("#room_id").val() + "&house_id=" + $("#house_id").val()

其中 ZRCONFIG.URL_GET_DETAIL_INF 值在 房屋详情里的 js 里定义的 ,值为 /detail/info?

room_id 和 house_id 在房屋详情的页面里也能找到:

<input type="hidden" value="7e9285535a7687bf2e71be617cd07466" id="hide_key" />
<input type="hidden" value="" id="user_sex" />
<input type="hidden" value="" id="user_uid" />
<input type="hidden" value="62035032" id="room_id" />
<input type="hidden" value="60321330" id="house_id" />
<input type="hidden" value="110000" id="current_city_code" />
<input type="hidden" value="" id="ly_name" />
<input type="hidden" value="" id="ly_phone" />
<input type="hidden" value="1" id="house_type" />
<input type="hidden" id="resblock_id" value="1111027374437"/>

拼接后的最终的 URL 地址是 /detail/info?id=62035032&house_id=60321330

然后拼接上自如网的前缀,就是最终的请求地址,http://www.ziroom.com/detail/info?id=62035032&house_id=60321330

请求一下,终于解决了,价格信息的问题。

20190420-7

4. 完整思路及代码

下面的问题就简单了,循环分页,筛选出每页里的房屋详情 URL ,然后根据详情里的信息去请求价格信息,最后可以把房屋信息写到 Excel里。就结束了

完整代码在这里

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
我的GitHub 我的LeetCode 我的掘金
Powered by Hexo Powered by three-cards
Copyright © 2017 - {{ new Date().getFullYear() }} 某ICP备xxxxxxxx号