超详细的Python实现百度云盘模拟登录(模拟登录进阶)

这是第二篇从简书搬运过来的文章(你们别误会,是我原创的)。由于前一篇文章,我看反响还挺好的,因此把这篇也搬运过来了,其实目的仍是为宣传本身的分布式微博爬虫(该项目的内容和工做量都很饱满啊,你们若是以为有帮助,请多多支持啊)。大概从下一篇起,就会一步一步讲解如何构建分布式爬虫再到微博分布式爬虫的方法了。由于关于初级爬虫的文章太泛滥了,因此我就不会讲比较基础的东西。java


今天我给你们讲讲如何模拟登录百度云盘(该分析过程也适用于百度别的产品,好比模拟登录百度搜索首页,它们的加密流程彻底同样,只是提交参数有微小差异)。方法不只适用于百度云,别的一些比较难以模拟登录的网站均可以按照这种方式分析。python

阅读文章以前,有一些东西须要给你们阐述:git

  • 本文并无对验证码识别进行分析,由于我以为写爬虫最主要的不是识别验证码,而是如何规避验证码,我本身写的分布式微博爬虫也是一直想着规避验证码,而不是去识别它,由于我以为那并非爬虫该作的事情(至少不是中低阶爬虫的事,我以为那是模式识别的事情了)。github

  • 本文要求读者具备模拟登录(主要是抓包和阅读js代码)和密码学的基本知识。web

模拟登录微博的分析流程同样,咱们首先要作的是以正常人的流程完整的登陆一遍百度网盘。在打开浏览器以前,先打开抓包工具,之前我在win平台用的是fiddler,如今因为电脑是mac系统,因此选择charles进行抓包。若是有同窗没有charles的使用经验,那么须要先了解如何让charles能抓取到本机的https数据包。因为使用charles抓包不是本文的重点,因此我就简略说一下:算法

  1. 安装charles证书。经过菜单'help'->'ssl proxying'->'install charles root certificate'进行安装,安装事后把证书设置为始终信任json

  2. 修改charles的proxy settings。 选择proxy->proxy settings,而后勾选“enable transparent HTTP proxying”segmentfault

  3. 修改Charles的SSL proxy settings。选择proxy->ssl proxy settings,在弹出的对话框中勾选"enable ssl proxying",并在location部分点击add,添加须要捕获的站点和443端口,如图:api

charles ssl proxy设置图

charles设置好了以后,咱们再使用浏览器直接打开百度网盘首页 。注意打开以前若是之前登陆过百度网盘,必定要先清除百度网盘的cookie,若是不清楚本身之前登陆过没,那么最好把关于百度的cookie都清除了吧。若是清除得不完全,极可能会错过很关键的一步,我先按下不表。经过抓包,咱们能够看到请求百度网盘的首页,大概有这些请求:浏览器

百度网盘首页请求数据

这里other hosts是本机别的请求,因此直接被我忽略了(经过设置请求为“focus”或者“ignore”)。下图给的是设置方法,主要是FocusDisable SSL Proxying

设置请求为focus,而且注意**disable ssl proxying**

你们能够查看具体每一个请求的内容和响应,因为篇幅限制,我就不啰嗦了。而后咱们在登陆页输入登陆帐号(先别输入密码和点击登陆,若是有想不明白的同窗能够阅读个人微博登录分析),而后观察charles的请求,会发现又多了一条请求:

在输入登陆帐号事后js触发的请求

咱们看看它返回的内容:

输入帐号后服务端返回的内容

能够看到有效信息大概有两个: pubkeykey,它们的用途咱们都还不知道,可是看命名可知大概pubkey是某种加密算法的公钥。

而后咱们输入密码,点击登陆,能够看到charles的请求:

登陆中的请求

上图圈出来的请求就是提交的密码和登陆帐号等信息,这个只有你们本身挨着请求查看,才能够肯定哪一个是post的请求。咱们查看post的参数:

post参数

如今终于Get到重点了,主要就是要把这些提交的参数的生成方式弄清楚。若是有过模拟登录或者爬虫编写经验的同窗,都应该知道请求参数构造以前必须分析清楚哪些参数是变的,哪些参数是不变的,变的哪些参数比较有规律,哪些没有规律。这个分析过程是经过反复登陆和抓包,对比post数据来完成的。咱们经过反复登陆和对比post的数据,能够发现:

staticpage、charset、 tpl、 subpro、apiver、codestring、 safeflg、u、 isPhone、detect、quick_user、logintype、logLoginType、idc、loginmerge、foreignusername、username、mem_pass、crypttype、countrycode、dv

等参数不会变化。因此咱们只须要分析变化的参数。

变化的参数当中,tt看样子基本能够肯定是请求时间戳(须要分析它是多少位,精确到毫秒仍是秒),其它好像都没什么规律。因为微博模拟登录的经验,咱们基本能够判断出password这个参数是最难分析的(从帐户安全角度上来讲也应该是加密最复杂的),咱们放到后面分析。

那么咱们先来分析token字段吧。参数不可能凭空产生,来源只有两种可能:一种是经过服务端返回, 另一种是经过请求回来的js构造。在分析token产生的时候,咱们须要用到charles的查找功能(良心推荐,很强大),它能够查找到整个登陆流程中,包含某个查找字符串的全部请求和响应。下图中的"望远镜"图标就是查找功能。

使用charles查找包含token值的请求和响应

经过查找,咱们能够看到有14个地方包含了token的值,咱们发现基本都是使用token做为请求参数的,不过有一个结果是返回token值的:

token的返回

它的请求url是

https://passport.baidu.com/v2...

请求参数格式化一下,可能更方便查看:

获取token的请求参数

根据上面介绍的分析变量与不变量的思路,这里咱们能够看到要获取到token,须要知道gidcallback的构造方法。而后用和分析token一样的方式,咱们来分析gid的产生。经过在charles中查找gid的值,咱们发现找到的结果全是在请求中,并无在响应中找到该值,说明该值是经过js生成的而不是经过服务端返回的。既然是经过js生成的,咱们须要找到该js文件。怎么找呢?咱们在charles中输入gid,再来看看查找的结果,注意此次咱们重点关注哪一个js文件中出现gid,不然查找的结果太多,看起来会比较费力。经过查找,能够看到名为login_tangram_c36ce25.js这个文件中频繁出现了*gid这个参数,基本能够肯定这个js文件很关键,这也是我先前说的在抓包分析以前须要把百度的cookie等历史数据清除的缘由,不然该js文件可能已经缓存了,charles中就查不到该js。咱们把该js文件下载下来,经过webstorm将其中的代码格式化,再查找gid,能够看到这段代码

gid的生成方式

其中的this.guideRandom函数就是生成gid的函数,由于咱们在webstorm中查找gid字符串的时候,能够发现不少以下图所示的语句,只须要定位到guideRandom便可

gid的声明

咱们如今找到了gid的生成方式了,若是读不懂这段js也不要紧,能够直接使用pyv8或者pyexecjs等库将运行后的js结果返回给python使用。而后咱们再回到获取token的请求参数那张图,发现还有个callback参数须要分析。同gid分析过程同样,咱们先搜索callback的值bd__cbs__v2xmbc,发现只有请求中包含,基本能够肯定它是经过js产生的,而加密js文件咱们已经找到了。若是你惧怕可能不是上面的那个js文件,咱们也能够经过在charles中搜索callback这个字符串,能够发现就是该js文件。经过在webstorm中搜索callback关键词(经过前面屡次登陆抓包分析,可发现callback的bd__cbs_前缀不会改变,这个也能够是搜索依据),能够找到callback的生成方式
<pre>
var i, r, o, a = this.url, s = document.createElement("SCRIPT"), u = "bd__cbs__", d = t || {},

l = d.charset, c = d.queryField || "callback", f = d.timeOut || 0,
p = new RegExp("(\\?|&)" + c + "=([^&]*)");

// 下面就是callback的生成逻辑
baidu.lang.isFunction(e) ? (i = u + Math.floor(2147483648 * Math.random()).toString(36)
</pre>

截至目前,咱们已经弄清楚了gidcallback的生成方式了,这样咱们就能够经过构造请求来获取到token了。咱们再返回post参数这张图片,能够看到还有passwordrsakeyppui_logintime这三个字段还须要分析。而经过搜索rsakey的值,能够看到其实它就是 图片输入帐号后服务端返回的内容 中的key的值,咱们能够经过

https://passport.baidu.com/v2...

这个请求获取到。请求的参数如图,都是咱们前面分析过而且可以获得的参数:

获取key和pubkey的请求参数

如今咱们就只有ppui_logintimepassword两个字段没分析了。
老规矩,咱们先在charles中搜索ppui_logintime的值,发现只有一个请求中出现了。那么它确定是js生成的,它是如何生成的呢?咱们又在咱们获取的login_tangram_c36ce25.js文件中搜索ppui_logintime这个字符串,能够发现这段代码:
<pre>
login: {

memberPass: "mem_pass",
            safeFlag: "safeflg",
            isPhone: "isPhone",
            timeSpan: "ppui_logintime",
            logLoginType: "logLoginType"
        }

</pre>
而后咱们再看timeSpan是如何生成的。能够看到这段代码

r.timeSpan = (new Date).getTime() - e.initTime

大概是一个时间差:当前时间-初始化时间。当前时间容易获取,那么初始化时间究竟是什么初始化呢?继续追踪initTime能够发现这段代码
<pre>
_initApi: function (e) {

var t = this;
        t.initialized = !0, t.initTime = (new Date).getTime(), passport.data.getApiInfo({
            apiType: "login",
            gid: t.guideRandom || "",
            loginType: t.config && t.config.diaPassLogin ? "dialogLogin" : "basicLogin"
        })

.....
</pre>
initApi中的initTime大概就是页面请求完成的时间,因此ppui_time应该就是登陆页面初始化完成到点击登陆按钮的时间差,为了方便,咱们只须要取抓包获取的值便可。

如今分析password参数,这个参数也是分析难度最大的参数了。此次咱们直接在加密js文件中搜索password关键词,能够搜索到不少地方有password这个字符串,那么如何作筛选呢?须要咱们有一点js的基础知识,在每一个匹配到password的地方都读读源码,大概知道它作什么的就好了。最后,咱们能够定位到这段代码:
<pre>
var r = baidu.form.json(e.getElement("form"));
r.token = e.bdPsWtoken, passport.data.setContext(baidu.extend({}, e.config)), r.foreignusername && (r.foreignusername = e._SBCtoDBC(r.foreignusername)), r.userName = e._SBCtoDBC(r.userName), r.verifyCode = e._SBCtoDBC(r.verifyCode);
var o = e._SBCtoDBC(e.getElement("password").value);
if (e.RSA && e.rsakey) {

var a = o;
    a.length < 128 && !e.config.safeFlag && (r.password = baidu.url.escapeSymbol(e.RSA.encrypt(a)), r.rsakey = e.rsakey, r.crypttype = 12)
                   }

var s, u = e.getElement("submit"), d = 15e3;
</pre>

上述代码既有rsakeyform又有password关键字,那么十有八九就是加密password的方法了。主要加密语句是:

e.RSA.encrypt(a)

咱们查看encrypt()的实现
<pre>
Jn.prototype.encrypt = function (e) {

try {
        return xn(this.getKey().encrypt(e))
    } catch (t) {
        return !1
    }

}
</pre>

这里的过程大概就是先用this.getKey()返回的对象对e进行加密,而后再进行一次xn(),这里js的代码十分复杂,若是想把对应的js转化为python实现,须要很深的js和python功底,可是这个转换已经有人帮咱们作了。这里的encrypt()便是使用rsa非对称加密算法对密码进行加密。而xn()是base64编码方法。判断encrypt()是rsa加密算法的依据是该js文件中出现了屡次rsakey,而且也有
<pre>
fn.prototype.getPrivateKey = function () {

var e = "-----BEGIN RSA PRIVATE KEY-----\n";
        return e += this.wordwrap(this.getPrivateBaseKeyB64()) + "\n", e += "-----END RSA PRIVATE KEY-----"
    }, fn.prototype.getPublicKey = function () {
        var e = "-----BEGIN PUBLIC KEY-----\n";
        return e += this.wordwrap(this.getPublicBaseKeyB64()) + "\n", e += "-----END PUBLIC KEY-----"
    }

</pre>
这类代码做为佐证。判断后者是base64算法的依据是xn()函数中出现了

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

这类字符串,它是base64编码的一个基础部分。

因此说这里的分析须要你们有基本的密码学知识,不然分析会比较困难。这里友情提示一句,目前主流的大中型网站都会使用rsa算法对密码进行加密,因此你们须要有这个意识。可是不要求你们会实现rsa等加密算法,由于不管是python仍是js仍是java都有相关的实现了,咱们只须要会分析会使用就好了。

到这里全部的参数分析就结束了,咱们能够经过代码进行验证。


上面详细介绍了百度整个登陆流程。咱们来总结一下:

  1. 先经过加密js文件获取到gid,callback参数

  2. 根据https://passport.baidu.com/v2/api/?getapi&...这个(get)请求获取到token

  3. 根据https://passport.baidu.com/v2/getpublickey?token=...这个(get)请求获取到rsakeypubkey

  4. 根据获取到的pubkey对password进行加密,而后再进行base64编码操做

  5. 将全部固定和构造的参数进行post请求,post请求的url为https://passport.baidu.com/v2/api/?login,若是该post返回err_no=0,那么模拟登录就成功了,不然则失败,会返回响应的err_no


前面费了这么大的力气分析百度的登陆流程,若是实在是想走捷径的,可使用selenium自动化的方式登陆,这个我也给了相关实现。读过个人新浪微博模拟登录的同窗大概对介于直接登陆和使用selenium自动化登陆之间的方法还有一些印象吧,这里我并无使用该方法,由于若是要使用该方法的话,须要改动一些js来使代码跑通。有兴趣的同窗能够试试,应该比较有意思。

若是有同窗感受本文有一些难度,能够尝试一些简单的模拟登录,好比知乎和CSDN等,我写过一篇关于CSDN模拟登录的文章,微博模拟登录应该比本文的分析难度稍微要小一点,若是有兴趣,也能够读读。

我把代码放到个人开源项目smart_login上了,点击这里能够查看百度模拟登录流程的实现,若是有不清楚的同窗,建议对照代码再来读本文,可能会更加清晰,若是实际动手按本文的分析流程
走一遍,那么可能会有一些收获。

此外,打一个广告,若是对如何构建分布式爬虫或者大规模微博数据采集感兴趣的话,能够专一一下个人开源分布式微博爬虫项目,目前还在快速迭代,单从功能角度来说,抓取部分基本上快实现和测试完了。

若是你们以为本文对你们有帮助,不妨点个推荐,若是代码对你们有帮助,也不妨点个star,以表示对个人鼓励吧。爱给别人点赞的孩子,运气始终不会太差。

相关文章
相关标签/搜索