步驟:
- 第一步:抓包查看登陸接口
- 第二步:分析js文件,提取加密請求參數的js腳本
- 第三步:分析js文件,構造需要加密的字符串
- 第四步:編寫代碼模擬請求登陸
第一步:抓包查看post登陸接口
登陸接口
參數(可以看到post的數據是經過加密的)
第二步:分析js文件,提取加密請求參數的js腳本
通過全局搜索sign_in字段,找到對應post請求
我們看到feachOpiton中的zsEncrypt參數為ture,zsEncrypt為ture應該是代表body需要加密,斷點后通過控制臺執行Object(d.decamelizeKeys)(e),可以獲取到body參數
通過搜索zsEncrypt字段,可以找到請求時對body進行加密的函數
斷點后發現o.default 為實際的加密函數,a為需要加密的字符串
控制臺調用o.default(a)可以獲取到加密后字符
進入o.defaul函數,o.defaul實際為加密模塊中對外暴露的Q加密函數
接下來需要提取加密模塊,然后使用python執行提取出來的js腳本
將Q函數所在的函數復制到本地zhihu.js文件,將最外層函數刪掉,只保存里面的代碼
同時將export模塊相關的代碼刪掉
在zhihu.js最后調用Q函數加密需要加密的a字符串
創建zhihu.html文件,引入zhihu.js文件
使用瀏覽器打開文件,控制臺console中可以看到加密過的字符串
此時我們已經提取出了加密所需要的js文件,可以通過瀏覽器執行,但時通過python執行會發現無法執行,原因是瀏覽器環境和python執行js的node環境不同,使用vscode斷點試調后發現node環境先缺少window對象、navigator對象(過程比較繁瑣,有興趣的可以自己去試調研究下),并且window對象下需要有encodeURIComponent函數,navigator對象下需要有userAgent屬性。同時也需要用到atob函數,通過全局搜索atob可以找到zap.js文件中的atob函數,atob函數實際上是將base64編碼的字符串轉換二進制編碼的字符串。缺少的這些我們可以自己定義出來,添加到zhihu.js文件開頭。
此時提取出來的zhihu.js文件已經可以正常運行了
我們自己定義一個encrypt函數供python調用,這樣就完成了加密js的提取
使用python調用執行js腳本
import execjs
def encrypt(string):
with open('./zhihu.js', 'r', encoding='utf-8') as f:
js = f.read()
result = execjs.compile(js).call('encrypt', string)
return result
print(encrypt('123456'))
第三步:分析js文件,構造需要加密的字符串
需要加密的字符串:
client_id=c3cef7c66a1843f8b3a9e6a1e3160e20&
grant_type=password&
timestamp=1551062570616&
source=com.zhihu.web&
signature=e3ab73425750a4dbcf9ab357f6030fc281ceeb22&
username=819221111@qq.com&
password=123456&
captcha=&
lang=en&
ref_source=homepage&
utm_source=
這些參數中會變的參數的總共有四個,分別是timestamp,signature,username,password,這些需要我們自己傳入,capthca參數是有驗證碼的時候需要傳,我還沒遇到過需要輸入驗證碼的,這里我們不作考慮。真正需要我們構造的只有signature參數,下面介紹如何構造
全局搜索signature,在main.app.xxx.js 中可以找到singnature的構造方法,是通過hmac加密
clientId,grantType,source,timestamp四個參數獲得的
使用python模擬加密的js,代碼如下
import time
import hmac
from hashlib import sha1
def get_signature():
h = hmac.new(key='d1b964811afb40118a12068ff74a12f4'.encode('utf-8'), digestmod=sha1)
grant_type = 'password'
client_id = 'c3cef7c66a1843f8b3a9e6a1e3160e20'
source = 'com.zhihu.web'
now = str(int(time.time()*1000))
h.update((grant_type + client_id + source + now).encode('utf-8'))
return h.hexdigest()
print(get_signature())
第四步:編寫代碼模擬請求登陸
我們的準備工作已經完成了,下面開始編寫代碼模擬請求
- 第一步:請求請求login_url,udid_url,captcha_url加載所需要的cookie
- 第二步:構造需要加密的字符串
- 第三步:加密字符串
- 第四步:使用加密后的字符串請求post登陸接口
完整代碼如下:
import requests
import re
import execjs
import time
import hmac
from hashlib import sha1
class Zhihu(object):
def __init__(self, username, password):
self.username = username
self.password = password
self.session = requests.session()
# 此處請求頭只需要這三個
self.headers = {
'content-type': 'application/x-www-form-urlencoded',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36',
'x-zse-83': '3_1.1'
}
def login(self):
# 請求login_url,udid_url,captcha_url加載所需要的cookie
login_url = 'https://www.zhihu.com/signup?next=/'
resp = self.session.get(login_url, headers=self.headers)
print("請求{},響應狀態碼:{}".format(login_url,resp.status_code))
# print(self.session.cookies.get_dict())
# self.save_file('login',resp.text)
udid_url = 'https://www.zhihu.com/udid'
resp = self.session.post(udid_url, headers=self.headers)
print("請求{},響應狀態碼:{}".format(udid_url,resp.status_code))
# print(self.session.cookies.get_dict())
captcha_url = 'https://www.zhihu.com/api/v3/oauth/captcha?lang=en'
resp = self.session.get(captcha_url, headers=self.headers)
print("請求{},響應狀態碼:{}".format(captcha_url,resp.status_code))
# print(self.session.cookies.get_dict())
# print(resp.text)
# self.save_file('captcha',resp.text)
# 校驗是否需要驗證嗎,需要則直接退出,還沒遇到過需要驗證碼的
if re.search('true',resp.text):
print('需要驗證碼')
exit()
# 獲取signature參數
self.time_str = str(int(time.time()*1000))
signature = self.get_signature()
# print(signature)
# 拼接需要加密的字符串
string = "client_id=c3cef7c66a1843f8b3a9e6a1e3160e20&grant_type=password×tamp={}&source=com.zhihu.web&signature={}&username={}&password={}&captcha=&lang=en&ref_source=homepage&utm_source=".format(self.time_str,signature,self.username,self.password)
# print(string)
# 加密字符串
encrypt_string = self.encrypt(string)
# print(encrypt_string)
# post請求登陸接口
post_url = "https://www.zhihu.com/api/v3/oauth/sign_in"
resp = self.session.post(post_url, data=encrypt_string, headers=self.headers)
print("請求{},響應狀態碼:{}".format(post_url,resp.status_code))
# print(self.session.cookies.get_dict())
# print(resp.text)
# self.save_file('post',resp.text)
# 校驗是否登陸成功
if re.search('user_id',resp.text):
print('登陸成功')
else:
print("登陸失敗")
exit()
def test(self):
# 請求個人信息接口查看個人信息
me_url = 'https://www.zhihu.com/api/v4/me'
data = {
'include': 'ad_type;available_message_types,default_notifications_count,follow_notifications_count,vote_thank_notifications_count,messages_count;draft_count;following_question_count;account_status,is_bind_phone,is_force_renamed,email,renamed_fullname;ad_type'
}
resp = self.session.get(me_url, data=data, headers=self.headers)
print("請求{},響應狀態碼:{}".format(me_url,resp.status_code))
print(resp.text)
# self.save_file('me',resp.text)
def encrypt(self, string):
with open('./zhihu.js', 'r', encoding='utf-8') as f:
js = f.read()
result = execjs.compile(js).call('encrypt', string)
return result
def get_signature(self):
h = hmac.new(key='d1b964811afb40118a12068ff74a12f4'.encode('utf-8'), digestmod=sha1)
grant_type = 'password'
client_id = 'c3cef7c66a1843f8b3a9e6a1e3160e20'
source = 'com.zhihu.web'
now = self.time_str
h.update((grant_type + client_id + source + now).encode('utf-8'))
return h.hexdigest()
def save_file(self, name, html):
with open('{}.html'.format(name),'w',encoding='utf-8') as f:
f.write(html)
if __name__ == "__main__":
account = Zhihu('賬號','密碼')
account.login()
account.test()
登陸成功
代碼
鏈接:https://pan.baidu.com/s/1aqzzkacgQM0n2ewV6r7csg
提取碼:inv2
參考文章
https://zhuanlan.zhihu.com/p/34073256
https://mp.weixin.qq.com/s/XplpQ6QUophvgfyMszk0Hg