【爬虫成长之路】(七)【大众点评】PC微信小程序+requests爬取数据

本系列文章共十篇:

【爬虫成长之路】(一)爬虫系列文章导读
【爬虫成长之路】(二)各篇需要用到的库和工具
【爬虫成长之路】(三)【大众点评】selenium爬虫
【爬虫成长之路】(四)【大众点评】selenium登录+requests爬取数据
【爬虫成长之路】(五)【大众点评】浏览器扫码登录+油猴直接爬取数据
【爬虫成长之路】(六)【大众点评】mitmproxy中间人代理爬虫
【爬虫成长之路】(七)【大众点评】PC微信小程序+requests爬取数据
【爬虫成长之路】(八)【大众点评】安卓APP爬虫

声明:本文相比于前面的文章来说,算是进阶爬虫了,由于涉及到较多的加解密算法,故只提供相关的加解密算法,不提供加解密算法的秘钥。读者在学习过程中,主要还是以学习爬虫思想为主

本文需要用到的工具:FiddlerJSON解析工具、小程序逆向工具
本文需要用到的库:requestsjsonbase64Cryptoxxteazliburllib.parse...

爬取目标数据:

  1. 指定城市的店铺列表及其评分数据
  2. 指定店铺下的用户评论数据

一、需求分析

这一篇总共需要爬取两个页面的数据,分别是:

  1. 某城市的店铺列表页面
  2. 某店铺的评论列表页面

二、获取目标页面的URL

由于这里的目标对象是微信小程序,获取接口就必须借助第三方工具了,文中使用的是Fiddler,当然你也可以用你顺手的工具,接下来简单讲下如何操作:

  1. 打开Fiddler,进入localhost://[port]获取Fiddler的证书,安装证书
  2. 配置好Fiddler,使其可以抓HTTPS的包
  3. 打开电脑客户端点评的小程序,进入到对应的页面,如果配置没有问题就能在Fiddler中看到数据包了
  4. 如果Fiddler里全是Tunnel to 443,请重新安装证书,并重启Fiddler,如果还有别的问题,请自行百度
  5. 开启代理后,小程序的很容易出现获取不到数据的状况,这个多刷新几次,到处点几下就好了,多试几次就能找到接口了
  6. 如果出来的过太多了,也可以在Fiddler中开启过滤,pass掉一部分无关URL

操作完后,获取到了店铺列表接口和评论列表接口,分别如下:

# 店铺列表接口
https://m.dianping.com/wxmapi/index/cnxh?...
# 评论列表接口
https://m.dianping.com/ugc/review/reviewlist?...

三、请求头Header、URL参数解析

获取到相应的接口后,就要去看里面的参数了,这里总共有三部分的参数需要查看,分别是:

  1. URL 后面带了参数
  2. response 返回的参数(json数据,未加密)
  3. 请求头Header里面还有参数

评论接口里面的相关参数:
序号 名称 参数个数 重点参数 是否加密 是否固定
1 方法类型 GET - - -
2 URL参数 14 shopUuid,cx,_token,optimus_uuid
3 Header参数 17 openid,openidPlt,token
4 response - reviewList

Header参数:

序号 Header参数 说明
1 Host m.dianping.com 域名
2 Connection keep-alive -
3 User-Agent Mozilla/5.0 (Windows NT 6.1;WOW64) ***WindowsWechat 用户代理
4 channel weixin 渠道
5 channelversion 7.0.9 渠道版本
6 content-type application/json 传输类型
7 minaname dianping-wxapp 可固定
8 minaversion 6.7.0 可固定,也可适当变化
9 openid Hv********** 微信提供给小程序的唯一ID
10 openidPlt oPp*********** 服务端生成
11 platform windows 平台
12 platformversion 1064 平台版本win10,64位
13 sdkversion 2.13.2 SDK版本
14 token 12a********** 服务端生成
15 wechatversion 7.0.9 微信版本
16 Referer https://servicewechat.com/wx734c1ad7b3562129/231/page-frame.html 来源
17 Accept-Encoding gzip,deflate,br 接受的编码格式

URL 参数:

序号 URL参数 说明
1 tagType 1 评论类型,美食评论or其他类型评论
2 tag 全部 获取全部评论,也可进行筛选
3 offset 10 页偏移,从0开始,以10为步进
4 shopUuid H58vsmtj2***** 店铺ID,店铺列表获取
5 cx WX__ver1.2.0_CCCC_QZ+jty4u********XU= 本地生成
6 mtsiReferrer /packages/ugc/pages/reviewlist/reviewlist?msource=wxappmain&shopUuid=H58vsmtj2*****&tag=全部&tagType=1 类似于refer
7 _token eJx********* 本地生成
8 optimus_uuid 176****** 本地生成
9 optimus_platform 13 平台类型
10 optimus_partner 203 未知,可固定
11 optimus_risk_level 71 风险等级,可固定
12 optimus_code 10 可固定
13 pullDown false 可固定
14 reLoad false 可固定
点评小程序评论列表接口及参数分析

店铺列表接口相关参数:
序号 名称 参数个数 重点参数 是否加密 是否固定
1 方法类型 GET - - -
2 URL参数 14 wxuuid,sessionId,optimus_uuid,_token
3 Header参数 17 openid,openidPlt,token
4 response - reviewList

Header参数(不变,同评论列表接口):

URL参数:

序号 名称 说明
1 cityId 4 城市ID
2 wxuuid Hvc*** 同微信openid
3 page 2 页码
4 lat ** 纬度
5 lng ** 经度
6 sessionId e7ef*** 会话ID
7 mtsiReferrer /pages/index/index?cityId=4&wxuuid=Hvc...&page=2&lat=...&lng=...&sessionId=e7ef... 类似refer
8 optimus_uuid 1763b93*****d 本地生成的uuid参数
9 optimus_platform 13 平台类型
10 optimus_partner 203 可固定
11 optimus_risk_level 71 风险等级,可固定
12 optimus_code 10 可固定
13 _token eJx***********pAA== 本地生成
店铺列表接口及参数分析

四、请求头Header、URL参数构造

在进行爬取时,我们必须要能够重新构造请求里的全部参数,因此,知道这些参数是如何生成的就十分有必要了,当然,这一步也是爬虫工作中最难、最费时间的一步,接下来教大家分析这些参数是如何生成的,其中对命名不准确的参数要大胆的去猜测其含义。

STEP 1:分析Header里的参数

  1. 参数一:openid
    从上面的Header参数列表我们可以知道,Header里重点需要关注的参数就3个,分别是openidopenidPlttoken,有过微信公众号或小程序开发的都应该知道,openid是微信为公众号或小程序提供的唯一openid,用于标识不同的用户,在不同公众号或小程序,同一用户的openid是不一致的;所以这个openid我们不能自行生成,必须要服务端返回给我们才行。如果没有相关开发经验,可以通过百度相关字段,也能在微信开发文档中找到其含义。

  2. 参数二:openidPlt
    这个参数也带了openid,应该也与openid有关系,不是本地生成的就是服务端获取的,就这两种情况,通过对请求的数据进行分析,易知,这个字段是服务端返回的;那如何知道的呢?我们看请求列表中会有这样一个请求,

https://maccount.dianping.com/thirdlogin/ajax/auth

在它的response中,可以看到有openidPlt这个字段(图中没有框起来),这里定位这个字段,需要在请求中找到这个字段第一次出现的位置,否则也是不能确定其是如何产生的。我们经过简单的分析后,可以知道openidPlt这个字段就是通过.../thirdlogin/ajax/auth这个请求从服务端获取的,因此这个字段是如何来的我们也确定了。

token参数获取接口及参数分析
  1. 参数三:token
    这个token参数的分析同参数二openidPlt,心细的同学可以发现,在参数二的获取时,其response响应返回的字段中,就有这么一个token参数,于是我们也可以知道,token这个参数也是服务端返回的,不能再本地生成。

通过上面的分析,Header里的参数已经解决了,知道是怎么来的了。因为Header里的openidopenidPlttoken这三个参数都是通过.../thirdlogin/ajax/auth这个接口向服务端获取的,因此我们只需要模拟一下这个请求就好了。那我们就再来看一下这个.../thirdlogin/ajax/auth请求,

  • 先看Header,可以知道它使用的方法是POST,Header里也没有什么重要的参数,基本可以确定这个请求就是首次验证身份用的。
  • 再看URL和Body,可以从下图.../thirdlogin/ajax/auth的Header看到,URL中并未带有其他参数,于是再看其body中是否带有相关参数,通过上图token参数获取接口及参数分析可以看到,其body中有5个参数,分别是codesourceTypedirectLogincx_token,分析时先从简单的入手,通过对比多个.../thirdlogin/ajax/auth请求,而我们可以发现,sourceTypedirectLogin这两个字段是固定的,不用变动。而对于其他三个参数codecx_token,明显经过加密,我们也不能从上下文的response响应中找到其身影,说明这三个参数就是本地生成的。
.../thirdlogin/ajax/auth的Header

现在大概流程就比较清晰了,Header里的关键参数在.../thirdlogin/ajax/auth的response中,发起.../thirdlogin/ajax/auth请求需要构造codecx_token这三个关键参数,而则三个关键参数是在本地生成的。通过一番百度,查看微信小程序开发手册可以得知,其中code这个参数是微信APP提供给小程序的,每次提供的code都是不一样的,这个和openid不一样,同样是微信提供给小程序的,但openid是始终不变的,而code则是小程序通过调用接口,由微信APP生成的,并且有时效性,这个可以通过查看微信小程序登录的时序图来判断。而剩下的两个参数cx_token由于是在本地生成的,就必须要去看小程序的源码了(其实code也是通过查看小程序源码来确定的...),要查看小程序源码,就需要借助其他的工具了,关于微信小程序逆向又可以写一篇文章了,这不是这篇文章的重点,故不展开讲,下面提供一些微信小程序逆向的参考文章:

  1. 反编译获取任何微信小程序源码(微信小程序逆向教程)
  2. wxappUnpacker
  3. 逆向小程序破解js-(逆向篇)
  4. 微信小程序“反编译”实战(一):解包
  5. 小程序爆破之旅---分析微信源码来解析微信小程序文件格式
微信小程序登录时序图
时序图说明
微信开发手册关于code

STEP 2:分析URL里的参数
其实通过上面的分析,也大概可以知道,就拿评论列表里的URL参数来说,关键的参数就3个,分别是cx_tokenoptimus_uuid,这里要注意,这里的_token和前面提到的token并不是同一个参数,这三个参数的分析思路也也前面的Header里的参数分析很相似,先从请求列表中寻找其首次出现的位置,如果首次首先的位置在请求的URL参数或Body中,则这个参数是本地生成的,如果是在响应的response中,就是从服务端获取到的。这里我就不做进一步的分析了,这三个参数其实也是通过分析反编译后小程序才能分析出来的,这三个参数都是其他参数经过了加密生成的。

这里反编译点评的微信小程序之后,最好还是能够导入到微信开发者工具中进行动态调试,无奈本人太菜,逆向过来的小程序导入后一堆错误,不想去改了,就直接静态分析了,费劲...

在获取到了token参数之后,还需要向服务器提交验证服务器(这一步不确定是否必须,没验证)

token参数接口及校验

所以综合来说,最关键的参数总共有这么几个,即验证身份所需要的codecx_token,知道了这几个参数如何生成,这个小程序爬虫就解决了。其中cx_token可以通过静态或动态分析小程序源码来解出,但code这个参数由于是微信APP给小程序提供的,所以要获取这个参数,就必须逆向微信APP,然而逆向微信APP这个工程有点大,所以这里就换种思路。在实际测试过程中,我们发现token这个参数的有效时长还是蛮长的,所以我们就先将手动获取到的token参数保存下来给后面的爬虫用。

通过观察发现,cx_token这两个参数在每次请求时都发生了变化,故这两个参数里必然带了时间戳进行加密,由于是静态解密,所以要做的就是搜索关键字,阅读源码,理清逻辑结构,也没啥技术含量,就不在这里讲解如何操作了。

五、相关参数的加解密算法

这里就直接给出相关参数的加解密算法了,具体如何得来,就是依靠静态分析,当然能动静结合自然是更好的。

通过搜索关键字就能发现,参数加解密的算法全部在finger.jskonan.js这两个文件里面,可以多看看finger.js这个文件,但这个文件太大了,我没看完,就看了部分和参数加解密相关的,需要的同学自行去查看。其中代码涉及到秘钥的部分会隐去,需要学习使用的同学请自行动手反编译小程序后查看。

cx:小程序原始js加密算法(部分代码)

wxPre = "WX__ver1.2.0_CCCC_", 
    getFinger = function(t) {
        fingerData.app = config.app, fingerData.openid = config.openid, fingerData.unionid = config.unionid, 
        fingerData.mchid = config.mchid;
        var e, r = wxPre;
        try {
            fingerData.location || getLocation(), getTimeStamp(), fingerData.userInfo ? 
            (e = JSON.stringify(fingerData), r += CBCencrypt(e), t && t(r)) : 
            getUserInfo(function() {
                var e = JSON.stringify(fingerData);
                r += CBCencrypt(e), t && t(r);
                console.log(r);
            });
        } catch (e) {
            t && t(r);
        }
    }

function CBCencrypt(t) {
        function e() {
            for (var t = [ "91EBA6DBE4E***", "C5F5FDF5F2F5F3F***" ], e = [], r = "", n = 0; n < t.length; n++) {
                r = "";
                for (var i = t[n], o = i.length, a = parseInt("0x" + i.substr(0, 2)), s = 2; s < o; s += 2) {
                    var c = parseInt("0x" + i.charAt(s) + i.charAt(s + 1));
                    r += String.fromCharCode(c ^ a);
                }
                e.push(r);
            }
            return e;   /* e = ["z7Jut6Ywr***", "080706050***"] */
        }
        var r = e()[0], n = e()[1], i = cryptoJs.enc.Utf8.parse(r), o = {  /* i.toString() -> 7a374a757436597**** */
            iv: cryptoJs.enc.Utf8.parse(n), /* 偏移量 .toString() -> 30383037303630**** */
            mode: cryptoJs.mode.CBC,        /* 加密模式 */ 
            padding: cryptoJs.pad.Pkcs7     /* 填充 */
        };
        return cryptoJs.AES.encrypt(t, i, o).toString(); /* t:待加密内容, i:秘钥,  */
}

cx 加解密算法,python版本

import base64
import urllib.parse
from Crypto.Cipher import AES
class AES_CBC:

    def __init__(self):
        self.key = 'z7Jut6Ywr***'
        self.iv = '080706050***'

    def add_to_16(self, value):
        while len(value) % 16 != 0:
            value += '\0'
        return str.encode(value)

    def encrypt(self, content):
        aes = AES.new(self.add_to_16(self.key), AES.MODE_CBC, self.add_to_16(self.iv))
        bs = AES.block_size
        pad2 = lambda s: s + (bs - len(s) % bs) * chr(bs - len(s) % bs)  # PKS7填充
        # 执行加密并转码返回bytes
        encrypt_aes = aes.encrypt(str.encode(pad2(content)))
        # 用base64转成字符串形式
        encrypted_content = str(base64.encodebytes(encrypt_aes), encoding='utf-8')
        return encrypted_content.replace('\n', '')

    def decrypt(self, en_content):
        aes = AES.new(self.add_to_16(self.key), AES.MODE_CBC, self.add_to_16(self.iv))
        base64_decrypted = base64.decodebytes(en_content.encode(encoding='utf-8'))  # 优先逆向解密base64成bytes
        decrypted_content = str(aes.decrypt(base64_decrypted), encoding='utf-8')  # 执行解密并转码返回str
        unpad = lambda s: s[0:-ord(s[-1])]
        return unpad(decrypted_content)

cx解密完成后,结果如下。可以看到,这个cx参数的生成几乎获取了手机的全部能获取到的参数。

{
  "system": {
    "albumAuthorized": true,
    "benchmarkLevel": -1,
    "bluetoothEnabled": false,
    "brand": "microsoft",
    "cameraAuthorized": true,
    "errMsg": "getSystemInfo:ok",
    "fontSizeSetting": 15,
    "language": "zh_CN",
    "locationAuthorized": true,
    "locationEnabled": true,
    "microphoneAuthorized": true,
    "model": "microsoft",
    "notificationAuthorized": true,
    "notificationSoundEnabled": true,
    "pixelRatio": 1.25,
    "platform": "windows",
    "power": 100,
    "safeArea": {
      "bottom": 676,
      "height": 676,
      "left": 0,
      "right": 415,
      "top": 0,
      "width": 415
    },
    "screenHeight": 736,
    "screenWidth": 415,
    "statusBarHeight": 20,
    "system": "win10",
    "theme": "light",
    "version": "7.0.9",
    "wifiEnabled": true,
    "windowHeight": 676,
    "windowWidth": 415,
    "SDKVersion": "2.11.0",
    "devicePixelRatio": 1.25,
    "WifiInfo": "",
    "Beacons": "",
    "LaunchOptionsSync": "{\"path\":\"\",\"query\":{},\"scene\":1001,\"referrerInfo\":{}}",
    "networkType": "wifi",
    "brightness": 0.88,
    "StorageInfo": "{\"currentSize\":768,\"errMsg\":\"getStorageInfo:ok\",\"keys\":[\"$PIKE_LOADBALANCE_WIFI\",\"OH_LAST_SELECTED_CITY\",\"WXOWLKEY-unionId\",\"_lx_sdk_lxcuid\",\"_lx_sdk_wxid\",\"_pike_config\",\"city\",\"dp_categoryList\",\"dp_city_history\",\"dp_index_abtest\",\"dp_index_activity\",\"dp_index_group-seckill\",\"dp_index_guesslike\",\"dp_list\",\"dp_my_columns\",\"dp_my_tools\",\"dp_shop\",\"dp_shop_dinevote_showtips\",\"dp_shop_list\",\"dp_shop_pop\",\"dp_shop_style\",\"geo\",\"hotelCheckTime\",\"keyword_history\",\"loccity\",\"logan_days_info\",\"logan_session_token\",\"loganlog_2020-12-07_1_0\",\"mtweapp_api_https://portal-portm.meituan.com/weapp/dynamic/v6/common/caster-report\",\"mtweapp_api_https://portal-portm.meituan.com/weapp/dynamic/v6/common/launch\",\"mtweapp_api_https://portal-portm.meituan.com/weapp/dynamic/v6/dpweapp/shop-branch\",\"mtweapp_api_https://portal-portm.meituan.com/weapp/dynamic/v6/dpweapp/shop-mall\",\"mtweapp_api_https://portal-portm.meituan.com/weapp/dynamic/v6/dpweapp/tab-my-index\",\"openid\",\"quickConfig\",\"showData\",\"uuid\",\"webp\"],\"limitSize\":10000}",
    "BatteryInfo": "{\"isCharging\":true,\"level\":\"100\",\"errMsg\":\"getBatteryInfo:ok\"}"
  },
  "app": 0,
  "timestamp": 160799699656
}

_token 加解密算法,python版本

import base64
import zlib
import urllib.parse
class Token:
    def encrypt(self, data):
        data_byte = json.dumps(data).encode()  # 字典 ---> 字符串 ---> byte数据
        compressed_data_byte = zlib.compress(data_byte)  # byte数据 ---> 压缩数据
        base64_data_byte = base64.b64encode(compressed_data_byte)  # 压缩数据  --->  base64加密数据(byte数据)
        decode_data = base64_data_byte.decode()  # 加密后的byte数据 --->  字符串token
        final_data = urllib.parse.quote(decode_data)
        return final_data

    def decrypt(self, sign_or_token):
        normal_data = urllib.parse.unquote(sign_or_token)  # 对其中的url进行解码
        decode_data_r = normal_data.encode()  # 将字符数据转成byte数据
        base64_data_byte_r = base64.b64decode(decode_data_r)  # 将byte数据进行解密
        compressed_data_byte_r = zlib.decompress(base64_data_byte_r)  # 将经过base64解密过的byte数据解压缩
        data_r = compressed_data_byte_r.decode()  # 将解压后的数据转成字符串
        return data_r

这里对_token解密完成后,解密结果如下,可以看到里面还有一个被加密了的sign参数:

token_data = {"rId": 100, "ts": int(time.time() * 1000), "cts": int(time.time() * 1000) + 1000000, "brVD": [415, 676],
              "brR": [[519, 845], [519, 845], 24, 24], "bI": ["pages/detail/detail", "packages/search/pages/list/list"],
              "mT": [], "kT": [], "aT": [], "tT": [],
              "sign": "eJx1jl1rw***ByIXg=="}

其实这个sign参数也是用的和_token相同的加密算法。

那就还剩下一个optimus_uuid了,这个怎么生成的我也没弄明白,因为在爬取的过程中发现它不会变动,所以也就不想去看了,逆向后的小程序关于optimus_uuid的部分如下,所在文件是preyoda.js..wechat-sdk/lib/index.js

//preyoda.js
exports.default = new r.default({
    type: "request",
    resolve: function(e) {
        var r = e.request;
        try {
            var a = i.default.getCurrentPage();
            a.hasRedirectStatusInited || (u.default.initRedirectStatus(), a.hasRedirectStatusInited = !0);
            var s = r.data;
            s && s.mtsiReferrer && (s.optimus_uuid = s.optimus_uuid || t.default.get("lxcuid"), 
            s.optimus_platform = s.optimus_platform || 13, s.optimus_partner = s.optimus_partner || 203, 
            s.optimus_risk_level = 71, s.optimus_code = 10, e.request = r);
        } catch (e) {}
        return e;
    }
});

// ..wechat-sdk/lib/index.js
Ct.lxcuid = function(e) {
    function t(e, t) {
        var n, r = 0;
        for (n = 0; n < t.length; n++) r |= s[n] << 8 * n;
        return e ^ r;
    }
    var n = S("lxcuid");
    if (n) return n;
    var r, i, a = function() {
        for (var e = 1 * new Date(), t = 0; e === 1 * new Date() && t < 200; ) t++;
        return e.toString(16) + t.toString(16);
    }, o = +(Math.random() + "").slice(2), c = e.ua || "", s = [], u = 0;
    for (r = 0; r < c.length; r++) i = c.charCodeAt(r), s.unshift(255 & i), 4 <= s.length && (u = t(u, s), 
    s = []);
    0 < s.length && (u = t(u, s)), c = u;
    var f = 0;
    e.sc && (f = +(f = e.sc.split("*"))[0] * +f[1]);
    var l = [ a(), o, c, f, a() ].map(function(e) {
        return e.toString(16);
    }).join("-");
    return x("lxcuid", l), l;
}(Ct);

在爬取的过程中,还遇到了验证码,提交验证码有两步,触发验证的数据如下,验证码提交时也有一个重要的参数,是behavior,这里就不再去触发它的验证码了,在测试的过程中可以使用Fiddler劫持response,来达到触发验证码,下面就直接给出behavior的加解密算法,不再进行说明:

{
  "msg": "您的网络好像不太给力,请稍后再试",
  "code": 406,
  "customData": {
    "verifyPageUrl": "https://verify.meituan.com/v2/app/general_page?action=spiderindefence&requestCode=ca0d5061bbaf49***&platform=13&adaptor=auto&succCallbackUrl=https://optimus-mtsi.meituan.com/optimus/verifyResult",
    "imageUrl": "https://verify.meituan.com/v2/captcha?action=spiderindefence&request_code=ca0d5061bbaf4951944***",
    "requestCode": "ca0d5061bbaf49519***",
    "verifyUrl": "https://optimus-mtsi.meituan.com/optimus/verify?request_code=ca0d5061bbaf4951***"
  }
}

下面是验证码加解密相关的小程序JS源码,文件所在位置是konan.js

// XXTEA 加密算法
function xxtea_encrypt(str, key) {
    var e, n, o, a, f, i, u = str.length, h = u - 1;
    for (n = str[h], o = 0, i = 0 | Math.floor(6 + 0x34 / u); i > 0; --i) {
        for (a = (o = o + 0x9E3779B9 & 0xFFFFFFFF) >>> 2 & 3, f = 0; f < h; ++f)
            e = str[f + 1];
        n = str[f] = str[f] + ((n >>> 5 ^ e << 2) + (e >>> 3 ^ n << 4) ^ (o ^ e) + (key[3 & f ^ a] ^ n)) & 0xFFFFFFFF;
        e = str[0];
        n = str[h] = str[h] + ((n >>> 5 ^ e << 2) + (e >>> 3 ^ n << 4) ^ (o ^ e) + (key[3 & h ^ a] ^ n)) & 0xFFFFFFFF;
    }
    return str;
}

function xxtea_decrypt(str, key) {
    if (str == "") {
        return "";
    }
    var v = str2long(str,  false);
    var k = str2long(key,  false);
    if (k.length < 4) {
        k.length = 4;
    }
    var n = v.length - 1;

    var z = v[n - 1], y = v[0], delta = 0x9E3779B9;
    var mx, e, p, q = Math.floor(6 + 52 / (n + 1)), sum = q * delta & 0xffffffff;
    while (sum != 0) {
       e = sum >>> 2 & 3;
        for (p = n; p > 0; p--) {
           z = v[p - 1];
           mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
           y = v[p] = v[p] - mx & 0xffffffff;
       }
       z = v[n];
       mx = (z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z);
       y = v[0] = v[0] - mx & 0xffffffff;
       sum = sum - delta & 0xffffffff;
   }

    return long2str(v,  true);
}

behavior参数加解密算法,python版本:

# behavior 的解析和生成
class Behavior:
    def gen_behavior(self, key):
        t = time.time()
        t1 = int(round(t)*1000)
        raw_behavior = {
            "env": {
                "zone": [2**.8, 25.54***],
                "client": [63.**, 36.3**],
                "Timestamp": [t1, t1 + 2077],
                # "Timestamp": [160907***, 160907***],
                "count": 1,
                "timeout": 0
            },
            "trajectory": [{
                "point": [
                    [0, 89+random.randint(-2,2), 350, 2077+random.randint(-2,2)],
                    [0, 101+random.randint(-2,2), 350, 2226+random.randint(-2,2)],
                    [0, 108+random.randint(-2,2), 350, 2232+random.randint(-2,2)],
                    [0, 116+random.randint(-2,2), 350, 2258+random.randint(-2,2)],
                    [0, 120+random.randint(-2,2), 350, 2261+random.randint(-2,2)],
                    [0, 128+random.randint(-2,2), 350, 2262+random.randint(-2,2)],
                    [0, 136+random.randint(-2,2), 350, 2266+random.randint(-2,2)],
                    [0, 144+random.randint(-2,2), 350, 2287+random.randint(-2,2)],
                    [0, 152+random.randint(-2,2), 350, 2287+random.randint(-2,2)],
                    [0, 164+random.randint(-2,2), 350, 2289+random.randint(-2,2)],
                    [0, 177+random.randint(-2,2), 350, 2311+random.randint(-2,2)],
                    [0, 192+random.randint(-2,2), 350, 2312+random.randint(-2,2)],
                    [0, 208+random.randint(-2,2), 350, 2314+random.randint(-2,2)],
                    [0, 236+random.randint(-2,2), 350, 2335+random.randint(-2,2)],
                    [0, 257+random.randint(-2,2), 350, 2335+random.randint(-2,2)],
                    [0, 284+random.randint(-2,2), 350, 2336+random.randint(-2,2)],
                    [0, 306+random.randint(-2,2), 350, 2359+random.randint(-2,2)],
                    [0, 324+random.randint(-2,2), 350, 2359+random.randint(-2,2)],
                    [0, 336+random.randint(-2,2), 350, 2359+random.randint(-2,2)]
                ],
                "vector":{
                    "orientation": "h"
                }
            }]
        }
        '''
        [{"point":[
        [0,93,350,1405],[0,100,350,1436],[0,102,350,1460],[0,106,350,1460],[0,113,352,1460],[0,124,353,1485],[0,136,353,1485],[0,148,353,1485],[0,158,353,1509],[0,176,353,1510],[0,188,353,1510],[0,204,353,1539],[0,217,353,1539],[0,230,353,1539],[0,244,353,1545],[0,258,353,1566],[0,272,353,1566],[0,284,353,1566],[0,298,353,1589],[0,310,353,1589],[0,322,353,1590],[0,332,353,1611],[0,340,353,1612]
        ],"vector":{"orientation":"h"}}]
        '''
        en1 = xxtea.encrypt(json.dumps(raw_behavior).replace(' ', ''), key)
        en2 = base64.b64encode(en1).decode('utf-8')
        return en2

    def de_behavior(self, content, key):
        en_xxtea = base64.b64decode(content)
        raw_data = xxtea.decrypt(en_xxtea, key)
        return raw_data.decode('utf-8')

六、优缺点分析

序号 优点 缺点
1 程序运行更快 参数构造麻烦
2 - 需要对小程序进行逆向

七、结语

文章到此已经篇幅过大了,就不再对程序进行演示,由于爬虫越高级,就越有可能使服务器无法区分是否是爬虫,这样容易给对方服务器造成过大的压力。但就本文所提到的爬虫而言,由于用户无法生成openidcode这两个参数,所以想要利用小程序爬虫发起大规模的并发请求是不现实的,希望同学们仅供学习使用,不要恶意发起大量请求。

在测试过程中发现,小程序爬虫还是很容易触发验证码的,所以想要大量爬取数据,也只能放慢速度,慢慢爬取。

注:

  1. 如果您不希望我在文章提及您文章的链接,或是对您的服务器造成了损害,请联系我对文章进行修改;
  2. 本文仅爬取公开数据,不涉及到用户隐私;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容