美文网首页
网易云评论api分析

网易云评论api分析

作者: Mr_Normal | 来源:发表于2018-12-31 14:26 被阅读0次

    title: 网易云评论api分析
    date: 2018-12-24 20:54:46
    tags: [python]


    网易云音乐是个好地方,里面各个都是人才,特别是评论区……

    所以我就想把评论爬下来看看,下面记录一下分析api的过程与结果。

    过程大概如下:

    • 定位到获取评论的请求
    • 分析评论请求方式
    • 用python获取评论

    定位评论请求

    废话少说,F12,在网页中随便找一首歌,url形如: https://music.163.com/#/song?id=28828076,抓包。

    待页面加载完成之后,随便找条评论搜索,定位到获取评论的那条请求,如下图:

    netease-comment-requests.png

    可以看到这条请求是发向https://music.163.com/weapi/v1/resource/comments/R_SO_4_28828076?csrf_token=的,而其中的R_SO_4_后面其实就是歌曲的id。

    分析请求

    请求中发送的数据如下图,看一眼,啥都看不懂,肯定是加密过了。

    netease-comment-post-data.png

    从表单中的名称猜测出一个是加密后的内容,另一个是加密密钥。

    下面要找到在哪里加密的信息,然后进一步找到加密方法,这样我们就可以用爬虫来模拟了。

    在哪加密

    同样,在network tab下面搜索encSecKey,搜到了一个js文件中的两个函数都有encSecKey

    netease-comment-encSecKey.png

    下面列出这两个函数:

    函数一

    ! 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
        }
    
        // a是待加密数据,b是passphrase
        function b(a, b) {
            var c = CryptoJS.enc.Utf8.parse(b),
                d = CryptoJS.enc.Utf8.parse("0102030405060708"),
                e = CryptoJS.enc.Utf8.parse(a),
                // 使用AES加密
                // e是待加密信息
                // c是passphrase
                // d是nonce
                f = CryptoJS.AES.encrypt(e, c, {
                    iv: d,
                    mode: CryptoJS.mode.CBC
                });
            return f.toString()
        }
    
        // 生成RSA密钥对
        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) {
            // d是表单数据
            // e和g是常数
            // 经过分析e: 010001
            //        g: 0CoJUm6Qyw8W8jud
            
            var h = {},
                i = a(16);  // 长度为16的随机串
            
            // 上面的b函数是用于AES加密,这里调用b进行了两次AES加密
            // 使用
            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
    }();
    

    函数二

    (function() {
        var c9h = NEJ.P,
            et2x = c9h("nej.g"),
            v9m = c9h("nej.j"),
            k9b = c9h("nej.u"),
            Xw8o = c9h("nm.x.ek"),
            l9c = c9h("nm.x");
        if (v9m.bl9c.redefine) return;
        window.GEnc = true;
        var brK4O = function(cxx0x) {
            var m9d = [];
            k9b.be9V(cxx0x, function(cxv0x) {
                m9d.push(Xw8o.emj[cxv0x])
            });
            return m9d.join("")
        };
        var cxt0x = v9m.bl9c;
        v9m.bl9c = function(Y9P, e9f) {
            var i9b = {},
                e9f = NEJ.X({}, e9f),
                mt4x = Y9P.indexOf("?");
            if (window.GEnc && /(^|\.com)\/api/.test(Y9P) && !(e9f.headers && e9f.headers[et2x.Cf0x] == et2x.Gl1x) && !e9f.noEnc) {
                if (mt4x != -1) {
                    i9b = k9b.hb3x(Y9P.substring(mt4x + 1));
                    Y9P = Y9P.substring(0, mt4x)
                }
                if (e9f.query) {
                    i9b = NEJ.X(i9b, k9b.fP2x(e9f.query) ? k9b.hb3x(e9f.query) : e9f.query)
                }
                if (e9f.data) {
                    i9b = NEJ.X(i9b, k9b.fP2x(e9f.data) ? k9b.hb3x(e9f.data) : e9f.data)
                }
                i9b["csrf_token"] = v9m.gO3x("__csrf");
                Y9P = Y9P.replace("api", "weapi");
                e9f.method = "post";
                delete e9f.query;
                var bVs3x = window.asrsea(JSON.stringify(i9b), brK4O(["流泪", "强"]), brK4O(Xw8o.md), brK4O(["爱心", "女孩", "惊恐", "大笑"]));
                e9f.data = k9b.cB0x({
                    params: bVs3x.encText,
                    encSecKey: bVs3x.encSecKey
                })
            }
            cxt0x(Y9P, e9f)
        };
        v9m.bl9c.redefine = true
    })();
    

    函数二调用了window.asrsea,而window.asrsea又在函数一中被赋值为d

    加密参数

    window.asrsea的参数表中,发现有两个硬编码的列表经过某个函数转换后传入到window.asrsea,也就是传入到d中,在d打下断点,发现经过转换后的传入到d中的参数如下:

    d: "{"ids":"[5113327]","br":128000,"csrf_token":""}"
    e: "010001"
    f: "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
    g: "0CoJUm6Qyw8W8jud"
    

    现在两个常数e,g都已经确定了,经过多次断点,发现f竟然也是常数。

    那么现在传入加密函数d中的所有参数都已经确定了,接下来要分析出来怎么用这些参数加密数据,然后发送到服务器。

    加密策略

    加密策略是使用AES加密请求数据,然后用RSA加密AES密钥,我尝试分析了一下,发现分析得头皮发麻,后面在github找到了。。。。。链接在这:网易云新版webapi分析

    代码

    代码是python2写的,改了一下,python3能用:

    import requests
    import os
    import binascii
    import base64
    import requests
    import json
    from Crypto.Cipher import AES
    
    
    modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
    nonce = b'0CoJUm6Qyw8W8jud'
    pubKey = '010001'
    
    """
    加密过程
    随机生成加密密钥secKey
    然后用secKey两次对post的查询数据进行两次AES加密
    用RSA对secKey加密
    最后把加密后的secKey和查询数据post到服务器
    """
    
    def createSecretKey(size):
        return binascii.hexlify(os.urandom(size))[:16]
    
    def aesEncrypt(text, secKey):
        pad = 16 - len(text) % 16
        text = text + pad * str(chr(pad))
        encryptor = AES.new(secKey, AES.MODE_CBC, '0102030405060708'.encode('utf8'))
        ciphertext = encryptor.encrypt(text.encode('utf8'))
        ciphertext = base64.b64encode(ciphertext).decode('utf8')
        return ciphertext
    
    def rsaEncrypt(text, pubKey, modulus):
        text = text[::-1]
        rs = int(bytes.hex(text), 16) ** int(pubKey, 16) % int(modulus, 16)
        return format(rs, 'x').zfill(256)
    
    def encrypted_request(text):
        text = json.dumps(text)
        secKey = createSecretKey(16)
        encText = aesEncrypt(aesEncrypt(text, nonce), secKey)
        encSecKey = rsaEncrypt(secKey, pubKey, modulus)
        data = {
            'params': encText,
            'encSecKey': encSecKey
        }
        return data
    
    def load_headers(filename):
        headers = {}
        with open(filename, 'r') as f:
            for line in f.readlines():
                entry = line.split(':', 1)
                if len(entry) != 2:
                    continue
                headers[entry[0].strip()] = entry[1].strip()
        return headers
    
    def comment_post_url(song_id, csrf=''):
        return 'https://music.163.com/weapi/v1/resource/comments/R_SO_4_' + \
            song_id + '?csrf_token=' + csrf
    
    if __name__ == '__main__':
    
        # 加载请求头
        headers = load_headers('headers')
    
        song_id = '28828074'  
    
        req = {
            "csrf_token":"",
            "limit":"20",
            "offset":"0",
            "rid":"R_SO_4_" + song_id,
            "total":"true"
        }
    
        url = comment_post_url(song_id)
    
        postdata = encrypted_request(req)
    
        print('sending to {}'.format(url))
    
        r = requests.post(url, data=postdata, headers=headers)
    
        print(r.text)
    

    经过测试Cookies中只有带有_ntes_nuid就行了。

    headers:

    Host: music.163.com
    Connection: keep-alive
    Origin: https://music.163.com
    User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/71.0.3578.80 Chrome/71.0.3578.80 Safari/537.36
    Content-Type: application/x-www-form-urlencoded
    Accept: */*
    Referer: https://music.163.com/song?id=558290126
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,en-GB;q=0.6
    Cookie: _ntes_nuid=9ae4bf42cfdf19018c7b461442deffe2
    

    参考资料

    网易云音乐API分析

    网易云新版webapi分析

    相关文章

      网友评论

          本文标题:网易云评论api分析

          本文链接:https://www.haomeiwen.com/subject/fdoilqtx.html