模擬知乎登陸——Python3

常常寫爬蟲的都知道,有些頁面在登陸以前是被禁止抓取的,好比知乎的話題頁面就要求用戶登陸才能訪問,而 「登陸」 離不開 HTTP 中的 Cookie 技術。python

登陸原理

Cookie 的原理很是簡單,由於 HTTP 是一種無狀態的協議,所以爲了在無狀態的 HTTP 協議之上維護會話(session)狀態,讓服務器知道當前是和哪一個客戶在打交道,Cookie 技術出現了 ,Cookie 至關因而服務端分配給客戶端的一個標識。 git

cookie

  1. 瀏覽器第一次發起 HTTP 請求時,沒有攜帶任何 Cookie 信息
  2. 服務器把 HTTP 響應,同時還有一個 Cookie 信息,一塊兒返回給瀏覽器
  3. 瀏覽器第二次請求就把服務器返回的 Cookie 信息一塊兒發送給服務器
  4. 服務器收到HTTP請求,發現請求頭中有Cookie字段, 便知道以前就和這個用戶打過交道了。

分析Post數據

因爲知乎進行了改版,網上不少其餘的模擬登陸的方式已經不行了,因此這裏從原理開始一步步分析要如何進行模擬登陸。github

要把咱們的爬蟲假裝成瀏覽器登陸,則首先要理解瀏覽器登陸時,是怎麼發送報文的。首先打開知乎登陸頁,打開谷歌瀏覽器開發者工具,選擇Network頁,勾選Presev log,點擊登錄。 咱們很容易看到登陸的請求首等信息:web

模擬登陸最終是要構建請求首和提交參數,即構造 Request Headers和FormData。正則表達式


構建Headers

Request Headers中有幾個參數須要注意:算法

  1. Content-Type (後面的boundary指定了表單提交的分割線)
  2. cookie (登錄前cookie就不爲空,說明以前確定有set-cookie的操做 )
  3. X-Xsrftoken (則是防止Xsrf跨域的Token認證,能夠在Response Set-Cookie中找到 )

接下來咱們看看登陸時咱們向服務器請求了什麼,由於這是開門的鑰匙,咱們必須先知道鑰匙由哪些部分組成,才能成功的打開大門: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 這是驗證碼模塊,有時會出現

後面還有一些參數是固定參數,這裏就不一一列出來了。如今總結咱們須要本身生成的一些參數:跨域

  1. X-Xsrftoken

    利用全局搜索能夠發現該參數的值存在cookie中,所以能夠利用正則表達式直接從cookie中提取;瀏覽器

  2. timestamp

    該參數爲時間戳,可使用 timestamp = str(int(time.time()*1000))生成

  3. 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 ''

保存Cookie

最後實現一個檢查登陸狀態的方法,若是訪問登陸頁面出現跳轉,說明已經登陸成功,這時將 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

總結

理解了咱們須要哪些信息,以及信息的提交方式,如今來整理完整的登陸過程:

  1. 構建HEADERS請求頭和FORM_DATA表單的基本信息,通常爲固定不變的信息;
  2. 從cookies中獲取x-xsrftoken,更新到headers中;
  3. 檢查用戶名和密碼是否在data表單中,若是沒有,則須要更新用戶名和密碼到表單中;
  4. 獲取時間戳,並利用時間戳來計算signature參數,模擬js中的hmac過程;
  5. 檢查驗證碼,若是須要驗證碼,則先將驗證碼的結果POST到驗證API端口,手動輸入驗證碼的結果;
  6. 將時間戳、驗證碼以及signature這三個參數更新到Request Payload中,即程序中的login_data表單;
  7. headersdata這兩個表單信息POST到Login_API這個接口,能夠查看咱們登陸時的信息,是把提交的信息發送到https://www.zhihu.com/api/v3/...
  8. 檢查返回的response結果,若是有error,則輸出錯誤的結果;不然表示登陸成功,保存cookie文件。

參考代碼:

模擬知乎登陸

相關文章
相關標籤/搜索