運行環境
Python:python 3.6.5
IDE:PyCharm 2018.1.2
抓包工具:Charles 4.2.1
操作系統:Windows 7 (32 bit)
瀏覽器:Chrome
抓包
首先先進行登錄抓包,打開Chrome瀏覽器(無痕模式),清空所有的緩存,打開Charles,然后再瀏覽器中輸入www.zhihu.com,打開知乎登錄界面。
點擊登錄,輸入郵箱賬號和密碼,進行登錄。
可能會出現驗證碼,出現的驗證碼分兩種情形,一種是英文的由英文字母和數字組成的四個字符,一種是中文的點擊圖中倒立的文字
這里我用了錯誤的賬號和密碼進行嘗試登錄,主要是為了查看并分析多次請求流程。綜合多次的分析,發現驗證碼的獲取是不一樣的,其他的流程都相同。
一般情況下,可以嘗試輸入錯誤的密碼和驗證碼,多查看幾次登錄的請求流程。
登錄分析
- 找到登錄請求
通常情況下,登錄請求都是POST請求。在這個請求中,找到了username和password,可以確定這個就是登錄請求,分析就從這里開始。
從紅框中看到出現了兩個字段:Authentication和Multipart,在其他的網站登錄沒有遇到過,Multipart是一個多部分編碼數據格式,Authentication是一種認證。
- Authentication
先搜索一下,發現是在一個main.app.xxxx.js的腳本中,打開看一下,都是固定值。而實際上,經過多次請求分析,可以發現Authentication就是固定不變的。
Authentication = 'C3cef7c66a1843f8b3a9e6a1e3160e20'
與此同時,也可以確定其他的固定參數。
- 分析登錄請求的參數
通過觀察,只有signature還未知,client_id就是上面Authentication的值,timestamp是時間戳(13位),captcha是輸入的驗證碼,其余為固定值。
-
解密signature
搜索signature,搜索結果如下圖,位于main.app.xxxx.js文件中。
搜索signature
signature的加密過程
在這里,我們還看到了其他的參數。
認真分析一下,SHA-1、setHMACKey、幾個update、getHMAC,可以肯定是加密了,這里采用了HMAC加密算法。
貼下解密代碼:
signature的解密 captcha
上面我們說過,驗證碼分為英文和中文兩種,請求的URL如下
(英文)
https://www.zhihu.com/api/v3/oauth/captcha?lang=en
(中文)https://www.zhihu.com/api/v3/oauth/captcha?lang=ch
可以看出,驗證碼請求方法有三種方式
- GET(判斷是否需要輸入驗證碼)
-
PUT(返回驗證碼的base64加密數據)
驗證碼請求的PUT方法 -
POST(返回驗證碼的驗證結果)
驗證碼請求的POST方法
對驗證碼這塊,有一個小技巧,我們只使用英文格式的驗證碼,利用GET方法的返回值,判斷是否為True,不為True,重新GET,直到返回值為True。
這樣就避免了,較為復雜的中文驗證碼的識別和操作。
重點說明一下,圖片的驗證請求是在登錄請求之前完成的。
到此,所有的提交參數都搞定了,接下來看一看headers
- 分析headers
authorization的值是固定的,與clien_id的值一樣。
- X-Xsrftoken
搜索一下這個值
來自于
首頁的請求response中設置的cookies值
可以直接從cookies中提取_xsrf值,加入headers即可。
- X-UDID
也來自
首頁的請求response中設置的cookies值
可以直接從cookies中提取d_c0值,加入headers即可。
分析到這里,基本就完成了,可以發送請求了。
發送請求
- multipart/form-data
這種表單也是第一次提交,查資料找到一個requests_toolbelt,可以提交這種類型的數據。(使用MultipartEncoder類)。
boundary 就是一個Multipart數據分界線,大小寫英文字母和數字組成,共16位。
import base64
import hmac
import random
import time
from hashlib import sha1
import requests
import urllib3
from requests_toolbelt import MultipartEncoder
urllib3.disable_warnings()
class ZhihuLogin:
def __init__(self, username, password):
if username.isdigit():
self.username = '+86{}'.format(username)
if '@' in username:
self.username = username
self.password = password
self.s = requests.session()
self.s.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 '
'Safari/537.36'
}
self.s.verify = False
@staticmethod
def signature(timestamp):
key = b'd1b964811afb40118a12068ff74a12f4'
r = hmac.new(key, digestmod=sha1)
r.update(b'password')
r.update(b'c3cef7c66a1843f8b3a9e6a1e3160e20')
r.update(b'com.zhihu.web')
r.update(timestamp.encode())
return r.hexdigest()
@staticmethod
def random_boundary():
temp = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
boundary = '----WebKitFormBoundary{}'.format(''.join(random.choices(temp, k=16)))
return boundary
def cookie_from_home(self):
self.s.get('https://www.zhihu.com')
cookie = self.s.cookies
if cookie.get('_xsrf'):
self.s.headers['X-Xsrftoken'] = cookie.get('_xsrf')
if cookie.get('d_c0'):
self.s.headers['X-UDID'] = cookie.get('d_c0').strip('"').split('|')[0]
def captcha(self):
url = 'https://www.zhihu.com/api/v3/oauth/captcha'
params = {
'lang': 'en',
}
self.s.headers['Authorization'] = 'oauth c3cef7c66a1843f8b3a9e6a1e3160e20'
result = self.s.get(url, params=params).json()
if not result.get('show_captcha'):
self.captcha()
else:
result = self.s.put(url, data=params).json()
with open('captcha.bmp', 'wb') as f:
f.write(base64.b64decode(result.get('img_base64')))
code = input('請輸入驗證碼:')
multipart = MultipartEncoder(
fields={
'input_text': code,
},
boundary=self.random_boundary()
)
self.s.headers['Content-Type'] = multipart.content_type
result = self.s.post(url, data=multipart.read(), params=params).json()
if result.get('success'):
print('驗證碼正確')
return code
def login(self):
code = self.captcha()
timestamp = str(int(time.time() * 1000))
multipart = MultipartEncoder(
fields={
'client_id': 'c3cef7c66a1843f8b3a9e6a1e3160e20',
'grant_type': 'password',
'timestamp': timestamp,
'source': 'com.zhihu.web',
'signature': self.signature(timestamp),
'username': self.username,
'password': self.password,
'captcha': code,
'lang': 'en',
'ref_source': 'homepage',
'utm_source': '',
},
boundary=self.random_boundary()
)
self.s.headers['Content-Type'] = multipart.content_type
url = 'https://www.zhihu.com/api/v3/oauth/sign_in'
result = self.s.post(url, data=multipart.read()).json()
if 'user_id' in result:
print('登陸成功!')
if __name__ == '__main__':
zhihu = ZhihuLogin('知乎郵箱(手機)賬號', '知乎密碼')
zhihu.cookie_from_home()
zhihu.login()
下面貼上兩張 運行結果圖(郵箱登錄和手機號碼登錄)
最后更新于 2018-05-15 18:39:23