专栏名称: 编程派
Python程序员都在看的公众号,跟着编程派一起学习Python,看最新国外教程和资源!
目录
相关文章推荐
Python爱好者社区  ·  开源!Transformers 快速入门书 ·  2 天前  
Python爱好者社区  ·  特朗普上台,第一刀再次扎在了留学生身上。。。 ·  2 天前  
Python开发者  ·  陪你一起刷题面试,字节跳动又出了一款新工具 ·  3 天前  
Python爱好者社区  ·  越来越多的人开始讨厌15薪。 ·  6 天前  
51好读  ›  专栏  ›  编程派

Scrapy 模拟登陆知乎

编程派  · 公众号  · Python  · 2017-09-13 11:30

正文

作者:brantou

原文:https://segmentfault.com/a/1190000010964862

折腾了将近两天,中间数次想要放弃,还好硬着头皮搞下去了,在此分享出来,希望有同等需求的各位能少走一些弯路。

源码放在了github上, 欢迎前往查看。若是帮你解决了问题,或者给了你启发,不要吝啬给加一星。

在开始之前,请确保 scrpay 正确安装,手头有一款简洁而强大的浏览器, 若是你有使用 postman 那就更好了。

  1. scrapy genspider zhihu

使用以上命令生成知乎爬虫,代码如下:

  1. # -*- coding: utf-8 -*-

  2. import scrapy

  3. class ZhihuSpider(scrapy.Spider):

  4.    name = 'zhihu'

  5.    allowed_domains = ['www.zhihu.com']

  6.    start_urls = ['http://www.zhihu.com/']

  7.    def parse(self, response):

  8.        pass

有一点切记,不要忘了启用 Cookies切记切记 :

  1. # Disable cookies (enabled by default)

  2. COOKIES_ENABLED = True

过程如下:

  • 进入登录页,获取 Header 和 Cookie 信息, 
    完善的 Header 信息能尽量伪装爬虫, 有效 Cookie 信息能迷惑知乎服务端,使其认为当前登录非首次登录,若无有效 Cookie 会遭遇验证码。 在抓取数据之前,请在浏览器中登录过知乎,这样才使得 Cookie 是有效的。

Header 和 Cookie 整理如下:

  1. headers = {

  2.    'Host':

  3.    'www.zhihu.com',

  4.    'Connection':

  5.    'keep-alive',

  6.    'Origin':

  7.    'https://www.zhihu.com',

  8.    'User-Agent':

  9.    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36',

  10.    'Content-Type':

  11.    'application/x-www-form-urlencoded; charset=UTF-8',

  12.    'Accept':

  13.    '*/*',

  14.    'X-Requested-With':

  15.    'XMLHttpRequest',

  16.    'DNT':

  17.    1,

  18.    'Referer':

  19.    'https://www.zhihu.com/',

  20.    'Accept-Encoding':

  21.    'gzip, deflate, br',

  22.    'Accept-Language':

  23.    'zh-CN,zh;q=0.8,en;q=0.6',

  24. }

  25. cookies = {

  26.    'd_c0':

  27.    '"AHCAtu1iqAmPTped76X1ZdN0X_qAwhjdLUU=|1458699045"',

  28.    '__utma':

  29.    '51854390.1407411155.1458699046.1458699046.1458699046.1',

  30.    '__utmv':

  31.    '51854390.000--|3=entry_date=20160322=1',

  32.    '_zap':

  33.    '850897bb-cba4-4d0b-8653-fd65e7578ac2',

  34.    'q_c1':

  35.    'b7918ff9a5514d2981c30050c8c732e1|1502937247000|1491446589000',

  36.    'aliyungf_tc':

  37.    'AQAAAHVgviiNyQsAOhSntJ5J/coWYtad',

  38.    '_xsrf':

  39.    'b12fdca8-cb35-407a-bc4c-6b05feff37cb',

  40.    'l_cap_id':

  41.    '"MDk0MzRjYjM4NjAwNDU0MzhlYWNlODQ3MGQzZWM0YWU=|1503382513|9af99534aa22d5db92c7f58b45f3f3c772675fed"',

  42.    'r_cap_id':

  43.    '"M2RlNDZjN2RkNTBmNGFmNDk2ZjY4NjIzY2FmNTE4NDg=|1503382513|13370a99ee367273b71d877de17f05b2986ce0ef"',

  44.    'cap_id':

  45.    '"NmZjODUxZjQ0NzgxNGEzNmJiOTJhOTlkMTVjNWIxMDQ=|1503382513|dba2e9c6af7f950547474f827ef440d7a2950163"',

  46. }

  • 在浏览器中,模拟登陆,抓取登陆请求信息。

从图中可以看到 _xsrf 参数, 这个参数与登陆验证信息无关,但很明显是由登陆页面携带的信息。 Google了下 xsrf 的含义, 用于防范 跨站请求伪造 。

整理以上,代码如下:

  1. loginUrl = 'https://www.zhihu.com/#signin'

  2. siginUrl = 'https://www.zhihu.com/login/email'

  3. def start_requests(self):

  4.    return [

  5.        scrapy.http.FormRequest(

  6.            self.loginUrl,

  7.            headers=self.headers,

  8.            cookies=self.cookies,

  9.            meta={'cookiejar': 1},

  10.            callback=self.post_login)

  11.    ]

  12. def post_login(self, response):

  13.    xsrf = response.css(

  14.        'div.view-signin > form > input[name=_xsrf]::attr(value)'

  15.    ).extract_first()

  16.    self.headers['X-Xsrftoken'] = xsrf

  17.    return [

  18.        scrapy.http.FormRequest(

  19.            self.siginUrl,

  20.            method='POST',

  21.            headers=self.headers,

  22.            meta={'cookiejar': response.meta['cookiejar']},

  23.            formdata={

  24.                '_xsrf': xsrf,

  25.                'captcha_type': 'cn',

  26.                'email': '[email protected]',

  27.                'password': 'xxxxxx',

  28.            },

  29.            callback=self.after_login)

  30.    ]

经过上述步骤登陆成功了,有点小激动,有没有! 但苦难到此还远没有结束,这个时候尝试抓取最近热门话题,直接返回 code:401 ,未授权的访问。 授权信息未设置,导致了此类错误,莫非遗漏了什么,看来只能在浏览器中追踪请求参数来侦测问题。 在浏览器的请求中,包含了Bearer Token, 而我在scrapy中模拟的请求中未包含此信息, 所以我被服务器认定为未授权的。 通过观察发现 Bearer Token 的关键部分,就是 Cookies 中的 zc0_ 包含的信息。

zc0_ 包含的信息,是在登陆完成时种下的,所以从登陆完成返回的登陆信息里,获取要设置的 Cookie 信息, 然后拼接出 Bearer Token,最后设置到 Header 中。

代码整理如下:

  1. def after_login(self, response):

  2.    jdict = json.loads(response.body)

  3.    print('after_login', jdict)

  4.    if jdict['r'] == 0:

  5.        z_c0 = response.headers.getlist('Set-Cookie')[2].split(';')[0].split(

  6.            '=')[1]

  7.        self.headers['authorization'] = 'Bearer ' + z_c0

  8.        return scrapy.http.FormRequest(

  9.            url=self.feedUrl,

  10.            method='GET',

  11.            meta={'cookiejar': response.meta['cookiejar']},

  12.            headers=self.headers,

  13.            formdata={

  14.                'action_feed': 'True',

  15.                'limit': '10',

  16.                'action': 'down',

  17.                'after_id': str(self.curFeedId),

  18.                'desktop': 'true'

  19.            },

  20.            callback=self.parse)

  21.    else:

  22.        print(jdict['error'])

上述步骤后,数据获取就水到渠成了,为了检测成功与否, 把返回信息写到文件中,而且只获取前五十个,代码如下:

  1. feedUrl = 'https://www.zhihu.com/api/v3/feed/topstory'

  2. nextFeedUrl = ''

  3. curFeedId = 0

  4. def parse(self, response):

  5.    with open('zhihu.json', 'a') as fd:

  6.        fd.write(response.body)

  7.    jdict = json.loads(response.body)

  8.    jdatas = jdict['data']

  9.    for entry in jdatas:

  10.        entry['pid'] = entry['id']

  11.        yield entry

  12.    jpaging = jdict['paging']

  13.    self.curFeedId += len(jdatas)

  14.    if jpaging['is_end'] == False and self.curFeedId < 50:

  15.        self.nextFeedUrl = jpaging['next']

  16.        yield self.next_request(response)

  17. def next_request(self, response):

  18.    return scrapy.http.FormRequest(

  19.        url=self.nextFeedUrl,

  20.        method='GET',

  21.        meta={'cookiejar': response.meta['cookiejar']},

  22.        headers=self.headers,

  23.        callback=self.parse)

最终获取的数据如下图所示:

知乎的数据,只有登录完成之后,才可有效的获取,所以模拟登陆是无法忽略不管的。 所谓的模拟登陆,只是在scrapy中尽量的模拟在浏览器中的交互过程,使服务端无感抓包过程。 请求中附加有效的 Cookies 和 Headers 头信息,可有效的迷惑服务端, 同时在交互的过程中,获取后续请求必要信息和认证信息,使得整个流程能不断先前。

若是你遇到什么问题,尽量提出来,欢迎一起来讨论解决。 源码放在了github上, 欢迎前往查看。若是帮你解决了问题,或者给了你启发,不要吝啬给加一星。


题图:pexels,CC0 授权。

点击阅读原文,查看更多 Python 教程和资源。