首发于xlzd 杂谈
Web Crawler with Python - 08.模拟登录 (知乎)

Web Crawler with Python - 08.模拟登录 (知乎)

(P.S.你也可以在我的博客阅读这篇文章

在抓取数据的过程中,经常会遇到需要登录的网站,尤其是抓取社交(微博、豆瓣等)网站,几乎无法避开模拟登录。由于自己本身很喜欢玩知乎,加上知乎的模拟登录并不是十分复杂,十分利于教学其他人,这篇博客将以知乎的模拟登录为例,讲述如何使用Python代码登录一个网站。

和之前一样,我们打开Chrome的开发者工具,如图所示:


注意上图选中的"Preserve log"选项,很多情况下,网站的登录操作完成之后都会伴随着一个跳转操作,如跳转到首页(如知乎)或跳转到个人页(QQ空间等),这会使我们的登录操作的网络请求记录被之后的请求覆盖掉(这样描述似乎不太准确,原谅我的语文水平一般)。当我们选中此项时,此刻起所有的历史请求都会被保留下来,方便我们查看。

OK,那么我们填入用户名和密码,点击登录按钮,看看发生了什么有趣的操作吧(虽然只是一个小号,还是把密码藏起来吧):


有朋友私信问我,Network下一般有很多请求记录,怎样才能找到哪个是我们需要的请求。一般来讲,对于登录操作,会是一个POST请求,名字中带有login或者signin的会更加可疑,另外一般可以排除掉js、css或者图片的请求,再在剩下的请求中找找,多几次经验之后就很准确了,和那什么事情是一样的,你懂的。

对于这个请求,我们通过右侧的"headers"这个tab可以得到如下信息:

  • 请求地址及方式:zhihu.com/login/email, POST
  • 请求参数:
    1. _xsrf:一会儿我们一起猜
    2. email:注册用户名
    3. password:密码
    4. captcha:验证码
    5. remember_me:记住我
  • 请求头:request headers那一长串

关于什么是xsrf/csrf,这里不过多讲解,摘抄一段Google的解释:

CSRF(Cross Site Request Forgery, 跨站域请求伪造)是一种网络的攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击站点,从而在并未授权的情况下执行在权限保护之下的操作,有很大的危害性。
这个参数反应到对应的网页源码上,是这个:

<input type="hidden" name="_xsrf" value="e0b12dd958040e47458a3e0dc59ee197"/>

还剩下最后一个需要解决的问题:验证码。这里主要是模拟登录知乎,所以不会过多涉及验证码方面的问题,对于这个例子,我们将手动输入验证码,不过在代码的设计上会考虑到如何替换为自动识别验证码的代码。现在我们需要做的,是找到验证码对应的URL。通过点击验证码可以获取新的验证码图片,这个过程中,实际上是向知乎服务器发送了一个请求。通过Chrome的开发者工具(配合知乎JS代码),可以看到得到验证码实际上是向"zhihu.com/captcha.gif"发送了一个GET请求,带有一个参数为当前Unix时间戳。

那么,我们从头理一下,在我们使用浏览器登录知乎的时候,我们究竟做了什么:

  1. 打开知乎登录页(GET,zhihu.com/#
  2. 浏览器(自动)从知乎加载验证码
  3. 输入用户名、密码、验证码
  4. 点击登录

所以,对于我们模拟登录的代码,我们也将复原上述步骤。
首先我们设计一个验证码识别的规范:通过一个函数,接收验证码图片内容,返回验证码文本字符串。这样的接口我们可以通过手动输入识别验证码,也可以通过人肉打码服务,或者通过OCR做机器识别。但是无论怎样的识别方式,我们都可以做到更换实现的时候不影响其它代码。如下,便是通过手动输入的验证码识别实现:

def kill_captcha(data):
    with open('captcha.png', 'wb') as fp:
        fp.write(data)
    return raw_input('captcha : ')

然后,我们的思路是通过一个函数模拟上面分析的步骤登录知乎,返回登录成功的requests.Session对象,我们通过持有这个对象,来完成登录之后才能完成的事情。函数的实现如下:

import time
import requests
from xtls.util import BeautifulSoup

def login(username, password, oncaptcha):
    session = requests.session()

    _xsrf = BeautifulSoup(session.get('https://www.zhihu.com/#signin').content).find('input', attrs={'name': '_xsrf'})['value']
    captcha_content = session.get('http://www.zhihu.com/captcha.gif?r=%d' % (time.time() * 1000)).content
    data = {
        '_xsrf': _xsrf,
        'email': username,
        'password': password,
        'remember_me': 'true',
        'captcha': oncaptcha(captcha_content)
    }
    resp = session.post('http://www.zhihu.com/login/email', data).content
    assert '\u767b\u9646\u6210\u529f' in resp
    return session

由于知乎登录成功之后会返回一个JSON格式的字符串,我们用assert来判断返回的字符串中是否包含登录成功会返回的内容,如果成功,则会返回这个requests.Session对象。另外这里的BeautifulSoup是通过xtls.util导入进来的,是因为默认创建BeautifulSoup对象时需要指定一个解析器,否则就会报一个警告,我实在懒得写,又不想看警告,就自己进行了一些封装,它会自己在你目前已有的解析器中选择(我认为)最好的一个。

按照我们分析的逻辑拼装好相应的代码之后,就可以真正测试一下是否可行了,测试代码非常简单:

if __name__ == '__main__':
    session = login('email', 'password', kill_captcha)
    print BeautifulSoup(session.get("https://www.zhihu.com").content).find('span', class_='name').getText()

登录过程中会要求手输验证码,当然如果你通过其他方式识别了验证码会更加方便。如果登录成功,那么这段测试代码会打印你的知乎昵称到终端上。

小结

这篇博客用登录知乎的示例讲解了如何模拟登录,总结成一句话就是:分析出你的浏览器是怎么操作的,模拟它。看过之后你会明白,原来模拟登录如此简单,那么,换一个网站自己试试吧(比如试试豆瓣吧),如果觉得非常简单,那么就挑战下微博的模拟登录吧。
好了,这篇博客到此结束,这些天比较忙,更新的速度较慢,见谅~~~

发布于 2016-01-12 22:23