爬蟲實戰(一)-新版知乎

知乎是爬蟲的一個經典案例,由於他常常改版,愈來愈難爬,可能我這個教程寫完他就又改版了。git

 

知乎的難點github

1. 登陸,且url跳轉web

2. 參數加密json

3. 驗證碼redux

本文將介紹模擬登陸知乎的詳細過程。api

 

抓包 -- 分析登陸過程

使用 fiddler 抓包瀏覽器

使用瀏覽器抓包

1. 獲取登陸urlsession

輸入帳號、密碼等,登陸網站app

post 方式訪問url,頁面跳轉,箭頭所指是真實的 登陸url  編輯器

 

2. 獲取登陸參數

能夠看到 form data 加密了

 

處理方法

須要解決兩個問題:提交了哪些參數;如何加密

1. 首先須要進入 source 面板,找尋相關 js 文件與加密函數;

2. 搜索與加密相關的英文,搜索方法見個人博客《瀏覽器抓包》,只要相關的函數名沒有加密,就能搜到,這裏搜索 encrypt;【encrypt:加密】

3. 在瀏覽器中格式化 js 代碼,定位到加密函數,獲取行號;【每每能夠搜到不少個encrypt,瀏覽器中只匹配到第一個,因此要拷到編輯器中,搜索定位】

4. 在對應行號設置斷點;【注意行號可能不徹底相同,在上下幾行中找找對應函數】

5. 從新登陸,進行調試,抓取登陸參數;

加密函數

var b = function(e) {
        return __g._encrypt(encodeURIComponent(e))
    };

e

"client_id=c3cef7c66a1843f8b3a9e6a1e3160e20&grant_type=password&timestamp=1559629752508&source=com.zhihu.web&signature=15317e3484b64449697b285a69a09af8ff23a1af&username=yanshuangwu258%40sina.com&password=6712007&captcha=&lang=en&ref_source=homepage&utm_source="

加密函數 b 傳入參數e,先進行 encodeURIComponent,根據經驗應該是 編碼成 key-value 形式,而後進行加密

 

先把參數意義搞清楚

client_id=c3cef7c66a1843f8b3a9e6a1e3160e2        客戶端id
grant_type=password                    受權類型
timestamp=1559629752508                    時間戳
source=com.zhihu.web                    源地址
signature=15317e3484b64449697b285a69a09af8ff23a1af    簽名
username=yanshuangwu258%40sina.com            用戶名
password=6712007                    密碼
captcha=                        驗證碼
lang=en                            驗證碼類型
ref_source=homepage                
utm_source=

 

多試幾回,觀察參數值是否固定;

經對比,不固定的是 時間戳、簽名、驗證碼;

時間戳就是時間,算是已知的,剩下就要獲得簽名和驗證碼了

 

3. 獲取簽名

從上得知,簽名也是通過加密的

破解方法相似第2步;在 source 中搜索 signature;

定位行數,設置斷點,調試;

 

signature 在這個函數中生成

function(e, t, n) {
    "use strict";
    var r = n(745)
      , o = n.n(r)
      , i = n(183)
      , a = n.n(i);
    Object.assign;
    a()("zhihu-redux-middlewares:oauth");
    var c = "c3cef7c66a1843f8b3a9e6a1e3160e20";
    var u = Object.assign || function(e) {
        for (var t = 1; t < arguments.length; t++) {
            var n = arguments[t];
            for (var r in n)
                Object.prototype.hasOwnProperty.call(n, r) && (e[r] = n[r])
        }
        return e
    }
    ;
    t.a = function(e, t) {
        var n = Date.now()
          , r = new o.a("SHA-1","TEXT");
        return r.setHMACKey("d1b964811afb40118a12068ff74a12f4", "TEXT"),
        r.update(e),
        r.update(c),
        r.update("com.zhihu.web"),
        r.update(String(n)),
        u({
            clientId: c,
            grantType: e,
            timestamp: n,
            source: "com.zhihu.web",
            signature: r.getHMAC("HEX")      #######
        }, t)
    }

該函數傳入 e、t和一些全局變量,e是字符串‘password’,t 見截圖,n是時間戳,c 見代碼,

這個函數顯示了 signature 的加密過程;【此處須要學習常規加密方法】

此處經過 祕鑰d1b964811afb40118a12068ff74a12f4 和 SHA-1密碼散列函數,進行加密,r.update 又添加了 e、c、‘com.zhihu.web’、string(n),

由截圖和代碼可知,e表明‘password’, c爲"c3cef7c66a1843f8b3a9e6a1e3160e20",n爲時間戳,由此可算出 signature

 

4. 獲取登陸驗證碼

知乎驗證碼的特色

1. 登陸知乎不是每次都須要驗證碼

2. 知乎有兩種驗證碼,一種是 「點擊倒立的文字」,一種是 「英文字母」

 

驗證碼分析 - 操做過程

1. 訪問知乎登陸頁面,F12,而後刷新

能夠看到,驗證碼url返回 false,即無需驗證碼

此時 的 request url 以下圖

 

2. 屢次刷新登陸頁面,觀察 驗證碼 url 的 response,直至爲 true

返回 true ,表明須要驗證碼

此時的 request url 以下圖

能夠看到 和不需驗證碼的url 相同,method 都是 get

 

咱們發現緊接着又有一個 驗證碼url,是什麼呢?

這應該就是驗證碼圖片, base64 編碼的圖片。

base64 編碼的圖片。可先存入本地,然後手動輸入

 

看下headers 

咱們發現 method 變成了 put,request url 仍是同樣

 

也就是說,若是訪問驗證碼url返回true, 會自動再次請求這個url,請求方式爲 put, 返回 base64編碼的圖片

 

3. 輸入帳號、密碼,彈出驗證碼,輸入驗證碼,點擊登陸

首先是 post 了驗證碼數據,一樣的url

 

post 參數以下圖

key 是 input_text, value 爲  圖片大小和倒立文字的位置,這是倒立文字驗證碼

 

英文字母以下圖

key 也是 input_text,value 爲英文字母

 

倒立文字 和 英文字母 的url 不一樣, 文字 cn,字母 en

 

也就是說,獲得 base64 編碼的圖片後,要給該 url post 驗證碼,而後才能登陸

 

至此,咱們獲得驗證碼,並 post,獲取登陸的全部參數。

 

也能夠嘗試經過 搜索 登陸url 的 js 關鍵字,獲取登陸參數。

 

 代碼實現登錄

import json
import requests
import time
from hashlib import sha1
from time import sleep
import hmac
import base64
from PIL import Image


class Zhihu(object):

    def __init__(self):
        self.session=requests.session()
        self.headers={
            # 'authority':'www.zhihu.com',
            'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0',
        }
        self.session.headers.update(self.headers)
        self.picture=None
        self.signature=None
        self.picture_url=None

    def getcapture(self):
        # 獲取驗證碼方法,有時候不用獲取驗證碼就能夠直接登陸
        # lang=en是英文字母 驗證碼
        message=self.session.get(url='https://www.zhihu.com/api/v3/oauth/captcha?lang=en').json()       # get 檢測是否須要驗證碼
        print(message)
        if message['show_captcha'] == False:
            self.picture=''
        else:
            self.picture_url = self.session.put(url='https://www.zhihu.com/api/v3/oauth/captcha?lang=en').json()    # put 獲取驗證碼
            # 採用base64格式將驗證碼經過圖片格式顯示出來
            with open('captcha.jpg','wb') as f:
                f.write(base64.b64decode(self.picture_url['img_base64']))
            image=Image.open('captcha.jpg')
            image.show()
            self.picture=input('請輸入驗證碼')
            sleep(2)
            message1=self.session.post(url='https://www.zhihu.com/api/v3/oauth/captcha?lang=en',data={'input_text':self.picture}).json()    # post 驗證碼
            print(message1)

    def get_signature(self):
        # 知乎登錄的主要問題在於找到signature了這是重點。
        a=hmac.new('d1b964811afb40118a12068ff74a12f4'.encode('utf-8'),digestmod=sha1)
        a.update('password'.encode('utf-8'))
        a.update(b'c3cef7c66a1843f8b3a9e6a1e3160e20')
        a.update(b'com.zhihu.web')
        a.update(str(int(time.time()*1000)).encode())
        self.signature=a.hexdigest()

    def Login_phone(self):
        # 登陸
        data={
            'client_id':'c3cef7c66a1843f8b3a9e6a1e3160e20',#'c3cef7c66a1843f8b3a9e6a1e3160e20',
            'grant_type':'password',
            'timestamp':str(int(time.time()*1000)),
            'source':'com.zhihu.web',
            'signature':self.signature,
            'username':'xxxxxx@sina.com',
            'password':'xxxxxxx',
            'captcha':self.picture,
            'lang':'en',
            # 'ref_source':'homepage',
            # 'utm_source':''
        }

        headers = {
                    # 'scheme':'https',
                    # 'accept':'*/*',
                    # 'accept-encoding':'gzip, deflate, br',
                    # 'accept-language':'zh-CN,zh;q=0.8',
                    # 'cache-control':'no-cache',
                    # 'content-length':'412',
                    # 'origin':'https://www.zhihu.com',
                   'content-type':'application/x-www-form-urlencoded',
                   # 'referer':'https://www.zhihu.com/signin?next=%2F',
                   'x-zse-83':'3_2.0',
                   }
        message=self.session.post(url='https://www.zhihu.com/api/v3/oauth/sign_in', headers=headers, data=data)
        message.encoding='utf-8'
        print(message.text)
        print(json.loads(message.text)['error']['message'])

    def target_url(self,url):
        text=self.session.get(url)
        return text.text


if __name__ == "__main__":
    zhihu=Zhihu()
    zhihu.getcapture()      # 驗證碼
    zhihu.get_signature()   # signature
    zhihu.Login_phone()     # 登陸
    # print(zhihu.target_url('https://www.zhihu.com/'))

知乎模擬登錄仍是很複雜的

 

 

 

參考資料:

https://blog.csdn.net/jiyukun1/article/details/82256222

https://blog.csdn.net/y15518325965/article/details/79406247

https://blog.csdn.net/sergiojune/article/details/87873787

https://blog.csdn.net/lvanboy/article/details/88044576

https://www.chainnews.com/articles/068650003844.htm  代碼 錯誤 解析

https://github.com/zkqiang/Zhihu-Login

相關文章
相關標籤/搜索