网易云音乐评论爬取
确定信息来源
查看网页中任意一首音乐的评论来源:网页本身/数据包
通过浏览器工具,可以确定数据来源于网络传输的数据包.
尝试进行抓取
首先查看该数据包对应的标头中请求的URL以及请求方式.
POST
请求,给定了URL.内容负载
其内容负载包含两个方面:
很明显,这个内容负载是经过了加密.因此为了将我们的数据进行同样的加密以获取评论内容,我们要知道被加密内容是什么以及怎样加密的.
获取加密方式
查看浏览器工具中的
Network->Initator
,其中包含有Request call stack
在Edge的调试工具中两者命名分别是
发起程序
和请求调用堆栈
.记录当发送请求到我们想要的URL时,经历过了哪些JS脚本的执行过程,由于是堆栈,因此越靠下的部分越先被调用.
查看最后被调用的代码.由于代码比较凌乱,因此我们点击其左下角的大括号,可以看到比较规矩的代码.
通过在代码处设置断点,并再次运行整个网页对函数运行进行拦截,可以看到每一次调用该函数时所设置/用到的相关变量.通过查看拦截后的右侧
Local
内容,查看每次调用时发送请求时的url,与我们之前确定好的url对比,如果相同,则查看相关变量,看是否有我们想要的变量.如果该变量被加密,则继续通过调用堆栈中的函数回溯找,直到找到加密前的我们想要的变量.以福禄寿歌曲《我用什么把你留住》为例
首先通过抓包工具截取到歌曲的评论数据包请求的url地址为:
1
"https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
然后找到堆栈调用的位置,在函数出口处打断点,然后重新加载请求网页.每次查看被中断的请求的请求地址是否与上面的地址相同.
经过大概四次/五次的中断,找到目的请求:
经过查看不难发现此时的参数仍然是被加密后的,因此我们去找堆栈中调用该层函数的函数,也就是其父结点,继续进行如上操作,直到找到未被加密的程序.如下图所示,为了后续使用方便,我们将该数据下面的i4m数据,也就是加密前的字典类型的数据暂存.
由此可以确定,加密应该是在函数
tax.be4i
函数中完成的.因此我们转去查看``tax.be4i`函数中的加密过程.通过查看函数内部的代码,可以发现下图中,下方的红框内的数据是待加密的数据,而上方的红框内为加密完成后的数据.
在函数内部中对内容
e4i
赋值的相关位置打断点,重新加载页面,确定内容加密的具体位置.可以发现在断点1处的数据仍然未被加密.
到断点3,可以发现数据已经被加密
到断点4,发现数据已经完全被加密完成:
那么通过查看上面的断点3处的代码,可以发现,在断点3处为数据进行了赋值,成员包括
encSevKey
和params
,两者正是我们要找到的参数,并且两者都来自于变量bVj8b
.而变量bVj8b
的数据又是通过函数Windows.asrsea
产生的.因此我们在整个页面中查找该函数.找到该函数的赋值位置,然后进一步确定该函数的内部参数的含义:
解析如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61// 参数通过调用此处的函数产生
var bVj8b = window.asrsea(JSON.stringify(i4m), bsR5W(["流泪", "强"]), bsR5W(Xp0x.md), bsR5W(["爱心", "女孩", "惊恐", "大笑"]));
e4i.data = j4n.cq4u({
params: bVj8b.encText, // 负载参数之一
encSecKey: bVj8b.encSecKey // 负载参数之二
})
// asrsea函数外部
!function() {
function a(a) { // 进行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) { // 也就是加密函数,以b为密钥,对a进行加密
var c = CryptoJS.enc.Utf8.parse(b) // 密匙编码
, d = CryptoJS.enc.Utf8.parse("0102030405060708") // 偏移量编码
, e = CryptoJS.enc.Utf8.parse(a) // 编码转换
// AES 加密
, f = CryptoJS.AES.encrypt(e, c, {
iv: d, // 偏移量
mode: CryptoJS.mode.CBC // 加密模式
});
return f.toString()
}
// 参数为随机字符串i,固定值e(?)和固定值f
function c(a, b, c) { // encSecKey的生成函数
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c), // 对不同歌曲的固定值e进行加密
e = encryptedString(d, a) // 同样是进行加密,用加密后的RSA密钥对对随机字符串i进行加密
}
// f='00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'\
// g='0CoJUm6Qyw8W8jud'
// e='01001'
// d=tostring(data),e=bsR5W(["流泪", "强"]),也即`01001`,f=bsR5W(Xp0x.md),g=bsR5W(["爱心", "女孩", "惊恐", "大笑"])
function d(d, e, f, g) { // 产生加密的函数 此处的唯一变动实际上是d的值,也就是取决于data的内容
var h = {}
, i = a(16); // 产生16个随机字符
return h.encText = b(d, g),
h.encText = b(h.encText, i), // 也就是对数据内容进行两次加密,第一次加密使用的密钥为固定值g
// 第二次加密使用的密钥为随机数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, // asrsea函数的真正本体
window.ecnonasr = e
}();
代码
1 | # -*- codeing = utf-8 -*- |
成功结果