因爲網易雲音樂大部分請求作了混淆加密處理,所以直接用requests請求是行不通。本文以獲取歌曲所有評論爲例,經過分析網易雲的加密過程,來反編譯構造加密參數。下面主要介紹下用Python實現加密參數的構造,並獲取歌曲的所有評論。python
1、瞭解加密過程
1.1 經過觀察網絡請求
發現以下接口包含所要的數據。包含該頁最新評論,以及全部熱評linux
再看它評論的參數,着實讓人有點懵逼git
不少人看見這一串字符,可能簡單看下先後接口有沒數據就放棄了。好了,不賣關子。這章分析下整個參數的加密過程,實現最終模擬請求拿到數據。算法
1.2 找請求參數
能夠看的到,它的參數有兩個,一個是params,一個是encSecKey而且都是通過加密的,咱們就要分析它在js中的位置(F12打開source搜索encSecKey)網絡
進入js內部,通過斷點調試發現這裏就是生成最終參數的地方app
1.3 觀察參數變化
模擬不一樣分頁請求,觀察變化。效果以下圖:dom
對比參數變化,得出參數構成:函數
- csrf_token :始終空字符串
- cursor :第一頁默認-1;請求下一頁時該參數爲上一頁最後一條評論時間戳(防止數據重複)
- offset :移動評論數
- orderType :默認1
- pageNo :頁碼
- pageSize :默認20
- rid : R_SO_4_ + 歌曲ID
- threadId : R_SO_4_ + 歌曲ID
自此,咱們已經知道整個參數的構成,接下來就看下網易雲是如何進行參數加密的工具
2、分析加密函數
經過斷點已經知道,下面這段js代碼爲咱們分析的重點學習
var bZj0x = window.asrsea(JSON.stringify(i2x), bkk0x(["流淚", "強"]), bkk0x(YS7L.md), bkk0x(["愛心", "女孩", "驚恐", "大笑"])); e2x.data = j2x.cr3x({ params: bZj0x.encText, encSecKey: bZj0x.encSecKey })
只要把bZj0x解出來就ok了,這裏主要分析幾個函數
2.1 JSON.stringify(i2x)
斷點調製找到i2x返回內容
就是上文分析的整個字典參數
csrf_token: "" cursor: "1610076350235" offset: "40" orderType: "1" pageNo: "2" pageSize: "20" rid: "R_SO_4_1807537867" threadId: "R_SO_4_1807537867"
2.2 bkk0x函數
bkk0x(["流淚", "強"]), bkk0x(YS7L.md), bkk0x(["愛心", "女孩", "驚恐", "大笑"]),都用到了同一個函數,這裏就看下bkk0x函數內部實現是怎樣的
var bkk0x = function(cJj8b) { var m2x = []; j2x.bf2x(cJj8b, function(cJi8a) { m2x.push(YS7L.emj[cJi8a]) }); return m2x.join("") }
等同於python寫法
def get_bq_n1x(keys): m0x = [] for key in keys: m0x.append(emj[key]) return ''.join(m0x)
YS7L.emj是一個固定的字典
YS7L.emj = { "色": "00e0b", "流感": "509f6", "這邊": "259df", "弱": "8642d", "嘴脣": "bc356", "親": "62901", "開心": "477df", "呲牙": "22677", "憨笑": "ec152", "貓": "b5ff6", "皺眉": "8ace6", "幽靈": "15bb7", "蛋糕": "b7251", "發怒": "52b3a", "大哭": "b17a8", "兔子": "76aea", "星星": "8a5aa", "鍾情": "76d2e", "牽手": "41762", "公雞": "9ec4e", "愛意": "e341f", "禁止": "56135", "狗": "fccf6", "親親": "95280", "叉": "104e0", "禮物": "312ec", "暈": "bda92", "呆": "557c9", "生病": "38701", "鑽石": "14af6", "拜": "c9d05", "怒": "c4f7f", "示愛": "0c368", "汗": "5b7a4", "小雞": "6bee2", "痛苦": "55932", "撇嘴": "575cc", "惶恐": "e10b4", "口罩": "24d81", "吐舌": "3cfe4", "心碎": "875d3", "生氣": "e8204", "可愛": "7b97d", "鬼臉": "def52", "跳舞": "741d5", "男孩": "46b8e", "奸笑": "289dc", "豬": "6935b", "圈": "3ece0", "便便": "462db", "外星": "0a22b", "聖誕": "8e7", "流淚": "01000", "強": "1", "愛心": "0CoJU", "女孩": "m6Qyw", "驚恐": "8W8ju", "大笑": "d" }
YS7L.md是一個固定的數據
YS7L.md = ["色", "流感", "這邊", "弱", "嘴脣", "親", "開心", "呲牙", "憨笑", "貓", "皺眉", "幽靈", "蛋糕", "發怒", "大哭", "兔子", "星星", "鍾情", "牽手", "公雞", "愛意", "禁止", "狗", "親親", "叉", "禮物", "暈", "呆", "生病", "鑽石", "拜", "怒", "示愛", "汗", "小雞", "痛苦", "撇嘴", "惶恐", "口罩", "吐舌", "心碎", "生氣", "可愛", "鬼臉", "跳舞", "男孩", "奸笑", "豬", "圈", "便便", "外星", "聖誕"]
因此整個window.asrsea的參數都是能夠獲得的了,下面看下 window.asrsea()查看這個函數執行了什麼操做
2.3 window.asrsea()
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 } function e(a, b, d, e) { var f = {}; return f.encText = c(a + e, b, d), f } window.asrsea = d
能夠看到window.asrsea = d,因此咱們要執行的就是d這個函數,主要執行3個操做
- a(16),生成16位隨機數
- 進行兩次AES加密獲得h.encText
- 經過位移等一系列運算生成h.encSecKey
3、Python實現相同的加密算法
""" 網易雲請求參數反編譯工具 :主要斷點觀察js,改成python實現 """ emj = { "色": "00e0b", "流感": "509f6", "這邊": "259df", "弱": "8642d", "嘴脣": "bc356", "親": "62901", "開心": "477df", "呲牙": "22677", "憨笑": "ec152", "貓": "b5ff6", "皺眉": "8ace6", "幽靈": "15bb7", "蛋糕": "b7251", "發怒": "52b3a", "大哭": "b17a8", "兔子": "76aea", "星星": "8a5aa", "鍾情": "76d2e", "牽手": "41762", "公雞": "9ec4e", "愛意": "e341f", "禁止": "56135", "狗": "fccf6", "親親": "95280", "叉": "104e0", "禮物": "312ec", "暈": "bda92", "呆": "557c9", "生病": "38701", "鑽石": "14af6", "拜": "c9d05", "怒": "c4f7f", "示愛": "0c368", "汗": "5b7a4", "小雞": "6bee2", "痛苦": "55932", "撇嘴": "575cc", "惶恐": "e10b4", "口罩": "24d81", "吐舌": "3cfe4", "心碎": "875d3", "生氣": "e8204", "可愛": "7b97d", "鬼臉": "def52", "跳舞": "741d5", "男孩": "46b8e", "奸笑": "289dc", "豬": "6935b", "圈": "3ece0", "便便": "462db", "外星": "0a22b", "聖誕": "8e7", "流淚": "01000", "強": "1", "愛心": "0CoJU", "女孩": "m6Qyw", "驚恐": "8W8ju", "大笑": "d" } md = ["色", "流感", "這邊", "弱", "嘴脣", "親", "開心", "呲牙", "憨笑", "貓", "皺眉", "幽靈", "蛋糕", "發怒", "大哭", "兔子", "星星", "鍾情", "牽手", "公雞", "愛意", "禁止", "狗", "親親", "叉", "禮物", "暈", "呆", "生病", "鑽石", "拜", "怒", "示愛", "汗", "小雞", "痛苦", "撇嘴", "惶恐", "口罩", "吐舌", "心碎", "生氣", "可愛", "鬼臉", "跳舞", "男孩", "奸笑", "豬", "圈", "便便", "外星", "聖誕"] def get_bq_n1x(keys): m0x = [] for key in keys: m0x.append(emj[key]) return ''.join(m0x) def __get_random_str(): """ Returns:16位的隨機字符串 """ str_set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" random_str = "" for i in range(16): index = random.randint(0, len(str_set) - 1) random_str += str_set[index] return random_str arg2 = get_bq_n1x(["流淚", "強"]) arg3 = get_bq_n1x(md) arg4 = get_bq_n1x(["愛心", "女孩", "驚恐", "大笑"]) random_str = __get_random_str() def __aes_encrypt(text, key): """ 獲取到ASW加密後的數據 Args: text: 首先CBC加密方法,text必須位16位數據 key: 加密的key Returns:加密後的字符串 """ # 加密或者解密的初始向量(16位) iv = "0102030405060708" # 不是16的倍數則填充 pad = 16 - len(text) % 16 if isinstance(text, str): text = text + pad * chr(pad) else: text = text.deocde("utf-8") + pad * chr(pad) aes = AES.new(key=bytes(key, encoding="utf-8"), mode=2, iv=bytes(iv, encoding="utf-8")) res = aes.encrypt(bytes(text, encoding="utf-8")) res = base64.b64encode(res).decode("utf-8") return res def __get_enc_text(arg1): """ 對稱加密後的參數 Args: arg1:加密參數 Returns: """ enc_text = __aes_encrypt(arg1, arg4) enc_text = __aes_encrypt(enc_text, random_str) return enc_text def __get_enc_sec_key(): """ 對稱加密密鑰 經過查看js代碼,獲取encSecKey """ # 隨機字符串逆序排列 text = random_str[::-1] rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(arg2, 16) % int(arg3, 16) return format(rs, 'x').zfill(256) def linux_encrypt(text): # print(text) return text def get_form_data(text, method=''): """ 反編譯生成 請求的form-data參數 Args: text: 跟蹤js,本身組裝參數 method: 方法 Returns:form-data參數 """ """ """ if method == 'linux': return linux_encrypt(text) text = str(text) return {"params": __get_enc_text(text), "encSecKey": __get_enc_sec_key()}
4、模擬請求歌曲評論
只要加密過程知道了,其實不少接口均可以模擬請求,此文僅供學習。
最終執行效果以下圖: