??免責聲明:本文章涉及到的代碼僅供學習交流使用,不得用于任何商業(yè)用途,數據來源于互聯(lián)網公開內容,沒有獲取任何私有和非公開權限的信息(個人信息等)。由此引發(fā)的任何法律糾紛與本人無關。禁止將本文技術或者本文所關聯(lián)的Github項目源碼用于任何除學習外的目的。

以影視綜Rank為例,分析如何提取某樂指數網站數據。
分析頁面
打開頁面后有劇集榜、電影榜、綜藝榜三個頁簽,每個面簽的加密邏輯相同,這里使用劇集榜做為樣例。
打開開發(fā)者工具,刷新頁面,可以在Fetch/XHR里看到如圖請求。
[圖片上傳失敗...(image-702f69-1707105865422)]
這個請求的頭部和載荷里分別有uuid和sign兩個參數需要模擬。
[圖片上傳失敗...(image-269624-1707105865422)]
[圖片上傳失敗...(image-688f94-1707105865422)]
查看響應數據,可以發(fā)現(xiàn)返回的是經過加密后的密文。
[圖片上傳失敗...(image-eaa82d-1707105865422)]
劇集榜參數特性:
[圖片上傳失敗...(image-83bcdf-1707105865422)]
電影榜參數特征:
[圖片上傳失敗...(image-f3630-1707105865422)]
綜藝榜參數特征:
[圖片上傳失敗...(image-d12794-1707105865422)]
由此可以看出,“影視綜Rank”里,三個頁簽接口分別對應“無參數”、“movielist”和“varietylist”,并且都設置了相對固定的sign值,理論上可以直接使用,不用進一步分析sign值生成JS。不過出于興趣,還是看看這個是怎么生成的,:P
[圖片上傳失敗...(image-f94bfb-1707105865422)]
解密
Sign生成
通過搜索,初步定位到上圖t.sign是參數生成的地方,在控制臺中確認后,確定目標就是_t.getSign(t).toString()。
[圖片上傳失敗...(image-1e387f-1707105865422)]
其中t就是channel參數。
[圖片上傳失敗...(image-81c56-1707105865422)]
看加密后的結果是長度32的16進制字符串,符合MD5特征,但是用標準MD5算法測試相同的參數,發(fā)現(xiàn)生成的字符并不一樣。所以這里的算法或者輸入參數并不是簡單的處理,需要進一步分析JS實現(xiàn)。
[圖片上傳失敗...(image-d5d9ba-1707105865422)]
通過查看代碼,發(fā)現(xiàn)參數傳入后,進行了字符串拼接,導致實際進行MD5計算的參數并不一樣??梢钥闯鰧嶋H加密的字符串是“channel_movielist_iIndex”這三個字符串的拼接。這里對Vie()方法測試后,是標準的MD5分組加密結果,調用toString()方法后,得到的值和標準MD5算法一致。所以這里用的就是標準MD5算法。
到此,便可以將網頁中的JS代碼復制過來,在本地封裝成JS腳本自己生成sing參數了。JS 代碼如下:
const CryptoJS = require("crypto-js")
function getSign(e) {
delete e.sign;
for (var t = [], n = Object.keys(e).sort(), r = 0; r < n.length; r++) {
var i = n[r]
, a = e[i];
t.push(i),
t.push(a)
} t.push("iIndex");
var s = t.join("_")
, c = CryptoJS.MD5(s);
return c
}
調用的Python代碼如下:
import execjs
def get_sign(channel):
with open("Sign.js", "r", encoding="utf-8") as f:
js_file = f.read()
js = execjs.compile(js_file)
return js.call('getSign', channel)
if __name__ == '__main__':
channel = {
"channel": "movielist"
}
print(get_sign(channel))
輸出結果為5f3cce6a40c09a221b21104cc98436a3,與之前抓包的結果一致。
返回值解密
對于返回值加密的搜索方法:
- 搜索
json.parse(關鍵字 - 搜索
decrypt關鍵字 - 搜索攔截器
- 下斷點到類似
.then的回調方法
這里先使用搜索json.parse,搜索結果很多,需要縮小范圍。
[圖片上傳失敗...(image-364273-1707105865422)]
查看啟動器尋找線索,但并沒有能“望文知義”的地方,只能確定調用的內容都在index.28827ebe.js文件中,那么搜索結果里可以先把其它文件放在一邊,從這個文件入手。
[圖片上傳失敗...(image-db48fc-1707105865422)]
對28827文件里的搜索結果中,可能性大的地方進行斷點確認后,初步確定了下圖兩個位置入口。
可能位置1:經過查看輸入和輸出,發(fā)現(xiàn)并沒有解密活動,所以不是該位置。
[圖片上傳失敗...(image-fce49d-1707105865422)]
可能位置2:查看輸入和輸出,由密文轉換成了明文,所以該方法是目標位置。
[圖片上傳失敗...(image-d112e4-1707105865422)]
這里也可以使用攔截器的方法進行定位。搜索攔截器關鍵字interceptors,找到28827文件相關的位置,查看有關response的條目。
[圖片上傳失敗...(image-249e49-1707105865422)]
然后在該文件中搜索interceptors.response關鍵字,發(fā)現(xiàn)有3位可疑位置,分別打上斷點,刷新頁面后,在每個斷點查看對應的輸入輸出參數,定位到如下位置有解密功能,于是確定這里為解密方法。
[圖片上傳失敗...(image-b7f6b2-1707105865422)]
現(xiàn)在開始分析_t.dataFilter(e.data)代碼,明確解密算法。跳轉到該方法實現(xiàn)位置,發(fā)現(xiàn)就是之前搜索到的“可能位置2“。
根據方法和參數信息,可以初步判斷Zie是對稱加密算法,因為它有iv參數。
而加密算法里有CryptoJS.enc.Utf8同時有parse()和stringify()方法,代入驗證后確認,那么Zie就是CryptoJS.AES。根據這些分析,可以將這部分代碼在本地改寫為:
var CryptoJS = require('crypto-js')
//encrypt_data為響應中的加密數據,lastFetchTime是響應中返回的時間戳
function get_decrypt_data(encrypt_data, lastFetchTime) {
var i = CryptoJS.enc.Utf8.parse(lastFetchTime + "000")
, a = CryptoJS.enc.Utf8.parse(lastFetchTime + "000")
, s = CryptoJS.AES.decrypt(encrypt_data.toString(), i, {
iv: a
})
, l = s.toString(CryptoJS.enc.Utf8);
return JSON.parse(l)
}
通過在Python中調用這個腳本即可完成解碼。
這種方法比較依賴經驗判斷,還有另外一種不需要經驗的簡單粗暴方法:
就是查看這個js文件后,可以發(fā)現(xiàn)dataFilter()方法是獨立賦給_t這個對象的,那么就可以直接調用。首先直接把index_28827ebe.js文件全部復制到本地,去掉不相關或者報錯的代碼后,直接把_t.dataFilter()方法封裝成Python中可調用的方法使用即可。這種方法因為這個js文件代碼有幾萬行,所以會導致電腦突然變慢,酌情使用:P。
[圖片上傳失敗...(image-33d694-1707105865422)]
獲得了解密方法,就可以通過爬蟲自動抓取數據信息了。