Abstract:在大众点评网的爬取过程当中,充满了大量的接口风控,对于敏感接口的请求均会要求携带_token参数,因此该参数的构造对于爬取地顺利进行极为重要。本文通过大众点评网的登录过程为例,详细介绍_token参数的生成逻辑,以实现正常爬取。

Key words:大众点评网    爬虫    _token参数    selenium

Introduction

大众点评网有着极为严格的反爬策略,除过常规的反爬手段之外,最重要的一环就是_token参数。具体而言,该参数可视为对请求风险的判别依据,其通过对document、window、screen、element对象以及自动化测试框架特征值等信息的综合判断,并融合请求体、查询字符串以及URL等数据生成token值,然后将该值作为请求体的一部分POST至后端,由后端完成解密从而达到识别风险的目的。

若请求由正常的浏览器发起(这意味着_token的值是正常的),则风险等级(riskLevel)为0,否则风险等级将会被提升到6(这意味着_token值生成错误或者非正常发起请求),将会进一步限制用户操作。被限制操作的用户若要继续操作,则需要通过滑动验证块的方式来解除高级别风控,但是根据笔者实际操作而言,滑动验证块仅仅在常规的反爬手段下有用(比如说请求过于频繁的情况),而对于高级别风控,即使验证块滑动成功也无法解除高风险等级限制,无论手动还是自动化操作。

由于_token的构造会考虑到自动化测试框架的特征值,这意味着诸如selenium之类的框架无用武之地,由该框架发起的请求riskLevel直接会被提升至6。有一种方法是通过中间人代理将对应js文件中的selenium特征值过滤或替换掉,从而规避被检测的风险。但是本文不采用该方法来处理。这是因为,一方面本次爬取数据量较大,而selenium速度较慢,故不适用于通过selenium来爬取;另一方面,即使过滤了selenium特征值,大众点评必然还会有其它的方法来识别它,所以该方法治标不治本。_token的生成还会考虑到document、window、screen、element等对象,并取其属性值,如果使用requests库来爬取,则无法读取该值,_token参数依然无法生成。这就形成了一个矛盾,两种办法都有缺陷,但是通过修改相应文件的js代码便可以解决requests库的使用导致无法读取相关属性值的问题。

此外,需要注意的是,大众点评网定义selenium特征值的rohr.min.js文件中,将其表示为了16进制,因此无法全局搜索。但在该文件开头定义了一个数组_$_543c,其中包含相关关键字及selenium的特征值序列,对其元素调用.encode("utf-8")方法可将其转化为ASCII字符串,从而方便过滤与替换。

基于上述,本文将会在构造生成_token参数的函数参数的基础上,对相应的js文件进行修改,然后通过pyexecjs驱动Node.js运行时执行之,最终获取_token值以实现顺利登录。

API analysis

通过对大众点评登录流程进行追踪,可发现其完整的接口调用链如下(均使用POST方法):

  1. https://account.dianping.com/account/ajax/checkRisk
  2. https://account.dianping.com/account/ajax/mobileVerifySend
  3. https://account.dianping.com/account/ajax/mfastlogin

以用户通过手机号+验证码方式登录为例。首先,在用户输入手机号并点击发送验证码按钮的瞬间,浏览器会通过/account/ajax/checkRisk接口向服务器端发起请求以进行风控验证,请求体如下:

{"riskChannel": "","user": "","_token": ""
}

其中,riskChannel的正常取值为202,取固定值即可;user为11位手机号;而_token就是B端对浏览器风险情况的一个预估计,也是本文的重点。服务器对此请求的响应体如下

{"code":200,"msg":{"riskLevel":"","publicKey":"","uuid":""},"riskChannel":202
}

code为响应码,正常的请求一般为200;riskLevel为风险等级,正常情况下为0,异常情况下会被提升至6;publicKey参数暂时用不到,先不管;uuid参数为下一步请求中请求体的内容,该参数应该是服务器端用来标识某项请求是否经过了/account/ajax/checkRisk接口;最后,riskChannel的值正常情况下依旧返回202。

若上述riskLevel的值为0,则浏览器会进行下一项请求,即向用户手机发送验证码,如果不是0,则会出现类似于“当前操作异常”的提示,而无法进行验证码的发送。

假设当前环境正常,则可向/account/ajax/mobileVerifySend接口发起请求,请求体如下

{"mobileNo": "","uuid": "","type": "304","countrycode": "86"
}

mobileNo为11位手机号;uuid/account/ajax/checkRisk接口的返回值之一;type取固定值304;countrycode为国家代码,固定为86。服务器对该请求的响应如下

{"code":200,"mobileNo":"","msg":{"info":"手机验证码已发送,请查看手机!"},"type":304,"uuid":""
}

code为响应码,发送成功为200,失败为100;mobileNo为11位手机号;msg为提示信息,发送成功的话msg对象为info字段,发送失败则为err字段;type应该是固定值,为304;uuid为请求时的uuid值。

得到验证码后,便可以通过/account/ajax/mfastlogin接口来执行登录动作。该接口的请求体如下

{"mobile": "","vcode": "","channel": "0","countrycode": "86","type": "304","keepMobile": "off","_token": ""
}

其中,参数mobile为11位手机号;vcode为6位验证码;channel应该是固定值,为0;countrycode固定为86;type为304;keepMobile为是否记住手机号选项,记住为on,否则为off;最后一个参数_token也是对浏览器的风险估计。服务器对该接口的响应如下

{"code":200,"msg":{"info":""}
}

若登录成功,则返回200状态码,msg对象中info为空,这是因为可以通过其余接口获取用户详细信息。

纵观上述对相关接口的分析,可以发现整个登录流程的核心就是参数_token的生成,接下来重点说明之。

_token generation strategy

通过抓包获取所有的js文件,并全局搜索_token,发现该关键词存在于下述两个文件当中

  1. https://www.dpfile.com/app/app-easy-login-frame/js/common.min.63556046f9f14a990d06e61e2afe0511.js
  2. https://www.dpfile.com/app/app-easy-login-frame/js/login.min.ee92918871492df484315e5d4ea55a9b.js

进一步分析可以发现,/account/ajax/checkRisk接口的_token值定义在第一个js文件当中;/account/ajax/mfastlogin接口的_token值定义在第二个js文件当中。接下来对js文件继续分析

定位到第一个js文件的具体代码,容易发现存在这样一个函数

    t.default = function(e, t) {if (!window.Rohr_Opt)return t;try {var n = [];for (var r in t)n.push(r + "=" + t[r]);var o = "?" + n.join("&"), i = Rohr_Opt.reload(e + o);return t._token = i,t} catch (e) {}}

很明显,该函数的主要作用就是构造_token,形参t为js对象,此处遍历t,然后将其用字符&拼接起来,再在拼接的结果前加上字符?,从而构造出参数o。容易推断出参数e应该为URI,从而e + o为URL,而_token的值则是通过函数Rohr_Opt.reload()构造的,其参数为e + o,即_token值初步依赖于e + o。我们可以进一步确认一下形参et的真实含义,继续寻找实现网络请求的代码,如下

         200 == m.code && (1 == e.status ? o() : (r = h.host + "/account/ajax/checkRisk",i = "86" == d.countryCode ? d.mobile : d.countryCode + "_" + d.mobile,a = (0,u.default)(r, {riskChannel: d.riskChannel,user: i}),(0,l.default)({url: r,data: a,success: function(e) {e && 200 == e.code ? 6 == e.msg.riskLevel ? (m.code = 100,m.msg = e.msg.riskMessage,v(m)) : 1 == e.msg.riskLevel ? (d.publicKey = e.msg.publicKey,o()) : (m.publicKey = e.msg.publicKey,m.uuid = e.msg.uuid,v(m)) : (m.code = 101,m.msg = "风控校验异常",v(m))},error: function() {m.code = 102,m.msg = "风控服务请求出错",v(m)}})))

显而易见,本文上述推断正确,实际参数r为URL作为形参e的实际值,a为对象作为形参t的实际值,然后传入函数当中构造_token。注意到success回调函数的逻辑:如果响应状态码code为200且风险等级riskLevel为6,则提示风险识别信息;若code为200且riskLevel为1则只设置publicKey;只有在riskLevel为其他值(包括0)的情况下设置uuid和publicKey以进行下一步操作。

接下来分析Rohr_Opt.reload()的代码,通过全局搜索Rohr_Opt,发现其存在于文件https://s0.meituan.net/mx/rohr/rohr.min.js中,打开该文件,发现其内部定义了匿名函数,继续寻找相应的代码,最终定位到下述代码

                if (typeof (Rohr_Opt) === _$_543c[2]) {iP.bindUserTrackEvent();Rohr_Opt.reload = iP.reload;Rohr_Opt.sign = iP.sign;Rohr_Opt.clean = iP.decrypt}

至此,已经找到了最关键的函数,下一步就是对该js文件进行修改。先将该js文件拷贝至本地,由于该文件定义的是匿名函数,而我们要调用的是Rohr_Opt.reload(),因此先定义一个Rohr_Opt对象,这样的话匿名函数的执行就可以将reload函数设置给Rohr_Opt

此外,由于该文件的运行依赖于document、window、screen、navigator等对象,因此需要在全局范围内定义之。对于document和window等对象,由于requests库未驱动浏览器,因此部分值需要直接在该js文件中修改,还要修改相应的函数,最后再js文档末尾定义下述函数即可。

function gen_token(url){return Rohr_Opt.reload(url);
}

接下来就可以通过pyexecjs驱动Node.js运行时来执行该文件中的gen_token()函数来生成实际的_token值作为接口请求体的一部分。测试代码如下

import execjstoken = execjs.compile(open(r"replace your js's path").read()).call('gen_token', 'https://account.dianping.com/account/ajax/checkRisk?riskChannel=202&user=replace your phone')
print(token)

执行接口/account/ajax/mfastlogin_token的生成策略也一样,只需要更换Python代码中call()的第二个参数即可。

Conclusion

本文较为详细地介绍了大众点评网的_token生成策略,通过修改对应js文件并以Python执行的方式来生成具体的_token值,可以有效突破大众点评网的接口风控,实现数据的正常爬取。缺陷在于通过pyexecjs驱动Node.js运行时执行js文件速度略慢,理想的方式是对_token生成技术进行逆向,这将会在下一篇文章中详细介绍,敬请关注。

修改后的rohr.min.js文件可以关注公众号“real小袁之家”,并后台回复“rohr”获取。

如何突破大众点评接口风控?相关推荐

  1. 大众点评api接口 开放平台

    大众点评接口支持以下功能 1,获取店铺信息 2,获取会员信息 3,更改会员头像 4,更改会员昵称 有需要的可以留言或者私信我.

  2. 微信公众平台消息接口开发(30)大众点评商户信息团购及优惠券查询

    微信公众平台开发 微信公众平台开发者 微信公众平台开发模式 大众点评 商户信息 团购 城市优惠券 作者:方倍工作室  原文:http://www.cnblogs.com/txw1958/archive ...

  3. 美团点评业务风控系统构建经验

     美团点评业务风控系统构建经验 义哲 ·2017-01-13 18:20 本文根据"第八届中国系统架构师大会"演讲内容整理而成. 背景 美团最初以团购的形式出现,到现在有了很大 ...

  4. Java程序员如何能进入阿里、大众点评这样的大型互联网公司?

    Java程序员如何能进入阿里.大众点评这样的大型互联网公司? 此面试题是我分享给大家,不管去不去还是能不能进去,知道点还是好的,说不定哪天就用上了. PS:列这么多,你想扼杀竞争对手的吗? 哈哈哈哈哈 ...

  5. 美团大众点评合并:背后技术力量的对比回顾

    美团网和大众点评网在10月8日中午联合发布声明,宣布达成战略合作,两者将共同成立一家新公司.两者也在InfoQ及其组织的大会上进行过多次分享,我们将对美团和大众点评使用的技术进行回顾,来看看这两家电商 ...

  6. 大众点评数据平台架构变迁

    2019独角兽企业重金招聘Python工程师标准>>> 最近和其他公司的同学对数据平台的发展题做了一些沟通,发现各自遇到的问题都类似,架构的变迁也有一定的相似性. 以下从数据& ...

  7. 大众点评订单分库分表实践之路

    http://dbaplus.cn/news-10-264-1.html 本文是关于大众点评订单分库分表实践的一个具体分享,包含对订单库的具体切分策略,以及我个人的一些思考. 背景 订单单表早已突破两 ...

  8. 大众点评app 数据解密和反序列化

    在使用charles 抓大众点评app数据包的时候会发现,请求接口是没有加密的. 但是抓到的数据 全都是乱码,这其实是点评使用了加密算法,所以就需要对应的解密算法. 数据解析操作需要先解压缩,然后再解 ...

  9. 大众点评订单系统分库分表实践

    原大众点评的订单单表早就已经突破两百G,由于查询维度较多,即使加了两个从库,优化索引,仍然存在很多查询不理想的情况.去年大量抢购活动的开展,使数据库达到瓶颈,应用只能通过限速.异步队列等对其进行保护: ...

  10. 数据驱动精准化营销在大众点评的实践

    精准化营销一直以来都是互联网营销业务在细分市场下快速获取用户和提高转化的利器.在移动互联网爆发的今天,数据量呈指数增长,如何在移动和大数据场景下用数据驱动进行精准营销,从而提高营销效能,成为营销业务部 ...

最新文章

  1. 关于学习Python的一点学习总结(35->关联超类)
  2. 异构智能吴韧:物联网是“伪命题”?智联网才是未来
  3. python椭圆函数_python数字图像处理(16):霍夫圆和椭圆变换
  4. vray渲染出图尺寸_Vray渲染出图该如何正确保存
  5. 转载:CSS垂直居中总结
  6. 震撼!波士顿动力最新逆天机器人视频,倒立翻筋斗!人类集体沉默...
  7. Qt的json对象不具备类似指针、引用的行为导致的更新不成功问题解决
  8. centos rpm 安装 perl_Linux【常用软件安装篇】
  9. 【Tensorflow】小白入门实战基础篇(上)
  10. kaggle案例实战
  11. 华为Mate X供货还没跟上,Mate Xs就要来了,余承东亲自确认
  12. 在STM8单片机中自己实现 printf()函数功能
  13. java replaceAll() 方法要用 4 个反斜杠,表示一个反斜杠,该怎么解决
  14. Graphviz样例之有向图
  15. qtextedit改变单个字的颜色_孩子从“妈妈”叫到“妈”,称呼少了一个字,暗示孩子三方面变化...
  16. 编程之美学习之最长子序列的解法
  17. 梦幻西游默认服务器怎么修改器,梦幻西游古龙服务端安装教程
  18. drawio二次开发
  19. 轻松实现钉钉机器人定时发消息
  20. 自动驾驶车载相机rosenberger接口防呆设计

热门文章

  1. Unity3D Shader系列之深度纹理
  2. 数字全息干涉偏振相移实验经验总结
  3. 如何压缩jpg图片的大小
  4. 高校科研项目管理系统
  5. 蚂蚁区块链第19课 联盟链创建及管理
  6. 关于Mysql以及Sqlyog的下载以及使用记录
  7. SQLyog使用教程
  8. 如何卸载赛门铁克(Symantec)企业防病毒客户端软件SEP(Symantec Endpoint Protection)?
  9. 离散数学与计算机的发展,计算机学科发展中离散数学的作用与运用
  10. 基于python+django框架+Mysql数据库的电影院售票选座系统设计与实现