首先分析可以得到网易云音乐的评论不是在网页源代码中存储的,而是通过ajax异步请求得到的数据,浏览器开启F12,刷新网页,发现这个请求返回的数据就是我们想找到的评论数据。
但是查看其请求头却发现,请求的参数是加密过的,那么如何找到这个加密方法呢?
通过查看Initiator我们可以定位到发起这个请求的js文件
然后我们在这行打上断点,刷新界面,观察变量的值,我们发现这个值已经被加密处理过来,然后我们查看函数调用的堆栈,一个一个的分析,找到被加密的地方。
然后找到了加密的地方
其实window.asrsea就是函数d
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,
那么这个时候我们已经知道加密的js代码了,此时只需将这段js代码用python来实现即可。我们首先来看函数d的参数,第一个参数d是将i9b这个变量转成json的字符串,因此我们在python中定义同样的变量即可。
i9b = {
"csrf_token": "",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_1901371647",
"threadId": "R_SO_4_1901371647"
}
第二个参数e,传入的是bsP6J(["流泪", "强"])
bsP6J是一个函数
var bsP6J = function(cxG5L) {
var m9d = [];
j9a.bg9X(cxG5L, function(cxF5K) {
m9d.push(Xk6e.emj[cxF5K])
});
return m9d.join("")
};
我们直接打开浏览器的Console面板,运行这个语句,发现得到的值都是一样的,即字符串"010001"。那我们就声明e = "010001"
按照此方法可以得到
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
至此,d函数的参数已经全部确定。
接下来,我们先看函数d中的h.encSecKey的值是如何确定的, h.encSecKey = c(i, e, f)
,其中的i是由函数函数a返回的,查看函数a的代码可以发现其功能就是返回一个长度为16的字符串,但是是随机的,每次返回的结果是不一样的,由此我们可以得出,在c函数中,这个变量i的值每次都不一样,而e和f的值又是确定的,所以我们只要固定i是固定的,那么c函数返回的值也是固定的,所以我们直接取一次运行的i值,代入函数c中,得到的encSecText作为我们的参数即可。改写成python代码如下:
#随便取一次调试的值即可
def get_encSecKey(): # 由于i是固定的. 那么encSecText就是固定的. c()函数的结果就是固定的
return "1b5c4ad466aabcfb713940efed0c99a1030bce2456462c73d8383c60e751b069c24f82e60386186d4413e9d7f7a9c7cf89fb06e40e52f28b84b8786b476738a12b81ac60a3ff70e00b085c886a6600c012b61dbf418af84eb0be5b735988addafbd7221903c44d027b2696f1cd50c49917e515398bcc6080233c71142d226ebb"
接下来我们分析h.encText是如何得到的,先看函数b
function b(a, b) { # a是要加密的内容,
var c = CryptoJS.enc.Utf8.parse(b) # # b是秘钥
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a) # e是数据
, f = CryptoJS.AES.encrypt(e, c, { # c 加密的秘钥
iv: d, # 偏移量
mode: CryptoJS.mode.CBC # 模式: cbc
});
return f.toString()
}
h.encText = b(d, g) # g秘钥
h.encText = b(h.encText, i) # 返回的就是params i也是秘钥
就是将内容进行了两次加密,两次使用的密钥不一样,第二次加密的内容就是第一次加密返回的结果作为参数传入。而b函数查看代码也看出了这个是AES加密,而python中也存在同样的包,对此,可以将加密方法改装成python代码。
# 把参数进行加密
def get_params(data): # 默认这里接收到的是字符串
first = enc_params(data, g)
second = enc_params(first, i)
return second # 返回的就是params
# 加密过程
def enc_params(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")) # 加密, 加密的内容的长度必须是16的倍数
return str(b64encode(bs), "utf-8") # 转化成字符串返回,
# 转化成16的倍数, 为下方的加密算法服务
def to_16(data):
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
所有代码如下
# 需要安装pycrypto: pip install pycrypto
#coding = utf-8
from Crypto.Cipher import AES
from base64 import b64encode
import requests
import json
url = "https://music.163.com/weapi/comment/resource/comments/get?csrf_token="
# 请求方式是POST
i9b = {
"csrf_token": "",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"pageNo": "1",
"pageSize": "20",
"rid": "R_SO_4_1901371647",
"threadId": "R_SO_4_1901371647"
}
# 服务于d的
f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g = "0CoJUm6Qyw8W8jud"
e = "010001"
i = "d5bpgMn9byrHNtAh" # 手动固定的. -> 人家函数中是随机的
def get_encSecKey(): # 由于i是固定的. 那么encSecText就是固定的. c()函数的结果就是固定的
return "1b5c4ad466aabcfb713940efed0c99a1030bce2456462c73d8383c60e751b069c24f82e60386186d4413e9d7f7a9c7cf89fb06e40e52f28b84b8786b476738a12b81ac60a3ff70e00b085c886a6600c012b61dbf418af84eb0be5b735988addafbd7221903c44d027b2696f1cd50c49917e515398bcc6080233c71142d226ebb"
# 把参数进行加密
def get_params(data): # 默认这里接收到的是字符串
first = enc_params(data, g)
second = enc_params(first, i)
return second # 返回的就是params
# 转化成16的倍数, 位下方的加密算法服务
def to_16(data):
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
# 加密过程
def enc_params(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")) # 加密, 加密的内容的长度必须是16的倍数
return str(b64encode(bs), "utf-8") # 转化成字符串返回,
# 发送请求. 得到评论结果
resp = requests.post(url, data={
"params": get_params(json.dumps(i9b)),
"encSecKey": get_encSecKey()
})
print(resp.text)
结果如下: