scrapy模擬登錄知乎--抓取熱點話題

折騰了將近兩天,中間數次想要放棄,還好硬着頭皮搞下去了,在此分享出來,但願有同等需求的各位能少走一些彎路。
源碼放在了github上, 歡迎前往查看
如果幫你解決了問題,或者給了你啓發,不要吝嗇給加一星css

工具準備

在開始以前,請確保 scrpay 正確安裝,手頭有一款簡潔而強大的瀏覽器, 如果你有使用 postman 那就更好了。html

scrapy genspider zhihu

使用以上命令生成知乎爬蟲,代碼以下:python

# -*- 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, 切記切記git

# Disable cookies (enabled by default)
COOKIES_ENABLED = True

模擬登錄

過程以下:github

  • 進入登陸頁,獲取 HeaderCookie 信息,
    完善的 Header 信息能儘可能假裝爬蟲, 有效 Cookie 信息能迷惑知乎服務端,使其認爲當前登陸非首次登陸,若無有效 Cookie 會遭遇驗證碼。 在抓取數據以前,請在瀏覽器中登陸過知乎,這樣才使得 Cookie 是有效的。

HeaderCookie 整理以下:shell

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 參數, 這個參數與登錄驗證信息無關,但很明顯是由登錄頁面攜帶的信息。 Google了下 xsrf 的含義, 用於防範 跨站請求僞造
json

  • 整理以上,代碼以下:api

    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)
        ]

設置Bearer Token

通過上述步驟登錄成功了,有點小激動,有沒有! 但苦難到此還遠沒有結束,這個時候嘗試抓取最近熱門話題,直接返回 code:401 ,未受權的訪問。 受權信息未設置,致使了此類錯誤,莫非遺漏了什麼,看來只能在瀏覽器中追蹤請求參數來偵測問題。 在瀏覽器的請求中,包含了Bearer Token, 而我在scrapy中模擬的請求中未包含此信息, 因此我被服務器認定爲未受權的。 經過觀察發現 Bearer Token 的關鍵部分,就是 Cookies 中的 z_c0 包含的信息。
z_c0 包含的信息,是在登錄完成時種下的,因此從登錄完成返回的登錄信息裏,獲取要設置的 Cookie 信息, 而後拼接出 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'])

獲取數據

上述步驟後,數據獲取就水到渠成了,爲了檢測成功與否, 把返回信息寫到文件中,並且只獲取前五十個,代碼以下:

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)

最終獲取的數據以下圖所示:

寫在最後

知乎的數據,只有登陸完成以後,纔可有效的獲取,因此模擬登錄是沒法忽略無論的。 所謂的模擬登錄,只是在scrapy中儘可能的模擬在瀏覽器中的交互過程,使服務端無感抓包過程。 請求中附加有效的 CookiesHeaders 頭信息,可有效的迷惑服務端, 同時在交互的過程當中,獲取後續請求必要信息和認證信息,使得整個流程能不斷先前。

如果你遇到什麼問題,儘可能提出來,歡迎一塊兒來討論解決。
源碼放在了github上, 歡迎前往查看
如果幫你解決了問題,或者給了你啓發,不要吝嗇給加一星

相關文章
相關標籤/搜索