折騰了將近兩天,中間數(shù)次想要放棄双谆,還好硬著頭皮搞下去了壳咕,在此分享出來,希望有同等需求的各位能少走一些彎路顽馋。
源碼放在了github上谓厘, 歡迎前往查看。
若是幫你解決了問題寸谜,或者給了你啟發(fā)竟稳,不要吝嗇給加一星。
工具準(zhǔn)備
在開始之前,請確保 scrpay 正確安裝住练,手頭有一款簡潔而強(qiáng)大的瀏覽器地啰, 若是你有使用 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
有一點(diǎn)切記讲逛,不要忘了啟用 Cookies, 切記切記 :
# Disable cookies (enabled by default)
COOKIES_ENABLED = True
模擬登陸
過程如下:
- 進(jìn)入登錄頁亏吝,獲取 Header 和 Cookie 信息,
完善的 Header 信息能盡量偽裝爬蟲盏混, 有效 Cookie 信息能迷惑知乎服務(wù)端蔚鸥,使其認(rèn)為當(dāng)前登錄非首次登錄,若無有效 Cookie 會遭遇驗證碼许赃。 在抓取數(shù)據(jù)之前止喷,請在瀏覽器中登錄過知乎,這樣才使得 Cookie 是有效的混聊。
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"',
}
-
在瀏覽器中弹谁,模擬登陸,抓取登陸請求信息句喜。
從圖中可以看到 _xsrf 參數(shù), 這個參數(shù)與登陸驗證信息無關(guān)预愤,但很明顯是由登陸頁面攜帶的信息。 Google了下 xsrf 的含義咳胃, 用于防范 跨站請求偽造 植康。
-
整理以上,代碼如下:
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': 'xxxxxx@163.com', 'password': 'xxxxxx', }, callback=self.after_login) ]
設(shè)置Bearer Token
經(jīng)過上述步驟登陸成功了展懈,有點(diǎn)小激動销睁,有沒有! 但苦難到此還遠(yuǎn)沒有結(jié)束存崖,這個時候嘗試抓取最近熱門話題冻记,直接返回 code:401 ,未授權(quán)的訪問。 授權(quán)信息未設(shè)置来惧,導(dǎo)致了此類錯誤冗栗,莫非遺漏了什么,看來只能在瀏覽器中追蹤請求參數(shù)來偵測問題违寞。 在瀏覽器的請求中,包含了Bearer Token, 而我在scrapy中模擬的請求中未包含此信息偶房, 所以我被服務(wù)器認(rèn)定為未授權(quán)的趁曼。 通過觀察發(fā)現(xiàn) Bearer Token 的關(guān)鍵部分,就是 Cookies 中的 z_c0 包含的信息棕洋。
z_c0 包含的信息挡闰,是在登陸完成時種下的,所以從登陸完成返回的登陸信息里,獲取要設(shè)置的 Cookie 信息摄悯, 然后拼接出 Bearer Token,最后設(shè)置到 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'])
獲取數(shù)據(jù)
上述步驟后,數(shù)據(jù)獲取就水到渠成了奢驯,為了檢測成功與否申钩, 把返回信息寫到文件中,而且只獲取前五十個,代碼如下:
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)
最終獲取的數(shù)據(jù)如下圖所示:寫在最后
知乎的數(shù)據(jù),只有登錄完成之后瘪阁,才可有效的獲取撒遣,所以模擬登陸是無法忽略不管的。 所謂的模擬登陸管跺,只是在scrapy中盡量的模擬在瀏覽器中的交互過程义黎,使服務(wù)端無感抓包過程。 請求中附加有效的 Cookies 和 Headers 頭信息豁跑,可有效的迷惑服務(wù)端廉涕, 同時在交互的過程中,獲取后續(xù)請求必要信息和認(rèn)證信息艇拍,使得整個流程能不斷先前狐蜕。
若是你遇到什么問題,盡量提出來淑倾,歡迎一起來討論解決馏鹤。
源碼放在了github上, 歡迎前往查看娇哆。
若是幫你解決了問題湃累,或者給了你啟發(fā),不要吝嗇給加一星碍讨。