爬虫入门实战第二站-爬取网易云歌曲评论
爬虫入门实战第二站-爬取网易云歌曲评论
简介
之前的工作:爬虫入门实战第一站——梨视频视频爬取
由于csdn中无法发布爬虫的内容,就在这里发布了。
这次的任务是获取网易云音乐下面的评论,涉及的知识比上次更多,包括Js逆向的知识。
使用的python包:
- execjs(运行Js文件,通过pip install PyExecJS安装)
- requests(发起请求)
- json(json数据转换)
使用浏览器:
- Edge浏览器
步骤
1.找到一首歌曲

2.按下F12键打开开发者模式,对其进行抓包
刷新网页进行抓包,结果如下:

3.查找获得评论数据的接口
方法一
点击这些接口,然后点击预览,预览里面是接口的返回数据,我们看下是否有评论数据。

方法二
在搜索栏搜索评论信息,找到接口,这个比上面一个一个接口分析要快。但注意如果页面加密了就不行了。

由此我们得到网易云评论数据的接口是:https://music.163.com/weapi/comment/resource/comments/get?csrf_token=
这里csrf_token是登录后才会有信息的。
4.对获得评论数据接口进行分析
(1)点击负载,可以看到接口的参数是进行了加密的,那我们需要找到它是如何进行加密的。

(2)为了找到加密过程,我们点击发起程序部分。
这是一个调用堆栈,它的执行顺序是从下往上执行。
(3)我们点击第一个程序,也是最后才执行的,出现如下结果:

这里的send函数是发送信息给服务器的作用,我们需要的加密数据也通过这个函数发送,我们对它进行进一步分析。
(4)给send函数位置打一个断点,找到目标接口调用这个函数的位置。
如下图所示,我们需要的是url应该是get?csrf_token=,而不是下图所示的内容。

继续进行debug,知道看到我们需要的接口。

(5)对目标接口位置进行分析。
通过该函数后参数被加密了,我们需要找到它没加密前是在哪个位置。

第(4)步调试结束的位置,下面有一个调用堆栈,就是该接口调用的一些文件,也是从下往上依次执行,我们对其进行分析,找到没有加密的数据最后存在的地方。

我们从上往下依次寻找,最后找到这个位置:

因此数据的形式为:
"rid":"R_SO_4_472603422",
"threadId":"R_SO_4_472603422",
"pageNo":"1",
"pageSize":"20",
"cursor":"-1",
"offset":"0",
"orderType":"1",
"csrf_token":""
(6)找到数据后,分析它是如何进行加密的。
打开第(5)步找到的文件并打上断点,如图所示:

然后刷新界面进行调试,注意接口需要是get才行:

然后继续运行后面的语句,发现数据加密了。

由此我们基本可以得到加密的语句为:
var bMr1x = window.asrsea(JSON.stringify(i0x), bsg8Y(["流泪", "强"]), bsg8Y(TH5M.md), bsg8Y(["爱心", "女孩", "惊恐", "大笑"]));
那我们之后需要做的就是实现跟这个函数类似的功能。
5.构建加密函数
找到第4步的window.asrsea函数,通过在文件里面按下Ctrl+F键,然后进行搜索。

由此可以得到加密函数的整体框架:
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
}
对于上述加密函数的实现,有两种方式:
- 使用js实现,通过该文件中的函数实现加密功能,遇到缺少的函数继续在该文件中查找。补齐后通过python的库函数调用js文件实现加密功能。
- 使用python实现,通过分析加密函数的逻辑,实现相同的功能。
在说明下面内容之前,先看下d函数,有4个参数,再看下调用它的地方:
window.asrsea(JSON.stringify(i0x), bsg8Y(["流泪", "强"]), bsg8Y(TH5M.md), bsg8Y(["爱心", "女孩", "惊恐", "大笑"]));
我们看下它的每个参数的值,发现除了第一个参数,其他参数其实都是定值,通过运行这些函数,得到:
e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
这对应d函数里面的参数,因此d函数只要传入一个字符串数据即可。
方法一
这里我说下具体思路。
对于上面的加密函数整体框架,我们一个一个补齐里面缺少的函数。
我们发现c函数中的RSAKeyPair是需要补齐的,补齐方法如下:

然后就找到函数了。

该函数里面的其他缺失函数也是用同样的方法补齐。
补齐完成后使用execjs库里面的函数进行调用,过程如下:
ctx = execjs.compile(open('补充完整的js文件地址', 'r', encoding='utf-8').read()).call('d', '输入的参数')
由此得到加密后的数据,也就是d函数返回的数据。
然后封装成参数,即:
post_data = {}
post_data['params'] = ctx['encText']
post_data['encSecKey'] = ctx['encSecKey']
最后发起请求:
get_comment_url = 'https://music.163.com/weapi/comment/resource/comments/get?csrf_token='
response = requests.post(url=get_comment_url,data=post_data)
方法二
先分析a函数,很明显它是要获得一个a位的随机字符串。
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
}
然后分析b函数,它里面没有其他的随机数,都是用于加密的一些函数,因此给定固定的参数,它就是定值。
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()
}
对于c函数,也是一个加密的过程,没有随机因素。
function c(a, b, c) {
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
通过分析d函数,我们可以知道h.encText其实就是params参数,h.encSecKey其实就是encSecKey参数。对于h.encText,由于d对于不同的歌曲来说是不同的,所以我们需要手动实现加密过程。
然后对于 h.encSecKey,e和f都是定值,i我们在运行过程中获得,那么h.encSecKey就是一个定值,所以我们直接用运行过程中得到的i和h.encSecKey就行。
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
}
下面开始完成python代码。
首先,在运行过程中,我们得到i和h.encSecKey的值:

i = 'ompBdaIcEweDSvm0'
def get_encSecKey():
return "ByfLFqCAkOx5zSR+99lUdjEA9eCX9cDOLq/BrIAllXsAVuqJ64yyGo7DXxAN5UhksFOalcMawpjJhZP3wVfqmhBdCbxSSZvY4zblYWx8T34wkJVfRxzYnfmuYm3iRIqoAYcFjLzKvogEbOIwqYx7vWyob+neQC/TzS7uYK2VH7LhWTveYQ/4PXVk1lUyA3puYYTlvQ3TGfUThGzBRa0sw1poUwGsIUfI1UkhQpzCO1UZlev/3HkweKWwsRmLntYp6Mq2feSKOfuNu+tgJyLEeiwM9ZEkXrqJjepzwEeMzc8="
然后完成b函数的功能,这里使用了AES类,是Crypto里面的。
def enc_parmas(data,key):
iv = "0102030405060708"
data = to_16(data)
aes = AES.new(key=key.encode("utf-8"),IV=iv.encode("utf-8"),mode=AES.MODE_CBC)
bs = aes.encrypt(data.encode("utf-8"))
return str(b64encode(bs),"utf-8")
最后得到h.encText,也就是params参数。
def get_params(data):
fisrt = enc_parmas(data,g)
second = enc_parmas(fisrt,i)
return second
得到接口需要的参数后发起请求就可以得到评论数据了。需要注意传入的data参数是json字符串格式的。
6.获取评论数据
通过发起请求后得到的内容为:

我们将其转换为json格式
response.encoding = 'utf-8'
json_resp = response.json()
然后获得json数据里面的评论数据:
comments = json_resp['data']['comments']
for comment in comments:
content = comment['content']
print(content)
其他数据也可以获得,这需要自己分析json数据了。如评论数量:
total_comment_num = json_resp['data']['totalCount']
全部代码
使用Js文件
Js文件目前没有提供,可以私聊我获取。
import json
import execjs
import requests
get_comment_url = 'https://music.163.com/weapi/comment/resource/comments/get?csrf_token='
data = {
"rid":"R_SO_4_472603422",
"threadId":"R_SO_4_472603422",
"pageNo":"1",
"pageSize":"20",
"cursor":"-1",
"offset":"0",
"orderType":"1",
"csrf_token":""
}
# 转换为json字符串
json_string = json.dumps(data)
# 调用json文件
ctx = execjs.compile(open('encry.js', 'r', encoding='utf-8').read()).call('d', json_string)
print(ctx)
# 组成提交的数据
post_data = {}
post_data['params'] = ctx['encText']
post_data['encSecKey'] = ctx['encSecKey']
# print(post_data)
response = requests.post(url=get_comment_url,data=post_data)
只使用python
from Crypto.Cipher import AES # pip install pycryptodome
from base64 import b64encode
import requests
import json
def get_encSecKey():
return "ByfLFqCAkOx5zSR+99lUdjEA9eCX9cDOLq/BrIAllXsAVuqJ64yyGo7DXxAN5UhksFOalcMawpjJhZP3wVfqmhBdCbxSSZvY4zblYWx8T34wkJVfRxzYnfmuYm3iRIqoAYcFjLzKvogEbOIwqYx7vWyob+neQC/TzS7uYK2VH7LhWTveYQ/4PXVk1lUyA3puYYTlvQ3TGfUThGzBRa0sw1poUwGsIUfI1UkhQpzCO1UZlev/3HkweKWwsRmLntYp6Mq2feSKOfuNu+tgJyLEeiwM9ZEkXrqJjepzwEeMzc8="
def to_16(data):
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
def get_params(data):
fisrt = enc_parmas(data,g)
second = enc_parmas(fisrt,i)
return second
def enc_parmas(data,key):
iv = "0102030405060708"
data = to_16(data)
aes = AES.new(key=key.encode("utf-8"),IV=iv.encode("utf-8"),mode=AES.MODE_CBC)
bs = aes.encrypt(data.encode("utf-8"))
return str(b64encode(bs),"utf-8")
get_comment_url = 'https://music.163.com/weapi/comment/resource/comments/get?csrf_token='
data = {
"rid": "R_SO_4_1325905146",
"threadId": "R_SO_4_1325905146",
"pageNo": "1",
"pageSize": "2000",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"csrf_token": ""
}
e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
i = 'ompBdaIcEweDSvm0'
post_data = {}
post_data['params'] = get_params(json.dumps(data))
post_data['encSecKey'] = get_encSecKey()
resp = requests.post(url=get_comment_url,data=post_data)
resp.encoding = 'utf-8'
json_resp = resp.json()
# print(json_resp)
comments = json_resp['data']['comments']
for comment in comments:
content = comment['content']
print(content)
小结与展望
以上就是网易云音乐评论获取的全部内容了,其实该套加密程序在网易云音乐里面的其他功能也有使用,因此掌握了该方法其他功能也可以进行尝试,如网易云的搜索。
除此之外,data里面的rid的值R_SO_4_1325905146中1325905146其实就是歌词的id,因此可以改变它来获取不同的歌曲的评论,也可以通过改变pageSize来获取每页评论的数目。

浙公网安备 33010602011771号