如何爬网易云音乐的评论数?

首先表明写这个代码纯粹是想用来找一下评论数多的歌曲,然后排个序依次听而已,并没有任何商业用途的意思。 下面是我的尝试: 比如爬see you agai…
关注者
693
被浏览
210,561

27 个回答

说下自己的爬评论分析过程吧(其实是为了爬评论才研究的,但是评论数和评论的数据在一个json中,虽然有点文不对题,但还是希望能对题主有帮助)

首先因为我们点击下一页看后面的评论时是只有评论刷新的,所以这里获得评论数据是发xhr(XMLHTTPRequest),那么打开浏览器的网络面板,找出所有类型为xhr的数据包,评论数据就在他们之中,挨个看一下可以确认是红圈内的这个。

然后会发现这个请求是一个post,有两个参数,params和encSecKey

那么这两个参数是怎么获得的呢?这模样当然是经过js加密的,从initiator一栏里可以看到这个请求的“发起人”是core.js,一般这样的js都是没法看的,下载下来美化过后发现有两万多行,但是没关系,我们需要的只是部分数据。

在这个js文件中搜索params和encSecKey,可以找到这里

那么问题就变成得到这个bua,它是由window.asrsea这个函数得到的,可以看到有4个参数,如果研究每个参数肯定是痛苦的,也没有必要,可以先把它们输出来看一下,这时候就需要线上调试js,我选择了Fiddler,在Fiddler的AutoResponder页添加Rule,大概长这样

之后网页加载使用的core.js文件就是我们本地的这个js文件了,而我们可以修改本地的这个文件来获得想要的数据

比如对于第一个参数,就是bl变成字符串,我们可以直接输出bl,同时输出params参数,然后根据正确的params找到正确的bl

然后就成功找到了bl,如图(这里csrf_token本来是有值的,但是你会发现没有它也没关系,这里就省略掉好了)

可以根据不同的歌曲和翻译页数多试几次,可以发现rid就是R_SO_4_加上歌曲的id(其实这个参数也是可以没有的),offset就是(评论页数-1) * 20,total在第一页是true,其余是false。

按这样的方式可以得到其余三个参数

第二个参数:

010001

第三个参数:

00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7

第四个参数:

0CoJUm6Qyw8W8jud

现在我们只要知道函数window.asrsea如何处理的就可以了,定位到这个函数发现它其实是一个叫d的函数

这里的i研究之后你会发现就是一个长度为16的随机字符串,既然是随机的,我就直接让他等于16个F了。这个encText明显就是params,encSecKey明显就是encSecKey。而b函数就是一个AES加密,经过了两次加密,第一次对d也就是那个json加密,key是第四个参数,第二次对第一次加密结果进行加密,key是i。在b函数中我们可以看到

密钥偏移量iv是0102030405060708,模式是CBC,那么就不难写出对于这个json的加密了。

接下来是第二个参数encSecKey,你会发现在我们这种情境下,这里传入c的三个参数i是16个F,e是第二个参数,f是第三个参数,全部是固定的值,那么无论歌曲id或评论页数如何变化,这个encSecKey都不随之发生变化,所以这个encSecKey对我们来说就是个常量,抄一个下来就是可以使用的。至此,我们得到了所有的两个参数。

代码如下(AES加密部分参考了

@洛克

的代码)

#coding = utf-8
from Crypto.Cipher import AES
import base64
import requests
import json


headers = {
    'Cookie': 'appver=1.5.0.75771;',
    'Referer': 'http://music.163.com/'
}

first_param = "{rid:\"\", offset:\"0\", total:\"true\", limit:\"20\", csrf_token:\"\"}"
second_param = "010001"
third_param = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
forth_param = "0CoJUm6Qyw8W8jud"

def get_params():
    iv = "0102030405060708"
    first_key = forth_param
    second_key = 16 * 'F'
    h_encText = AES_encrypt(first_param, first_key, iv)
    h_encText = AES_encrypt(h_encText, second_key, iv)
    return h_encText


def get_encSecKey():
    encSecKey = "257348aecb5e556c066de214e531faadd1c55d814f9be95fd06d6bff9f4c7a41f831f6394d5a3fd2e3881736d94a02ca919d952872e7d0a50ebfa1769a7a62d512f5f1ca21aec60bc3819a9c3ffca5eca9a0dba6d6f7249b06f5965ecfff3695b54e1c28f3f624750ed39e7de08fc8493242e26dbc4484a01c76f739e135637c"
    return encSecKey
    

def AES_encrypt(text, key, iv):
    pad = 16 - len(text) % 16
    text = text + pad * chr(pad)
    encryptor = AES.new(key, AES.MODE_CBC, iv)
    encrypt_text = encryptor.encrypt(text)
    encrypt_text = base64.b64encode(encrypt_text)
    return encrypt_text


def get_json(url, params, encSecKey):
    data = {
         "params": params,
         "encSecKey": encSecKey
    }
    response = requests.post(url, headers=headers, data=data)
    return response.content


if __name__ == "__main__":
    url = "http://music.163.com/weapi/v1/resource/comments/R_SO_4_30953009/?csrf_token="
    params = get_params();
    encSecKey = get_encSecKey();
    json_text = get_json(url, params, encSecKey)
    json_dict = json.loads(json_text)
    print json_dict['total']
    for item in json_dict['comments']:
        print item['content'].encode('gbk', 'ignore')

根据我的搜索,用这个帖子(

kevinsfork.info/2015/07

)算出来的的参数去post这个地址 (

http://music.163.com/weapi/v1/resource/comments/R_SO_4_30953009/?csrf_token=

) 就应该能得到评论的json数据

代码大概这样子

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import json
import os
import base64
from Crypto.Cipher import AES
from pprint import pprint


def aesEncrypt(text, secKey):
    pad = 16 - len(text) % 16
    text = text + pad * chr(pad)
    encryptor = AES.new(secKey, 2, '0102030405060708')
    ciphertext = encryptor.encrypt(text)
    ciphertext = base64.b64encode(ciphertext)
    return ciphertext


def rsaEncrypt(text, pubKey, modulus):
    text = text[::-1]
    rs = int(text.encode('hex'), 16)**int(pubKey, 16) % int(modulus, 16)
    return format(rs, 'x').zfill(256)


def createSecretKey(size):
    return (''.join(map(lambda xx: (hex(ord(xx))[2:]), os.urandom(size))))[0:16]


url = 'http://music.163.com/weapi/v1/resource/comments/R_SO_4_30953009/?csrf_token='
headers = {
    'Cookie': 'appver=1.5.0.75771;',
    'Referer': 'http://music.163.com/'
}
text = {
    'username': '邮箱',
    'password': '密码',
    'rememberLogin': 'true'
}
modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
nonce = '0CoJUm6Qyw8W8jud'
pubKey = '010001'
text = json.dumps(text)
secKey = createSecretKey(16)
encText = aesEncrypt(aesEncrypt(text, nonce), secKey)
encSecKey = rsaEncrypt(secKey, pubKey, modulus)
data = {
    'params': encText,
    'encSecKey': encSecKey
}

req = requests.post(url, headers=headers, data=data)
pprint(req.json())
for content in req.json()['comments']:
    print content['content'].encode('utf-8')
    print
print req.json()['total']



最后那个数字就是评论总数, 其余是最新评论.