js破解歷程
- 前言
- 技能點
- 界面概況
- 靜態網頁動態網頁
- 頁面決議
- step1: 找引數step2:分析js函式step3:分析引數step4: 校驗step5:轉為python代碼
- 撰寫爬蟲
很多人學習python,不知道從何學起,
很多人學習python,掌握了基本語法過后,不知道在哪里尋找案例上手,
很多已經做案例的人,卻不知道如何去學習更加高深的知識,
那么針對這三類人,我給大家提供一個好的學習平臺,免費領取視頻教程,電子書籍,以及課程的源代碼!??¤
QQ群:623406465
前言
網路爬蟲的大障礙,就是各種加密,這其中包過登錄的驗證碼以及加密,js混淆、js引數加密等等,其實以前也就了解過js加密,但是沒有深入研究,借著這次實踐研究了一下網易云音樂的加密方式,
博主通過網易云音樂評論加密的實體來做個學習程序的分析和分享,
如果有問題或者不懂的地方可以關注我的微信公眾號(bigsai),聯系我,
技能點
- 前端:js知識(比較重要)、谷歌瀏覽器debug、抓包、打斷點除錯能力(必須),以及js各種加密函式(了解).
- python:基礎的請求requests,Crypto.Cipher加密解密模塊,
- 其他:postman(模擬請求使用),良好的思維能力和分析能力,(加密演算法有些亂),還有一點就是js加密轉python的代碼實作,
界面概況
靜態網頁
對于一般的url隨著頁面的變化而變化的頁面,網易云還是有的,你只需要抓取網頁進行分析即可,
動態網頁
但隨著前后端分離的流行,以及資料分離好處明顯,越來越多的資料采用ajax渲染,而網易云的評論即使如此,
在前后端分離付訓,那時很多網站對借口并沒有太大的防護措施,就使得很多網站輕松獲取結果,至今也有很多這樣的借口存在,這種網站爬去就是傻瓜式爬取,
然而隨著前段技術的發展,介面也變的越來越棘手,就拿網易云的評論來說:它的引數就讓人很懵逼,
這一串串數字到底是啥,很多人見到這樣的資料就會選擇放棄,那么讓我為你解開它什么的面紗,
頁面決議
step1: 找引數
你可以看的到,它的引數有兩個,一個是params,一個是encSecKey并且都是經過加密的,我們就要分析它的源頭,F12打開source搜索encSckey.
'在查找這個js內部的encSecKey,發現原來在這里,經過斷點除錯發現這里就是最終引數的結果,
step2:分析js函式
這個js有4w多行,如何能在4w多行js中找到有用的資訊,然后理清楚這里的思路呢?
這就需要你的抽象和逆向思維了,來,咱么開始分析,
var bYc7V = window.asrsea(JSON.stringify(i3x), bkY2x(["流淚", "強"]), bkY2x(VM8E.md), bkY2x(["愛心", "女孩", "驚恐", "大笑"])); e3x.data = k4o.cz4D({ params: bYc7V.encText, encSecKey: bYc7V.encSecKey })
上面這段代碼中就是來源,我們先不管這個JSON.stringify(i3x)這些引數是啥,先搞清楚window.asrsea是什么,在上面不遠處你會發現:
這個就是d函式才是所有資料,方法的根源,d、e、f、g四個引數就是我們剛剛說的不要管的引數,
從這個函式就是分析:encText是經過兩次b()函式,encSecKey是經過c()函式,執行的一個引數,注意其中i引數來源是a(16).網上看看這些函式,
function a(a) { var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = ""; for (d = 0; a > d; d = 1) e = Math.random() * b.length, e = Math.floor(e), c = b.charAt(e); return c } function b(a, b) { var c = CryptoJS.enc.Utf8.parse(b) , d = CryptoJS.enc.Utf8.parse("0102030405060708") , e = CryptoJS.enc.Utf8.parse(a) , f = CryptoJS.AES.encrypt(e, c, { iv: d, mode: CryptoJS.mode.CBC }); return f.toString() } function c(a, b, c) { var d, e; return setMaxDigits(131), d = new RSAKeyPair(b,"",c), e = encryptedString(d, a) } function d(d, e, f, g) { var h = {} , i = a(16); return h.encText = b(d, g), h.encText = b(h.encText, i), h.encSecKey = c(i, e, f), h }
可以發現a(16)就是一個隨機生成的數,所以我們不需要管他,而b目前來看是AES的cbc模式加密,那么這個encText生成的規則我們就很清楚了,兩次AES的cbc加密,其中偏移量為0102030405060708固定不變,兩次的key不同,而函式c就是三個引數進行RSA加密,整個演算法大體流程差不多稍微了解,
到這里先停一下,不要在分析函式了,我們在分析分析資料,
step3:分析引數
再回到var bYc7V = window.asrsea(JSON.stringify(i3x), bkY2x(["流淚", "強"]), bkY2x(VM8E.md), bkY2x(["愛心", "女孩", "驚恐", "大笑"]))這個函式,憑直覺能夠感覺得到有些資料一定跟我們的核心引數無關,最多跟時間戳有關,
查找bky2x源頭,
再找的話其實沒必要,這類函式你找找,可以復制到vscode溯源找到根源,分析,在這里就不繁瑣介紹,直接打斷點分析吧!看看他是怎么執行的,
其實多次抓你會發現后三個引數是固定不變的(非互動型資料),
然而最想要的是第一個引數
你心心年年的引數原來長這個樣,那么和預想差不多,僅僅第一個引數和我們的引數有關,offset就是頁面*20,R_SO_4_ songid就是當前這首歌的id.其實到這個時候,你的i和encSecKey可以一起保存了,因為上面分析說過,這個i是隨機生成,而encSecKey也和我們核心引數無關,但是和i相關,所以要記錄一組,用作ESA加密的引數和post請求的引數,
現在的你是不是很激動,因為真想即將浮出水面,
step4: 校驗
這步驟也是很重要的一環,因為你在它的js中會發現,
網易是否會動手腳呢?下載原始的js進行測驗,發現哈哈,結果一致,那么就不需要更改再仔細查看那段加密演算法的代碼了,
架構圖為
step5:轉為python代碼
需要將AES的cbc模式的代碼用Python克隆,達到加密的效果,測驗一下,發現結果一致nice
撰寫爬蟲
下面就開始撰寫爬蟲,先用postman測驗需要那些引數,
沒問題,撰寫爬蟲,根據你喜歡的哥,把id輸進去,生成你愛的詞云!一首光輝歲月送給大家!
import requests import urllib.parse import base64 from wordcloud import WordCloud import jieba.analyse import matplotlib.pyplot as plt from bs4 import BeautifulSoup from Crypto.Cipher import AES header={'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.90 Safari/537.36', #'Postman-Token':'4cbfd1e6-63bf-4136-a041-e2678695b419', "origin":'https://music.163.com', #'referer':'https://music.163.com/song?id=1372035522', #'accept-encoding':'gzip,deflate,br', 'Accept':'*/*', 'Host':'music.163.com', 'content-lenth':'472', 'Cache-Control':'no-cache', 'content-type': 'application/x-www-form-urlencoded', 'Connection':'keep-alive', #'Cookie':'iuqxldmzr_=32; _ntes_nnid=a6f29f40998c88c693bc910331bd6bea,1558011234325; _ntes_nuid=a6f29f40998c88c693bc910331bd6bea; _ga=GA1.2.2120707788.1559308501; WM_TID=pV2C%2BjTrRwBBAAERUVJojniTwk8%2B8Zta; JSESSIONID-WYYY=nvf%2BggodQRfcT%2BTvBRmANqMrsDeQCxRvqwFsxDr3eJvNNWhGYFhfCXKFkfAfOdbHhpCsMzT39mAeJ7ZamBQZbiwwtnSZD%5CPWRqKxD9t6dGKD3bTVjomjgB39DB07RNIWI32bYKa2H4fg1qQgqI%2FR%2B%2Br%2BZXJvgFg1Vh%2FA2XRj9S4p0EMu%3A1560927288799; WM_NI=DthwcEQf5Ew2NbTIZmSNhSnm%2F8VWsg5RxhkYogvs2luEwZ6m5UhdzbHYPIr654ZBWKV4o22%2BEwb9BvdLS%2BFOmOAEUG%2B8xd8az4CX%2FiAL%2BZkz3syA0onCPkhQwCtL4pkUcjg%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6eed2d650989c9cd1dc4bb6b88eb2c84e979f9aaff773afb6fb83d950bcb19ecce92af0fea7c3b92a88aca898e24f93bafba6f63a8ebe9caad9679192a8b4ed67ede89ab8f26df78eb889ea53adb9ba94b168b79bb9bbb567f78ba885f96a8c87a0aaf13ef7ec96a3d64196eca1d3b12187a9aedac17ea8949dccc545af918fa6d84de9e8b885bb6bbaec8db9ae638394e5bbea72f1adb7a2b365ae9da08ceb5bb59dbcadb77ca98bad8be637e2a3' } def pkcs7padding(text): """ 明文使用PKCS7填充 最終呼叫AES加密方法時,傳入的是一個byte陣列,要求是16的整數倍,因此需要對明文進行處理 :param text: 待加密內容(明文) :return: """ bs = AES.block_size # 16 length = len(text) bytes_length = len(bytes(text, encoding='utf-8')) # tips:utf-8編碼時,英文占1個byte,而中文占3個byte padding_size = length if(bytes_length == length) else bytes_length padding = bs - padding_size % bs # tips:chr(padding)看與其它語言的約定,有的會使用'\0' padding_text = chr(padding) * padding return text + padding_text def encrypt(key, content): """ AES加密 key,iv使用同一個 模式cbc 填充pkcs7 :param key: 密鑰 :param content: 加密內容 :return: """ key_bytes = bytes(key, encoding='utf-8') iv = bytes('0102030405060708', encoding='utf-8') cipher = AES.new(key_bytes, AES.MODE_CBC, iv) # 處理明文 content_padding = pkcs7padding(content) # 加密 encrypt_bytes = cipher.encrypt(bytes(content_padding, encoding='utf-8')) # 重新編碼 result = str(base64.b64encode(encrypt_bytes), encoding='utf-8') return result def getcomment(songid,page): url="https://music.163.com/weapi/v1/resource/comments/R_SO_4_"+songid+"?csrf_token=" print(url) formdata = { "params": "", "encSecKey": "c81160c64a08feb6cfed91c1619d5bffd05dd278b685c94a748689edf035ee0436b66aa7019927ce0fedd26aee9a22cdc6743e58a120f9db0126ebb2e61dae3f7ee21088eb747f829bceed9a5bbb9ee7a2eecf1a358feac431acaab17c95b8491a6a955f7c17a02a3e7886390c2cb3b981f4ccbd5163a566d27ace95db073401", } aes_key = '0CoJUm6Qyw8W8jud'## 不變的 print('aes_key:' + aes_key) # 對英文加密 source_en = '{"rid":"R_SO_4_'+songid+'","offset":"'+str(page*20)+'","total":"false","limit":"20","csrf_token":""}' #offset自己該 print(source_en) encrypt_en = encrypt(aes_key, source_en)#第一次加密 print(encrypt_en) aes_key='3Unu7SzdXGctW1vA' encrypt_en = encrypt(aes_key, str(encrypt_en)) # 第二次加密 print(encrypt_en) formdata['params']=encrypt_en print(formdata['params']) formdata = urllib.parse.urlencode(formdata).encode('utf-8') print(formdata) req = requests.post(url=url, data=https://www.cnblogs.com/sn8888/archive/2020/10/22/formdata, headers=header) return req.json() if __name__ == '__main__': songid='346576' page=0 text='' for page in range(10): comment=getcomment(songid,page) comment=comment['comments'] for va in comment: print (va['content']) text+=va['content'] ags = jieba.analyse.extract_tags(text, topK=50) # jieba分詞關鍵詞提取,40個 print(ags) text = " ".join(ags) backgroud_Image = plt.imread('tt.jpg') # 如果需要個性化詞云 wc = WordCloud(background_color="white", width=1200, height=900, mask=backgroud_Image, # 設定背景圖片 #min_font_size=50, font_path="simhei.ttf", max_font_size=200, # 設定字體最大值 random_state=50, # 設定有多少種隨機生成狀態,即有多少種配色方案 ) # 字體這里有個坑,一定要設這個引數,否則會顯示一堆小方框wc.font_path="simhei.ttf" # 黑體 # wc.font_path="simhei.ttf" my_wordcloud = wc.generate(text) plt.imshow(my_wordcloud) plt.axis("off") plt.show() # 如果展示的話需要一個個點 file = 'image/' + str("aita") + '.png' wc.to_file(file)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/185754.html
標籤:其他
