知乎是爬蟲的一個經典案例,因為他經常改版,越來越難爬,可能我這個教程寫完他就又改版了。
知乎的難點
1. 登錄,且url跳轉
2. 參數加密
3. 驗證碼
本文將介紹模擬登錄知乎的詳細過程。
抓包 -- 分析登錄過程
使用 fiddler 抓包
使用瀏覽器抓包
1. 獲取登錄url
輸入賬號、密碼等,登錄網站
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×tamp=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