scrapy模拟登陆知乎
source link: https://brantou.github.io/2017/08/22/scrapy-crawl-zhihu/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
折腾了将近两天,中间数次想要放弃,还好硬着头皮搞下去了,在此分享出来,希望有同等需求的各位能少走一些弯路。 源码放在了github上, 欢迎前往查看。 若是帮你解决了问题,或者给了你启发,不要吝啬给我加一星。
1 工具准备
在开始之前,请确保 scrpay 正确安装,手头有一款简洁而强大的浏览器, 若是你有使用 postman 那就更好了。
scrapy genspider zhihu
使用以上命令生成知乎爬虫,代码如下:
# -*- coding: utf-8 -*-
import scrapy
class ZhihuSpider(scrapy.Spider):
name = 'zhihu'
allowed_domains = ['www.zhihu.com']
start_urls = ['http://www.zhihu.com/']
def parse(self, response):
pass
有一点切记,不要忘了启用 Cookies, 切记切记 :
# Disable cookies (enabled by default)
COOKIES_ENABLED = True
2 模拟登陆
过程如下:
- 进入登录页,获取 Header 和 Cookie 信息, 完善的 Header 信息能尽量伪装爬虫, 有效 Cookie 信息能迷惑知乎服务端,使其认为当前登录非首次登录,若无有效 Cookie 会遭遇验证码。 在抓取数据之前,请在浏览器中登录过知乎,这样才使得 Cookie 是有效的。
<img src="/images/zhihu-headers-cookies.jpg” />
Header 和 Cookie 整理如下:
headers = {
'Host':
'www.zhihu.com',
'Connection':
'keep-alive',
'Origin':
'https://www.zhihu.com',
'User-Agent':
'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',
'Content-Type':
'application/x-www-form-urlencoded; charset=UTF-8',
'Accept':
'*/*',
'X-Requested-With':
'XMLHttpRequest',
'DNT':
1,
'Referer':
'https://www.zhihu.com/',
'Accept-Encoding':
'gzip, deflate, br',
'Accept-Language':
'zh-CN,zh;q=0.8,en;q=0.6',
}
cookies = {
'd_c0':
'"AHCAtu1iqAmPTped76X1ZdN0X_qAwhjdLUU=|1458699045"',
'__utma':
'51854390.1407411155.1458699046.1458699046.1458699046.1',
'__utmv':
'51854390.000--|3=entry_date=20160322=1',
'_zap':
'850897bb-cba4-4d0b-8653-fd65e7578ac2',
'q_c1':
'b7918ff9a5514d2981c30050c8c732e1|1502937247000|1491446589000',
'aliyungf_tc':
'AQAAAHVgviiNyQsAOhSntJ5J/coWYtad',
'_xsrf':
'b12fdca8-cb35-407a-bc4c-6b05feff37cb',
'l_cap_id':
'"MDk0MzRjYjM4NjAwNDU0MzhlYWNlODQ3MGQzZWM0YWU=|1503382513|9af99534aa22d5db92c7f58b45f3f3c772675fed"',
'r_cap_id':
'"M2RlNDZjN2RkNTBmNGFmNDk2ZjY4NjIzY2FmNTE4NDg=|1503382513|13370a99ee367273b71d877de17f05b2986ce0ef"',
'cap_id':
'"NmZjODUxZjQ0NzgxNGEzNmJiOTJhOTlkMTVjNWIxMDQ=|1503382513|dba2e9c6af7f950547474f827ef440d7a2950163"',
} - 在浏览器中,模拟登陆,抓取登陆请求信息。
<img src="/images/zhihu-login-args.jpg” />
从图中可以看到 _xsrf 参数, 这个参数与登陆验证信息无关,但很明显是由登陆页面携带的信息。 Google 了下 xsrf 的含义, 是用于防范 跨站请求伪造 。
<img src="/images/zhihu-xsrf.jpg” />
- 整理以上,代码如下:
loginUrl = 'https://www.zhihu.com/#signin'
siginUrl = 'https://www.zhihu.com/login/email'
def start_requests(self):
return [
scrapy.http.FormRequest(
self.loginUrl,
headers=self.headers,
cookies=self.cookies,
meta={'cookiejar': 1},
callback=self.post_login)
]
def post_login(self, response):
xsrf = response.css(
'div.view-signin > form > input[name=_xsrf]::attr(value)'
).extract_first()
self.headers['X-Xsrftoken'] = xsrf
return [
scrapy.http.FormRequest(
self.siginUrl,
method='POST',
headers=self.headers,
meta={'cookiejar': response.meta['cookiejar']},
formdata={
'_xsrf': xsrf,
'captcha_type': 'cn',
'email': '[email protected]',
'password': 'xxxxxx',
},
callback=self.after_login)
]
3 设置Bearer Token
经过上述步骤登陆成功了,有点小激动,有没有! 但苦难到此还远没有结束,这个时候尝试抓取最近热门话题,直接返回 code:401 ,未授权的访问。 授权信息未设置,导致了此类错误,莫非遗漏了什么,看来只能在浏览器中追踪请求参数来侦测问题。 在浏览器的请求中,包含了Bearer Token, 而我在scrapy中模拟的请求中未包含此信息,所以我被服务器认定为未授权的。 通过观察发现 Bearer Token 的关键部分,就是 Cookies 中的 z_c0 对应的信息。
<img src="/images/zhihu-bearer-token.png” />
z_c0 包含的信息,是在登陆完成时种下的,所以从登陆完成返回的登陆信息里,获取要设置的 Cookies 信息, 然后拼接出 Bearer Token,最后设置到 Header 中。
代码整理如下:
def after_login(self, response):
jdict = json.loads(response.body)
print('after_login', jdict)
if jdict['r'] == 0:
z_c0 = response.headers.getlist('Set-Cookie')[2].split(';')[0].split(
'=')[1]
self.headers['authorization'] = 'Bearer ' + z_c0
return scrapy.http.FormRequest(
url=self.feedUrl,
method='GET',
meta={'cookiejar': response.meta['cookiejar']},
headers=self.headers,
formdata={
'action_feed': 'True',
'limit': '10',
'action': 'down',
'after_id': str(self.curFeedId),
'desktop': 'true'
},
callback=self.parse)
else:
print(jdict['error'])
4 获取数据
上述步骤后,数据获取就水到渠成了,为了检测成功与否, 把返回信息写到文件中,而且只获取前五十个,代码如下:
feedUrl = 'https://www.zhihu.com/api/v3/feed/topstory'
nextFeedUrl = ''
curFeedId = 0
def parse(self, response):
with open('zhihu.json', 'a') as fd:
fd.write(response.body)
jdict = json.loads(response.body)
jdatas = jdict['data']
for entry in jdatas:
entry['pid'] = entry['id']
yield entry
jpaging = jdict['paging']
self.curFeedId += len(jdatas)
if jpaging['is_end'] == False and self.curFeedId < 50:
self.nextFeedUrl = jpaging['next']
yield self.next_request(response)
def next_request(self, response):
return scrapy.http.FormRequest(
url=self.nextFeedUrl,
method='GET',
meta={'cookiejar': response.meta['cookiejar']},
headers=self.headers,
callback=self.parse)
获取的数据,采用json格式, 如下所示:
<img src="/images/zhihu-feeds.png” />
5 写在最后
Last Updated 2017-08-23 Wed 15:21.
Render by hexo-renderer-org with Emacs 25.3.2 (Org mode 8.2.10)
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK