常常寫爬蟲的都知道,有些頁面在登陸以前是被禁止抓取的,好比知乎的話題頁面就要求用戶登陸才能訪問,而 「登陸」 離不開 HTTP 中的 Cookie 技術。python
Cookie 的原理很是簡單,由於 HTTP 是一種無狀態的協議,所以爲了在無狀態的 HTTP 協議之上維護會話(session)狀態,讓服務器知道當前是和哪一個客戶在打交道,Cookie 技術出現了 ,Cookie 至關因而服務端分配給客戶端的一個標識。 git
因爲知乎進行了改版,網上不少其餘的模擬登陸的方式已經不行了,因此這裏從原理開始一步步分析要如何進行模擬登陸。github
要把咱們的爬蟲假裝成瀏覽器登陸,則首先要理解瀏覽器登陸時,是怎麼發送報文的。首先打開知乎登陸頁,打開谷歌瀏覽器開發者工具,選擇Network頁,勾選Presev log,點擊登錄。 咱們很容易看到登陸的請求首等信息:web
模擬登陸最終是要構建請求首和提交參數,即構造 Request Headers和FormData。正則表達式
Request Headers中有幾個參數須要注意:算法
接下來咱們看看登陸時咱們向服務器請求了什麼,由於這是開門的鑰匙,咱們必須先知道鑰匙由哪些部分組成,才能成功的打開大門:json
能夠看到Request Payload中出現最多的是---Webxxx
這一字符串,上面已經說過了,這是一個分割線,咱們能夠直接忽略,因此第一個參數是:client_id=c3cef7c66a1843f8b3a9e6a1e3160e20 ;第二個參數爲grant_type=password....整理了全部的參數以下(知乎的改版可能致使參數改變):api
參數 | 值 | 生成方式 |
---|---|---|
client_id | c3cef7c66a1843f8b3a9e6a1e3160e20 | 固定 |
grant_type | password | 固定 |
timestamp | 1530173433263 | 時間戳 |
signature | 283d218eac893259867422799d6009749b6aff3f | Hash |
username/password | xxxxx/xxxxxx | 固定 |
captcha | Null | 這是驗證碼模塊,有時會出現 |
後面還有一些參數是固定參數,這裏就不一一列出來了。如今總結咱們須要本身生成的一些參數:跨域
利用全局搜索能夠發現該參數的值存在cookie中,所以能夠利用正則表達式直接從cookie中提取;瀏覽器
該參數爲時間戳,可使用 timestamp = str(int(time.time()*1000))生成
signature
首先ctrl+shift+F全局搜索signature,發現其是在main.app.xxx.js的一個js文件中生成的,打開該.js文件,而後複製到編輯器格式化代碼
所以咱們能夠用python來重寫這個hmac加密過程:
def _get_signature(timestamp): """ 經過 Hmac 算法計算返回簽名 實際是幾個固定字符串加時間戳 :param timestamp: 時間戳 :return: 簽名 """ ha = hmac.new(b'd1b964811afb40118a12068ff74a12f4', digestmod=hashlib.sha1) grant_type = self.login_data['grant_type'] client_id = self.login_data['client_id'] source = self.login_data['source'] ha.update(bytes((grant_type + client_id + source + timestamp), 'utf-8')) return ha.hexdigest()
登陸提交的表單中有個captcha
參數,這是登陸的驗證碼參數,有時候登陸時會出現須要驗證碼的狀況。captcha
驗證碼,是經過 GET 請求單獨的 API 接口返回是否須要驗證碼(不管是否須要,都要請求一次),若是是 True 則須要再次 PUT 請求獲取圖片的 base64 編碼。
因此登陸驗證的過程總共分爲三步,首先GET請求看是否須要驗證碼;其次根據GET請求的結果,若是爲True,則須要發送PUT請求來獲取驗證的圖片;最後將驗證的結果經過POST請求發送給服務器。
這是lang=cn
的API須要提交的數據形式,實際上有兩個 API,一個是識別倒立漢字,一個是常見的英文驗證碼,任選其一便可,漢字是經過 plt 點擊座標,而後轉爲 JSON 格式。
最後還有一點要注意,若是有驗證碼,須要將驗證碼的參數先 POST 到驗證碼 API,再隨其餘參數一塊兒 POST 到登陸 API。該部分完整的代碼以下:
def _get_captcha(lang, headers): if lang == 'cn': api = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=cn' else: api = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=en' resp = self.session.get(api, headers=headers) show_captcha = re.search(r'true', resp.text) if show_captcha: put_resp = self.session.put(api, headers=headers) json_data = json.loads(put_resp.text) img_base64 = json_data['img_base64'].replace(r'\n', '') with open('./captcha.jpg', 'wb') as f: f.write(base64.b64decode(img_base64)) img = Image.open('./captcha.jpg') if lang == 'cn': plt.imshow(img) print('點擊全部倒立的漢字,按回車提交') points = plt.ginput(7) capt = json.dumps({'img_size': [200, 44], 'input_points': [[i[0]/2, i[1]/2] for i in points]}) else: img.show() capt = input('請輸入圖片裏的驗證碼:') # 這裏必須先把參數 POST 驗證碼接口 self.session.post(api, data={'input_text': capt}, headers=headers) return capt return ''
最後實現一個檢查登陸狀態的方法,若是訪問登陸頁面出現跳轉,說明已經登陸成功,這時將 Cookies 保存起來(這裏 session.cookies 初始化爲 LWPCookieJar 對象,因此有 save 方法),這樣下次登陸能夠直接讀取 Cookies 文件。
self.session.cookies = cookiejar.LWPCookieJar(filename='./cookies.txt') def check_login(self): resp = self.session.get(self.login_url, allow_redirects=False) if resp.status_code == 302: self.session.cookies.save() return True return False
理解了咱們須要哪些信息,以及信息的提交方式,如今來整理完整的登陸過程:
x-xsrftoken
,更新到headers中;signature
參數,模擬js中的hmac過程;signature
這三個參數更新到Request Payload
中,即程序中的login_data表單;headers
和data
這兩個表單信息POST到Login_API這個接口,能夠查看咱們登陸時的信息,是把提交的信息發送到https://www.zhihu.com/api/v3/... ;error
,則輸出錯誤的結果;不然表示登陸成功,保存cookie文件。參考代碼: