zoukankan      html  css  js  c++  java
  • 通过爬虫抓取链家二手房数据

     背景:

      公司需要分析通过二手房数据来分析下市场需求,主要通过爬虫的方式抓取链家等二手房信息。

    一、分析链家网站

      1.因为最近天津落户政策开放,天津房价跟着疯了一般,所以我们主要来分析天津二手房数据,进入链家网站我们看到共找到29123套天津二手房;

      2.查看下页面的数据结构以及每页显示数据条数;

         通过分析每页显示数据30条,总共显示100页面,即使我们通过翻页方式100页也只能拿到数据3000条与其提示的数据信息还差的很多。

        通过翻页的方式没有办法获取需要的数据的话,那么我后来考虑自己拼接url的方式来获取更多的数据,通过拼接方式发现只要超过100页的时候,无论怎么拼接他都是现实最后一页的数据,通过拼接的方式只能以失败告终

         通过拼接url方式失败以后只能考虑通过按照区域的查询条件来检索数据,但是发现通过区域检索的时候有些区域二手房数量也会超过3000条,这样我们必须还的继续按照区域下面的划分,这样会虽然可以但是比较麻烦,所以这样我们暂时不考虑;

       通过上面的分析链家对这块限制比较多,如果爬取来容易被封还的考虑使用代理IP来应对,这样会增加成本同时还会限制速度,所以考虑通过APP或者小程序下手抓包分析下他的接口;

    二、抓包分析链家APP;

      1.一般情况抓包主要通过fiddler 来抓包,下面抓下链家APP的包,手机中配置代理以后会在fiddler中显示手机发送的所有请求,这样不便于我这边分析,所以需要在fiddler设置下过滤规则;

      2.访问链家APP,抓取链家二手房的接口,很轻松的我们就抓到了链家二手房的请求接口,通过抓包我们看到了我们需要的数据,瞬间感觉比爬取页面简单的多;

      3.把接口拷贝至Postman里面来验证下接口中需要的验证和必要的参数,经过验证接口里面请求参数都是必须参数,而且headers里面有一个必须添加的参数Authorization,通过验证发现Authorization,接口参数改变后Authorization值失效;

      分析步骤:

        1.直接复制接口在Postman请求,请求结果返回"无效的请求";

        2.headers 添加参数UA再次请求,请求结果返回"无效的请求";

        3.headers 添加参数Authorization再次请求,请求结果返回正常数据(抓包的过程中发现这个值很特殊所以优先尝试);

        4.判断Authorization是否是动态值变更接口的参数再次请求,请求结果返回"无效的请求";

        5.筛减请求参数再次请求,请求结果返回返回"无效的请求";

      根据上述分析Authorization 可能根据请求参数进行加密生成的,这样我们要了解逻辑只能反编译APP,那只能再次改变策略,尝试小程序是否可以实现;

      4.抓包分析链家小程序

        我们依然需要按照上述的分析步骤来抓包分析,接口请求的参数均是必须参数,而且headers里面的参数依然需要authorization,这个时候我们再也没有办法避开分析这个参数了;

    # 二手房微信小程序接口
    https://wechat.lianjia.com/ershoufang/search?city_id=120000&condition=&query=&order=&offset=0&limit=10&sign=
    #headers 参数
    lianjia-source:ljwxapp
    authorization:bGp3eGFwcDoxYTNjMDA3MmQ0ZDA3NTM2ODVlOTJlMDQ0NmUwNDk5NQ==
    time-stamp:1527659945696

    三、反编译链家微信小程序

      1.获取微信小程序所对应的 wxapkg 包文件;

        i.安装android-sdk-windows,安装完成执行adb --version命令返回版本号证明安装成功;

    C:Usershunk>adb --version
    Android Debug Bridge version 1.0.39
    Version 0.0.1-4500957
    Installed as D:Program Filesandroid-sdk-windowsplatform-toolsadb.exe

       ii.安装MuMu模拟器安装登陆微信,添加链家小程序;

       iii.通过adb命令链接模拟器,获取wxapkg 包文件;

      

    #链接mumu 模拟器
    adb connect 127.0.0.1:7555   # 注意adb连接手机端口5555
    # 
    adb shell
    cd /data/data/com.tencent.mm/MicroMsg/8c12ff3e9b0391b589c0d5b16dc21952/appbrand/pkg   #8c12ff3e9b0391b589c0d5b16dc21952为当前微信的用户名字,可以根据自己的实际来查找
    cancro:/data/data/com.tencent.mm/MicroMsg/8c12ff3e9b0391b589c0d5b16dc21952/appbrand/pkg # rm -rf *   # 便于识别那个属于链家,先删除目录内容再次打开链家小程序
    cancro:/data/data/com.tencent.mm/MicroMsg/8c12ff3e9b0391b589c0d5b16dc21952/appbrand/pkg # ls
    _-1261323258_17.wxapkg   # 名字不固定,根据实际来查看
    _1123949441_130.wxapkg
    and/pkg # cp _-1261323258_17.wxapkg  /sdcard/
    cancro:/data/data/com.tencent.mm/MicroMsg/8c12ff3e9b0391b589c0d5b16dc21952/appbrand/pkg # exit
    # 把_-1261323258_17.wxapkg 下载本地
    C:Usershunk>adb pull /sdcard/_-1261323258_17.wxapkg . /sdcard/_-1261323258_17.wxapkg: 1 file pulled. 1.0 MB/s (988967 bytes in 0.941s)

      2.反编译微信小程序,获取重要信息;

        通过github上获取微信小程序.wxapkg解压工具,我这边使用python3来解压;

        把_-1261323258_17.wxapkg和unwxapkg.py 放在通过个目录解压,会在当前目录生成_-1261323258_17.wxapkg_dir

     python .unwxapkg.py .\_-1261323258_17.wxapkg

        下面是解压后的目录结构

        目录结构简单的介绍

    *.html  包含wxss样式信息
    app-service.js  所有js汇总文件
    app-config.json app.json 以及各个页面的配置文件
    page-frame.html  wxml文件及app.wxss 样式

      

      3.分析加密过程,生成authorization;

       i.微信小程序的页面逻辑主要都会存储在app-service.js,下面我们需要来解析下这个文件,打开文件的时候里面的内容比较乱,我们需要通过格式化工具格式下js文件,使用Nodepad++ 来打开,安装下JSTool工具格式化js代码,这样会让我们看的舒服一些;

      ii.通过关键字"authorization"搜索,对应的加密信息

      搜索出来第一处带有authorization关键字的代码,这里是请求的定义的header 信息,我们这里可以看到Authorization的值是经过base64编码;

      

      搜索出来第二处带有authorization关键字的代码,这里我们可以看到一个关键信息l += "6e8566e348447383e16fdd1b233dbb49",这里猜想6e856这个串应该是appkey校验信息;

      根据搜索出来的两端代码我们来进行一次分析,首先获取请求参数,然后对请求参数转换成为字典,然后通过key进行排序,使用将key和value通过= 链接起来,在末尾添加appkey,进行md5加密,最后在开头添加appid 进行base64 编码;

    /*定义变量 l,空字符串*/
    var l = "";
    /*   
    1.将请求参数解析成key:value 键值对; 
    2.通过key进行从小到大排序;
    3.通过"="将 key和value连接起来,并追加到变量 l中;
     */
    Object.keys(a.data).sort().forEach(function (e) {
        return l += e + "=" + ("object" == t(a.data[e]) ? JSON.stringify(a.data[e]) : a.data[e])
    }),
    /*l 中再次追加字符串6e8566e348447383e16fdd1b233dbb49 */
    l += "6e8566e348447383e16fdd1b233dbb49",
    /*对l 字符串进行md5加密*/
    l = e.default.hexMD5(l),
    /*对加密的结果添加前缀 ljwxapp:*/
    l = "ljwxapp:" + l,
    c.Authorization = n.encode(l),

      4.验证逻辑是否正确,抓取二手房列表;

    import hashlib
    import base64
    from urllib.parse import urlparse, parse_qs
    
    app_id = "ljwxapp:"
    app_key = "6e8566e348447383e16fdd1b233dbb49"
    
    
    def get_authorization(url):
        """
        根据url 动态获取authorization
        :param url:
        :return:
        """
    
        param = ""
        parse_param = parse_qs(urlparse(url).query, keep_blank_values=True)  # 解析url参数
        data = {key: value[-1] for key, value in parse_param.items()}  # 生成字典
        dict_keys = sorted(data.keys())  # 对key进行排序
        for key in dict_keys:  # 排序后拼接参数,key = value 模式
            param += str(key) + "=" + data[key]
        param = param + app_key  # 参数末尾添加app_key
        param_md5 = hashlib.md5(param.encode()).hexdigest()  # 对参数进行md5 加密
        authorization_source = app_id + param_md5  # 加密结果添加前缀app_id
        authorization = base64.b64encode(authorization_source.encode())  # 再次进行base64 编码
        return authorization.decode()
    
    
    if __name__ in "__main__":
      # 天津链家二手房信息 url
    = "https://wechat.lianjia.com/ershoufang/search?city_id=120000&condition=&query=&order=&offset=0&limit=10&sign=" print(get_authorization(url)) # 生成加密串:bGp3eGFwcDoxYTNjMDA3MmQ0ZDA3NTM2ODVlOTJlMDQ0NmUwNDk5NQ==

        我们把生成的结果放在Postman中验证看看是否正确

     四、通过Scrapy 来爬取所有的二手房数据,验证上述逻辑是否正确

      1.settings.py 配置文件配置接口请求的地址和APP_ID 和APP_KEY

      

      2.定义我们要抓取的字段

    # -*- coding: utf-8 -*-
    # @Time    : 2018/5/30 10:52
    # @Author  : Hunk
    # @File    : ErShouFangListItems.py
    # @Software: PyCharm
    
    from scrapy import Item
    from scrapy import Field
    
    
    class ErShouFangItems(Item):
        house_code = Field()  # 二手房ID
        resblock_id = Field()  # 小区ID
        resblock_name = Field()  # 小区名字
        price = Field()  # 价格元/平

      3.编写下Spider逻辑

    # -*- coding: utf-8 -*-
    # @Time    : 2018/5/30 10:51
    # @Author  : Hunk
    # @File    : ParseErShouFangList.py
    # @Software: PyCharm
    import time
    import json
    import math
    from scrapy import Spider
    from scrapy import Request
    from lib.Encrypt import get_authorization
    from scrapy.utils.project import get_project_settings
    from ..items.ErShouFangListItems import ErShouFangItems
    
    
    class ParseErShouFangSpider(Spider):
        """
        楼盘列表
        """
        name = 'TJErShouFang'
    
        def __init__(self, city_id, *args, **kwargs):
            super(ParseErShouFangSpider, self).__init__(*args, **kwargs)
            self.settings = get_project_settings()
            self.base_url = self.settings["WX_BASE_URL"]  # 获取配置文件中的微信小程序的请求地址
            self.api = "/ershoufang/search"  # 二手房接口地址
            self.city_id = city_id
            self.limit_offset = 0
            self.new_house_url = self.base_url + self.api + "?city_id=%s&condition=&query=&order=&offset=%s&limit=10&sign"  # 拼接二手房接口地址
            self.start_urls = self.new_house_url % (city_id, self.limit_offset)  # 请求地址
            self.headers = {"time-stamp": str(int(time.time() * 1000)), "lianjia-source": "ljwxapp",
                            "authorization": ""}  # 定义header
    
        def start_requests(self):
            """
            重写start_requests
            :return:
            """
            self.headers["authorization"] = get_authorization(self.start_urls)
            yield Request(url=self.start_urls, headers=self.headers, callback=self.parse)
    
        def parse(self, response):
    
            total_count = self.parse_page(response)  # 获取一共有多少页面数据
            for i in range(0, total_count):  # 翻页操作
                url = self.new_house_url % (self.city_id, self.limit_offset)  # 定义请求的url
                self.limit_offset += 10  # 每页显示10条数据,每次翻页递增10条
                self.headers["authorization"] = get_authorization(url)
                yield Request(url=url, headers=self.headers, callback=self.parse_house_item)
    
        def parse_house_item(self, response):
            """
            解析JSON 数据
            :param response:
            :return:
            """
            item = ErShouFangItems()
            content = json.loads(response.body.decode())
            ershoufang_list = content["data"]["list"]
            if len(ershoufang_list) > 0:
                for ershoufang in ershoufang_list:
                    item["house_code"] = ershoufang_list[ershoufang]["house_code"]
                    item["resblock_id"] = ershoufang_list[ershoufang]["resblock_id"]
                    item["resblock_name"] = ershoufang_list[ershoufang]["resblock_name"]
                    yield item
    
        def parse_page(self, response):
            """
            计算总共页数
            :param response:
            :return:
            """
            content = json.loads(response.body.decode())
            total_count = int(content["data"]["total_count"])
            return int(math.ceil(total_count / 10))

      4.运行自己的爬虫,查看下结果

      

    以上过程主要记录了,抓取链家二手房数据时候的过程。

     

  • 相关阅读:
    Win Server 2008 R2 一键配置全环境 PHP5+MYSQL5+ZEND+PHPMYADMIN
    服务器加固,安全狗V4.0正式版 (Windows)
    Win server 2008 R2激活工具使用图文教程(SK Patch v1 R2 Final OEM)
    Laoy8 V4.0 营销插件
    OK3W发布插件
    流量精灵(P2P方式,刷真实流量)
    aspcms2发布插件
    今天写一篇技术文章,关于TemplateEngine的。
    全面分析新浪博客的登录过程
    网站克隆插件
  • 原文地址:https://www.cnblogs.com/mengyu/p/9115832.html
Copyright © 2011-2022 走看看