话不多说直接开始干
首先明确我们的目标,爬取网易云评论数据(每条评论和评论总数),打开网页版网易云飙升榜
(作为学习用途进行探讨,不涉及任何商业用途,不公开任何数据,只探讨爬虫)
首先我们很容易的就拿到榜单前100的链接,没有啥技术难度
注意的点是把链接中的#去掉,否则拿不到源码
listurl = 'https://music.163.com/discover/toplist'
while True:
response = requests.get(listurl, headers=header)
html = etree.HTML(response.text)
url = html.xpath('//ul[@class="f-hide"]/li/a/@href')
song_name = html.xpath('//ul[@class="f-hide"]/li/a/text()')
拿到所有飙升榜歌曲url
好了,然后干嘛呢?写个循环遍历挨个挨个拿嘛,简单!
来了
先看看原有数据(评论数,每条评论内容,评论人,评论的点赞数。。。。)
写到一半突然发现,这个网页是异步加载的
能直接get到的网页都有哪些数据呢?
好的,啥也没有
这个时候打开我们祖传的f12测试的网络
好样的,非常经典的异步加载
看看啥类型
非常好,非常经典的post
看看啥表单
完犊子,又是加密
虽然可以通过复制表单内容,取获取这些信息
但是下一页怎么办?一首歌就有几千页
100首歌,emmmmm,还是得破解一下加密信息
重点来了
打开启动器,看看是哪个该死的文件发出的加密
展开起码上万行。。
我们直接定位这俩post参数,既然放在一起,这俩肯定是同时出现,很容易就找到了这一段js代码
看明白了
我们的目标由找 params
和 encSecKey
变成了找 bVj8b.encText
和 bVj8b.encSecKey
继续搜
好的我们得到了bVj8b的初始化函数,找到函数
把这一行代码拿出来
!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
}
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, window.ecnonasr = e
}();
var bVj8b = window.asrsea(JSON.stringify(i4m), bsR5W(["流泪", "强"]), bsR5W(Xp0x.md), bsR5W(["爱心",
"女孩", "惊恐", "大笑"])
);
这下逻辑清晰了
表单就是由上面的d函数生成的,传入的参数分别是
(别问为啥是这个)JSON.stringify(i4m)
bsR5W(["流泪", "强"])
bsR5W(Xp0x.md)
bsR5W(["爱心","女孩", "惊恐", "大笑"])
每个参数深入挖掘
第一个,首先我们很容易确定内部的i4m是一个json格式的变量,传入的时候把它变成了一个字符串而已
ok来经典做法,取断点调试,控制台直接打印这个i4m
看到了 这下就好理解了,
看看这些都是啥,不难猜到csrf_token
应该是个人信息,但是不登录也可以访问,所以空着没事cursor
这是一个奇怪的东西,就是反爬用的,源码中是这样定义的
cursor: pageNo === 1 || !flag ? -1 : map[options.key][lastPage].cursor
offset
偏移量,就写0就行orderType
不管写1pageNo
页数,多少页写多少pageSize
每页的数量rid
'R_SO_4_'+歌曲idthreadId
与rid相同
源码中初始代码是这样的
options.data = {
rid: data.rid,
threadId: data.rid,
pageNo: pageNo,
pageSize: options.limit,
cursor: pageNo === 1 || !flag ? -1 : map[options.key][lastPage].cursor,
offset: !flag ? 0 : (Math.abs(lastPage - pageNo) - 1) * options.limit,
orderType: pageNo === 1 || !flag || pageNo > lastPage ? 1 : 0
};
所以修改这些post的信息就能获取整个网易云的所有评论信息,是不是很刺激
但是,除了第一页外,其他页数除了要修改pageNo
还要修改cursor
,这个东西吧。。是根据上一页post的信息得到的
剩下三个参数
都是用bsR5W函数加密的,把函数抽出来看,发现函数内部返回的值是由参数确定的
二四两个函数的参数都是写死的,那就不用管了,直接像上面一样调试获取值,
结果看了一会发现三个函数的参数也是写死的,内容如下(有点奇葩,但确实是这样)
Xp0x.md=["色","流感","这边","弱","嘴唇","亲","开心","呲牙","憨笑","猫","皱眉","幽灵","蛋糕","发怒","大哭","兔子","星星","钟情","牵手","公鸡","爱意","禁止","狗","亲亲","叉","礼物","晕","呆","生病","钻石","拜","怒","示爱","汗","小鸡","痛苦","撇嘴","惶恐","口罩","吐舌","心碎","生气","可爱","鬼脸","跳舞","男孩","奸笑","猪","圈","便便","外星","圣诞"]
最后我们调式得到其他三个死值,一级棒
知道四个参数后,我们就可以搞事情了
参数的格式我们已经弄清楚
现在就差加密函数了,
重新写一下原加密js代码
!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
}
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, window.ecnonasr = e
}();
var bVj8b = window.asrsea(JSON.stringify(i4m), bsR5W(["流泪", "强"]), bsR5W(Xp0x.md), bsR5W(["爱心", "女孩", "惊恐", "大笑"]));
现在就是考验代码功底了
将这段js代码用python复现
为方便,直接拆成get_encSecKey()
,get_params()
i4m = {
"csrf_token": "",
"cursor": "-1",
"offset": "0", # 偏移量,前面每页评论数的总和pageSize
"orderType": "1",
"pageNo": "1", # 页数
"pageSize": "20", # 页评论数
"rid": "R_SO_4_{}",
"threadId": "R_SO_4_{}"
}
bsR5W2 = "010001"
bsR5W3 = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
bsR5W4 = "0CoJUm6Qyw8W8jud"
random_num = "d5bpgMn9byrHNtAh" # 随机值,取啥无所谓,这里直接固定了
iv = "0102030405060708" #函数内部调用的一个固定值
def get_encSecKey(): # 因为random_num固定了,这里返回固定值
return "1b5c4ad466aabcfb713940efed0c99a1030bce2456462c73d8383c60e751b069c24f82e60386186d4413e9d7f7a9c7cf89fb06e40e52f28b84b8786b476738a12b81ac60a3ff70e00b085c886a6600c012b61dbf418af84eb0be5b735988addafbd7221903c44d027b2696f1cd50c49917e515398bcc6080233c71142d226ebb"
def get_params(data):
first = enc_params(data, bsR5W4)
second = enc_params(first, random_num)
return second
def to_16(data): # 转化成16的倍数, 位下方的加密算法服务
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data
def enc_params(data, key): # 加密过程
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(BASE_URL, data={
"params": get_params(json.dumps(i4m)), # 记得format两个songid
"encSecKey": get_encSecKey()
})
# 代码有参考网络
最后就可以得到我们加密后的post表单(其实你会发现只用到了参数1和4,其他俩参数都是打酱油的)
修改参数1 json内的各个变量,我们就可以拿下网易云了(bushi,corsor问题还没解决
但是不重要了,本来我的目的只是评论数
最终我们通过任意歌曲id,便可以获取评论信息,评论数量了
最后提一嘴
这定死的参数有够奇葩的,不知道网易程序猿经历了啥。。
调试方法有很多种,因为我们确定了xhr网络请求
所以我们直接用Chrome内置的功能,非常方便
最后加一个展示界面
完结撒花✿✿ヽ(°▽°)ノ✿