zhengrenzhe's blog   About

scrapy爬知乎带验证码登录

用scrapy爬知乎时,如果想要看到当前用户都有谁关注了,则必须登录,否则就会被重定向到登录页面。经过几次登录知乎发现,如果浏览器中没有任何知乎的cookie,在登录时就需要验证码。如果是在已经登录知乎的状态下退出再登录,浏览器中还有知乎的cookie,这时登录就不需要验证码,而这也是现在很多博客在说scrapy模拟登录知乎时,需要把浏览器中的cookie复制到scrapy代码里的原因。

在浏览器上没有cookie的情况下,向知乎的登录地址发POST请求

http://www.zhihu.com/login/email

得到的返回值如下:

{
    "r": 1,
    "errcode": 1991829,
    "data": {
       "captcha": "验证码错误"
    },
    "msg": "验证码错误"
}

此时的响应带有三条cookie:

这里的cap_id最重要,它标识了用户用的是哪一个验证码。这是什么意思呢?

知乎的验证码地址如下:

http://www.zhihu.com/captcha.gif?r=1445241534131

后面的请求参数应该是记录当前验证码的请求次数的,因为每次刷新验证码这个数字都会增加。在后面的代码中把它写死也可以,这并不重要。 当第一次POST请求后,再向这个地址发送GET请求来请求验证码图片,此时的请求就会带上上一次响应的cookie。 其中的cap_id会标识此次会话的验证码是什么,因为cap_id是不变的,只是每次的图片不同。(这是我猜的,不一定准确)

现在已经获取了验证码的图片,验证码也就获取到了。这是再向知乎的登录地址发POST请求,请求数据如下

'email': 'xxxxx',
'password': 'xxxx',
'remember_me':'true',
'_xsrf': 'xxxxx',
'captcha': 'xxxxx',

其中的_xsrf是在登录页面的一个隐藏input,但是登录请求中不包含这个字段也行。captcha则是验证码。

现在已经知道了知乎的登录原理,写scrapy代码就很快了。scrapy创建了一个项目后,在 settings.py 中要开启cookie中间件,添加如下字段就可以

COOKIES_ENABLED=True
COOKIES_DEBUG=True

这是scrapy发出的每一个请求都会自动的带上cookie,与浏览器表现的一样。下方就是基本的登录功能实现的代码:

#-*- coding: UTF-8 -*-

import scrapy, json
from PIL import Image
from StringIO import StringIO
from scrapy.selector import Selector
from scrapy.http import FormRequest, Request

class ZhihuSpider(scrapy.spiders.Spider):

    name = "zhihu"

    #  最开始的请求
    def start_requests(self):
        # 这个请求用来获取cookie,以及获取xsrf字段
        return [FormRequest("http://www.zhihu.com/login/email", callback=self.init)]

    def init(self,response):
        # 获取了cookir和xsrf字段后向验证码地址发请求,此时scrapy会自动在请求上带上cookie
        self._xsrf = Selector(response).xpath('//input[@name="_xsrf"]/@value').extract()[0]
        return Request('http://www.zhihu.com/captcha.gif?r=1445224613695', callback=self.login)

    def getcapid(self,response):
        # 显示验证码,接收用户输入,返回验证码。显示图片使用PIL库,在OSX上可以直接调用预览显示图片,别的系统不知道了。
        Image.open(StringIO(response.body)).show()
        return raw_input('输入验证码: ')

    def login(self,response):
        # 登录动作
        return FormRequest("http://www.zhihu.com/login/email",formdata={
            'email': 'xxxxxxx',
            'password': 'xxxxxx',
            'remember_me':'true',
            '_xsrf': self._xsrf,
            'captcha': self.getcapid(response),
        },callback=self.after_login)

    def after_login(self,response):
        # 现在已经收到登录请求的响应了
        if json.loads(response.body)['msg'].encode('utf8') == "登陆成功":
            yield self.make_requests_from_url('http://www.zhihu.com/people/droiz')
        else:
            print "验证码错误"

    def parse(self,response):
        print response.body

← 前端音频处理的超级无敌终极大法-WebAudio  使用ES6的Generator完成异步操作 →