爬取網易雲音樂個人動態中的視訊(Ⅱ): 分析並獲取api
回顧和概覽
在 爬取網易雲音樂個人動態中的視訊(Ⅰ) 中簡單的分析了一下需要做什麼, 現在要做的就是獲取網易雲的api, 很遺憾, 網易雲並沒有開放api出來, 但是我們可以對網頁進行除錯, 嘗試從中獲得我們需要的資訊.
參考
為什麼已有兩篇分析我還要自己再寫一篇呢?
原因有二:
- 兩篇分析對我來說還不足夠詳細
- 看完兩篇分析之後, 是我第一次使用斷點除錯js, 所以想詳細記錄下來, 當作筆記
開始
從 爬取網易雲音樂個人動態中的視訊(Ⅰ) 中, 我們發現api(http://music.163.com/weapi/cloudvideo/playurl)中引數有兩個 params 和 encSecKey, 加密肯定是通過js驅動的, 所以, 在網易雲網頁的js上搜索可能會有所發現.
還是在 爬取網易雲音樂個人動態中的視訊(Ⅰ) 中提到的 視訊網頁 上進行分析, 可能第一次開啟這個網頁會出現如下圖的情況
重新整理一遍, 就有我們需要的core.js檔案了:
分析core.js
點選開啟這個core.js, 你會發現js程式碼都沒有縮排, 很難看, 點選一下頁面的"{}", Chrome瀏覽器會對其進行格式化並在新視窗開啟這個格式化後的core.js, 如下圖
搜尋字串"encSecKey", 可以看到有3個匹配, 逐一檢視, 在第二個查詢結果中發現這和 爬取網易雲音樂個人動態中的視訊(Ⅰ) 中提到的引數很像
仔細看看encSecKey所在的這個function
(function() { var c7f = NEJ.P , eq9h = c7f("nej.g") , v7o = c7f("nej.j") , k7d = c7f("nej.u") , Ua2x = c7f("nm.x.ek") , l7e = c7f("nm.x"); if (v7o.bk8c.redefine) return; window.GEnc = true; var brX9O = function(crL1x) { var m7f = []; k7d.bd7W(crL1x, function(crK1x) { m7f.push(Ua2x.emj[crK1x]) }); return m7f.join("") }; var crH1x = v7o.bk8c; v7o.bk8c = function(Y7R, e7d) { var i7b = {} , e7d = NEJ.X({}, e7d) , lL1x = Y7R.indexOf("?"); if (window.GEnc && /(^|\.com)\/api/.test(Y7R) && !(e7d.headers && e7d.headers[eq9h.yH5M] == eq9h.Ht7m) && !e7d.noEnc) { if (lL1x != -1) { i7b = k7d.hf0x(Y7R.substring(lL1x + 1)); Y7R = Y7R.substring(0, lL1x) } if (e7d.query) { i7b = NEJ.X(i7b, k7d.fJ9A(e7d.query) ? k7d.hf0x(e7d.query) : e7d.query) } if (e7d.data) { i7b = NEJ.X(i7b, k7d.fJ9A(e7d.data) ? k7d.hf0x(e7d.data) : e7d.data) } i7b["csrf_token"] = v7o.gI0x("__csrf"); Y7R = Y7R.replace("api", "weapi"); e7d.method = "post"; delete e7d.query; var bRB5G = window.asrsea(JSON.stringify(i7b), brX9O(["流淚", "強"]), brX9O(Ua2x.md), brX9O(["愛心", "女孩", "驚恐", "大笑"])); e7d.data = k7d.cz8r({ params: bRB5G.encText, encSecKey: bRB5G.encSecKey }) } crH1x(Y7R, e7d) } ; v7o.bk8c.redefine = true } )();
一個關鍵的語句是
window.asrsea(JSON.stringify(i7b), brX9O(["流淚", "強"]), brX9O(Ua2x.md), brX9O(["愛心", "女孩", "驚恐", "大笑"]));
這個brX90在這可能是一個拼接字串的作用, 因為這個函式裡面有push和join這兩個關鍵字
window.asrsea的定義不在這裡面, 搜尋"window.asrsea", 如下圖
到現在我們可以確定, window.asrsea就是我們需要的東西, 是大boss!
仔細看看window.asrsea所在的函式
!function() { 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.ecnonasr = e }();
函式d實際是去呼叫兩個加密函式(AES加密函式b和RSA加密函式c)對傳入的引數進行加密, 進行分析, 大致邏輯如下:
- 呼叫函式a生成一個16位隨機數i;
- 呼叫函式b, 加密d(後文可知這是四個引數值唯一可變的), 得到encText
- 呼叫函式b, 加密步驟2得到的encText, 得到新的encText
- 呼叫函式c, 得到encSecKey
這樣就得到api(http://music.163.com/weapi/cloudvideo/playurl)中所需要的params(encText)和encSecKey(encSecKey)了
斷點分析
在上圖中的紅框處的 var h = {} 也即12828行, 點選行號設定一個斷點, 在右側的Watch視窗新建一個觀察, 填入"window.asrsea", 如下圖
然後, 重新整理頁面, 如下圖, 我們就獲得了window.asrsea中所需的3個固定引數(圖中的引數1, 2, 3), 但是圖中引數0到底怎樣的還需要商榷
看回Network, 如下圖, 點選XHR進行一下過濾, 沒發現我們需要的東西
現在我們繼續執行除錯, 可以按F8, 也可以點選下圖紅框處
看回Source和Network
雖然Source中的Watch沒怎麼變, 但是在這裡我們可以確定圖中的引數1, 2, 3都是定值. 從Network可以看出第一次的window.asrsea值是給 http://music.163.com/weapi/cdns 所用的.
重複上述過程, 每次都要去Network看看網路請求, 不要點選太快, 不然又要重來一次除錯, 可以把每次Source都截圖下來, 方便再次檢視.
下圖是最後一次除錯的Network, 到這就要小心了, 不要繼續除錯了
下圖是Source介面, 把滑鼠放在圖中引數0的位置, 以防看漏了引數
繼續除錯, 頁面正常執行, 看回Network, 如下圖
據此, 可以肯定api(http://music.163.com/weapi/cloudvideo/playurl)需要4個引數, 其中後三個引數是定值, 第一個引數由ids, resolution, csrf_token組成
0: {"ids":"[\"5B0AF067CBB42F7789F7B97E13827565\"]","resolution":"720","csrf_token":""}
1: 010001
2: 00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
3: 0CoJUm6Qyw8W8jud