weibo.cn模拟登录器的python重构

    之前封装过一个完整的微博爬虫工具包,但是在我查找python多进程爬虫的相关资料时发现,其实使用urllib2写网络爬虫已经是陈年往事,如今大家都用requests包,就像明明有.NET和Qt框架偏偏要用MFC框架,我是自找麻烦了。在使用requests包逐个重构网络爬虫部件的时候,对weibo.cn的模拟登录过程有了一些新的理解,于是来写篇文介绍一下。

    我们再来重新分析一遍weibo.cn的登录过程。出发前先来检查一下装备:

  • firefox浏览器
  • 一个firefox浏览器下的HTTP报文记录分析插件——httpfox
  • python2语言环境——python 2.7
  • 一个python HTTP包——requests
  • 一个python XML解析包——BueatifulSoup4

    妥,先来用httpfox记录一下手工登录的过程:
图1 weibo.cn模拟登录过程总览
    无视掉第二条失败的图片请求,剩下的就是weibo.cn登录的全过程。我们逐条分析:
图2 请求登录页面
    上图中是第一条请求的报文头部分,第一条请求是在访问weibo.cn的登录页面。请求到的页面源码中,掐头去尾,比较关键的部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form action="?backURL=http%3A%2F%2Fweibo.cn%2F&amp;backTitle=%E5%BE%AE%E5%8D%9A&amp;vt=1&amp;revalid=2&amp;ns=1" method="post">
<div>
手机号/电子邮箱/会员帐号:<br/>
<input type="text" name="mobile" size="30" value="" /><br/>
密码:<br/>
<input type="password" name="password_3523" size="30" /><br/>
<input type="checkbox" name="remember" checked="checked" />记住登录状态,需支持并打开手机的cookie功能。<br/>
<input type="hidden" name="backURL" value="http%3A%2F%2Fweibo.cn%2F" />
<input type="hidden" name="backTitle" value="微博" />
<input type="hidden" name="tryCount" value="" />
<input type="hidden" name="vk" value="3523_4ea5_1803939589" />
<input type="submit" name="submit" value="登录" />
</div>
</form>

    可以看到表单的action地址、密码输入框的name、隐藏变量vk的值都是变动的,这三样东西正是我们在模拟登录时需要从登录页面解析获得的。对应的python代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 尽管不设置报文头也不会被新浪服务器阻拦,还是做一些必要的伪装与配置比较保险
headers = {
'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:42.0) Gecko/20100101 Firefox/42.0',
'DNT': '1',
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
}

def get_post_needs():
url = 'http://login.weibo.cn/login/?ns=1&revalid=2&backURL=http://weibo.cn/&backTitle=微博&vt='
text = requests.get(url, headers=headers).text
soup = BeautifulSoup(text, 'lxml')
action_url = 'http://login.weibo.cn/login/' + soup.form['action']
password_tag = soup.find('input', type = 'password')
password_name = re.search('(?<=name=")\w+(?=")', str(password_tag)).group(0)
vk_tag = soup.find('input', attrs = {'name': 'vk'})
vk = re.search('(?<=value=")\w+(?=")', str(vk_tag)).group(0)
return action_url, password_name, vk

    页面解析完毕还需要填充表单,对应的python代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
def get_post_data(username, password, password_name, vk):
form_data = {
'mobile': username,
password_name: password,
'remeber': 'on',
'backURL': 'http://weibo.cn/',
'backTitle': '微博',
'tryCount': '',
'vk': vk,
'submit': '登录'
}
return form_data

    填充完登录表单,就该post出去了,由4个连续的状态码为302的请求不难看出,发送的post请求随后被进行了足足4次重定向。
    可以看到,第一次重定向后,请求访问了newlogin.sina.cn,此时新浪服务器已经给我们的请求加上了两个cookie字段(httpfox可以以字典的形式查看每个请求的cookie字段,这里我就不上图了),一个叫“_T_WM”另一个叫“SUB”,虽然不明白这两个字段的含义,但是经过实验得知,其实这个“SUB”字段就可以用来做登录验证了。
    第二次重定向请求访问了passport.weibo.com,这次新浪服务器给请求重新填充了许多cookie字段,但是仍然包含一个“SUB”字段,没错,这此的“SUB”也可以拿来做登录验证。
    类似的,第三次访问weibo.cn的重定向也填充了新cookie字段,我们又获得了一个可用的“SUB”字段。
    至于最后一次重定向,只是通过一个几乎纯JavaScript组成的页面让我们以已登录的状态跳转到前面表单参数中的backURL指向的页面。
    在使用python记录cookie字段时需要注意,这种经过若干次重定向的”长会话“不能直接使用Request对象来发送最初的post请求,而是需要一个能处理”长会话“的Session对象的帮助,获得cookie字段的对应python代码如下:

1
2
3
4
5
6
7
8
9
def wap_login(username, password):
url, password_name, vk = get_post_needs()
data = get_post_data(username, password, password_name, vk)
session = requests.Session()
response = session.post(url, headers=headers, data=data)
# 以下三种来自不同domain的名为SUB的cookie字段都可以用来验证登录状态
# return session.cookies.get('SUB', domain='.sina.cn')
# return session.cookies.get('SUB', domain='.weibo.com')
return session.cookies.get('SUB', domain='.weibo.cn')

    拿到了必要的cookie字段,模拟登录过程其实就已经完成了,接下来我们也可以用下面的代码测试一下cookie字段是否管用:

1
2
3
4
5
6
url = 'http://weibo.cn/moegirlwiki'
username = raw_input('请输入新浪通行证用户名:')
password = getpass('请输入新浪通行证密码:')
cookies = {'SUB': wap_login(username, password)}
response = requests.get(url, cookies=cookies)
print '登录成功!' if response.url == url else '登录失败!'

    如果读者接触过weibo.com的模拟登录过程想必明白,weibo.cn的登录过程简单多了,更重要的是,必要cookie字段在weibo.cn和weibo.com是通用的,那我们又何必选择更麻烦的模拟登录途径呢?

Fork me on GitHub