利用requests模块模拟登录百度并请求百小度

因为兴趣想测试一下百小度,想弄一些常见的问题测试一下百小度回答的质量怎么样,所以想到用python的requests模块模拟登录请求,先说一下我在做的时候遇到的三个问题.

1.获取token值
2.在登录的表单里面发现密码是通过加密的
3.登录表单里哪些字段是必须的哪些非必须说实话我还是没弄明白

首先我们打开Fiddler,如果和我一样是用chrome而且用了代理插件的,先把插件暂时设置为系统代理
接着我们打开百度首页,登录自己的帐号,接着我们会看到Fiddler已经抓取到了很多信息了。
enter image description here
我们发现获取token的地址如下
'https://passport.baidu.com/v2/api/?getapi&tpl=pp&apiver=v3&class=login'
然后我们直接用requests请求一下这个地址。至于为什么要用requests,大概是因为urllib,urllib2太多函数了记不住,很麻烦的样子,而且在python3.0以后整合到了一起
requests的官方文档在这.

1
2
3
4
5
6
7
8
9
10
11
import requests
header_base = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4'
}
def _get_token():
ret = requests.get('https://passport.baidu.com/v2/api/?getapi&tpl=pp&apiver=v3&class=login', headers=header_base).content
print ret

然后运行代码,发现返回

1
2
3
4
D:\Python27\python.exe E:/pythonPractice/RequestTest.py
{"errInfo":{ "no": "0" }, "data": { "rememberedUserName" : "", "codeString" : "", "token" : "the fisrt two args should be string type:0,1!", "cookie" : "0", "usernametype":"", "spLogin" : "rate", "disable":"", "loginrecord":{ 'email':[ ], 'phone':[ ] } }}
Process finished with exit code 0

我们把获取token的地址在浏览器打开,发现返回的是

1
{"errInfo":{ "no": "0" }, "data": { "rememberedUserName" : "", "codeString" : "", "token" : "28563c348ee57b720560d7d278337d80", "cookie" : "1", "usernametype":"", "spLogin" : "rate", "disable":"", "loginrecord":{ 'email':[ ], 'phone':[ ] } }}

为什么不一样呢
答案是cookies,你重新开一个隐身窗口打开那个地址会发现获得和requests请求一样的结果.
所以我们先访问一下百度的首页,然后获取cookies,再访问那个获取token的地址
代码如下

1
2
3
4
5
6
7
def _get_token():
s = requests.session()
s.get('http://www.baidu.com')
ret = s.get('https://passport.baidu.com/v2/api/?getapi&tpl=pp&apiver=v3&class=login').content
token = re.search('"token" : "(?P<tokenVal>.*?)"', ret)
token_final = token.group('tokenVal')
return ret

结果成功获取到了token值,接着我们来看看登录表单里面还需要哪些值
enter image description here
还有个rsakey,另外我们发现密码被加密过了,我们接着去找一下获取pubkey的地址在哪
enter image description here
这下任务明晰了,我们接着去获取一下pubkey值,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
def _get_publickey():
postData = {
'token': token,
'tpl':'mn',
'apiver':'v3',
'tt':'1440514499019',
'gid':'06019B9-F329-42A0-B64E-F1825D5F578E',
'callback':'bd__cbs__rl713j'
}
content = requests.get('https://passport.baidu.com/v2/getpublickey', params=postData).content
jdata = json.loads(content.replace('\'','"').replace('bd__cbs__rl713j(','').replace('})','}'))
return (jdata['pubkey'], jdata['key'])

运行代码如下:

1
2
3
4
5
6
7
8
9
10
11
D:\Python27\python.exe E:/pythonPractice/RequestTest.py
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyTKKeJYXAY3/5c2VuFBIGED6S
32zA8Tx6jPx+Z+ZwtCMkPAGAGzP36b5yQ1Ymq8ad7Ntrfw/IpqCj9qW/N6gpjlfD
S0juemauXasTYfFJ+1fNFxjc9n5XFET7tV6V5EDEcCw3EC03pVzQS+95uF+WXj/4
P2YOoL0qRfkltKmoNwIDAQAB
-----END PUBLIC KEY-----
xjhFm4PqjPLGPpMjxCU3xSU47YTv2LQd
Process finished with exit code 0

熟悉rsa的人应该看出来了我们获取的是pem格式的公钥
我们引入rsa模块进行解密.
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def getPUBLICKey(password,token):
s = requests.session()
s.get('http://www.baidu.com')
raw_content = s.get(token_url, headers=header_base).content
token = re.search('"token" : "(?P<tokenVal>.*?)"', raw_content)
token_final = token.group('tokenVal')
postData = {
'token': token,
'tpl':'mn',
'apiver':'v3',
'tt':'1440514499019',
'gid':'06019B9-F329-42A0-B64E-F1825D5F578E',
'callback':'bd__cbs__rl713j'
}
content = s.get('https://passport.baidu.com/v2/getpublickey', params=postData).content
jdata = json.loads(content.replace('\'','"').replace('bd__cbs__rl713j(','').replace('})','}'))
pubkey, rsakey = (jdata['pubkey'], jdata['key'])
key = rsa.PublicKey.load_pkcs1_openssl_pem(pubkey)
print key
password_rsaed = base64.b64encode(rsa.encrypt(password, key))
return password_rsaed, rsakey

万事俱备只欠东风,现在可以登录百度了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def _login(token, username, password):
password_rsaed, rsakey = _get_publickey(password)
code_string = ''
login_data = {'staticpage': 'http://www.baidu.com/cache/user/html/v3Jump.html',
'charset': 'UTF-8',
'token': self.user['token'],
'tpl': 'pp',
'subpro': '',
'apiver': 'v3',
'tt': str(int(time.time())),
'codestring': code_string,
'isPhone': 'false',
'safeflg': '0',
'u': 'https://passport.baidu.com/',
'quick_user': '0',
'logLoginType': 'pc_loginBasic',
'loginmerge': 'true',
'logintype': 'basicLogin',
'username': username,
'password': password_rsaed,
'mem_pass': 'on',
'rsakey': str(rsakey),
'crypttype': 12,
'ppui_logintime': '50918',
'callback': 'parent.bd__pcbs__oa36qm'}
#Jpost_data = json.dumps(post_data)
self.session.post('https://passport.baidu.com/v2/api/?login', data=login_data, headers=header_base)

登录以后可以get一下百度的首页然后搜索一下有没有登陆后的用户名就可以确认是否真的登录了,另外因为cookies的关系强烈建议创建一个session会话,会自动保存cookies,不需要手动加载,另外登录以后最好手动保存一下cookies文件,防止频繁登录被ban或者要求验证码,验证码因为没碰到所以暂时没去研究.
接下来同样的步骤去抓百小度的包获取请求地址以及登录表单就可以了.

1
2
3
4
5
6
7
8
9
10
11
12
def _getXiaodu(cmd):
load_data = {
'sample_name': 'bear_brain',
'request_query': cmd,
'bear_type': 2,
'request_time': str(int(time.time()*1000)),
'callback': 'jQuery110206030894953291863_1440604236835',
'_': 1440604236840
}
r = s.get('https://sp0.baidu.com/yLsHczq6KgQFm2e88IuM_a/s', params=load_data, headers=header_base).content
return r

运行以后的结果如下

1
2
3
4
D:\Python27\python.exe E:/pythonPractice/RequestTest.py
/**/jQuery110206030894953291863_1440604236835({"status": 0, "result_list": [{"source_type": "talk_service", "service_id": "", "result_confidence": 100, "result_type": "txt", "result_content": "{\"answer\":\"(抠鼻)我妹不在\",\"question\":\"\"}\n"}, {"source_type": "bear_grown", "result_confidence": 100, "result_type": "txt", "result_content": "{\"answer\":\"主人,你的陪伴让我又长大了一点,亲密度+1!小度越来越稀罕你啦!\"}"}], "request_uid": "117192806", "request_time": "1440836860993", "tips_num": 0, "se_query": "", "emotion": 0, "level_info": "{ \"level\": 5, \"new_equipments\": \"1\" }"})
Process finished with exit code 0

我问的是”你妹啊”,百小度还挺有意思的
返回的是一个json的字符串,处理一下就可以,直接打印的话可以用pprint .
以上.