1. 程式人生 > >爬取網易雲音樂個人動態中的視訊(Ⅱ): 分析並獲取api

爬取網易雲音樂個人動態中的視訊(Ⅱ): 分析並獲取api

回顧和概覽

在 爬取網易雲音樂個人動態中的視訊(Ⅰ) 中簡單的分析了一下需要做什麼, 現在要做的就是獲取網易雲的api, 很遺憾, 網易雲並沒有開放api出來, 但是我們可以對網頁進行除錯, 嘗試從中獲得我們需要的資訊.

參考

為什麼已有兩篇分析我還要自己再寫一篇呢?

原因有二:

  1. 兩篇分析對我來說還不足夠詳細
  2. 看完兩篇分析之後, 是我第一次使用斷點除錯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)對傳入的引數進行加密, 進行分析, 大致邏輯如下:

  1. 呼叫函式a生成一個16位隨機數i;
  2. 呼叫函式b, 加密d(後文可知這是四個引數值唯一可變的), 得到encText
  3. 呼叫函式b, 加密步驟2得到的encText, 得到新的encText
  4. 呼叫函式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

實現加密