python爬虫获取网易云音乐评论歌词以及歌曲地址
- 一.寻找数据接口
- 二.对负载分析
- 三.寻找参数加密过程
- 1.首先找到评论的请求包并找到发起程序
- 2.寻找js加密的代码
- 四.扣取js的加密源码
- 1.加密函数参数分析
- ①.JSON.stringify(i0x)
- ②bse6Y(["流泪", "强"])
- ③bse6Y(Qu1x.md)
- ④.bse6Y(["爱心", "女孩", "惊恐", "大笑"])
- 1.加密函数分析
- 五.在本地环境使用此函数
- 1.直接将js代码复制并用js环境运行
- 2.将js代码翻译为python代码
- 六.python代码
一.寻找数据接口
评论
url:https://music.163.com/weapi/comment/resource/comments/get
歌词
url:https://music.163.com/weapi/song/lyric
歌曲地址
url:https://music.163.com/weapi/song/enhance/player/url/v1
二.对负载分析
从三个接口的负载我们可以得出
data={
params:加密数据
encSecKey:加密数据
}
所以我们只需要找出这个两个参数的加密过程就能模拟请求并得到我们想要的数据
三.寻找参数加密过程
因为这三个接口的加密是一样的这里我们用评论的接口来讲解
1.首先找到评论的请求包并找到发起程序
f12打开调试并且点击网络然后找到对应的包https://music.163.com/weapi/comment/resource/comments/get
点击发起程序
2.寻找js加密的代码
我们先点击发起程序的第一个进入到js文件中找到请求发出的地方也就是
send()函数
先加个断点然后刷新页面(下图)
刷新后我们就可以看到调用栈然后从第一个往下点一直找到send()(上图)
找到send()后我们取消掉前面的断点并给send()打上断点然后再次刷新网页(上图)
这个url代表的是此次请求发送的url我们评论的url为https://music.163.com/weapi/comment/resource/comments/get所以我们点击上面的小三角放过此次请求直至抓取到我们所需的请求(上图)
我们现在就抓取到了评论请求的data数据等信息我们可以看到此时的信息还是加密的所以我们要继续向前找寻找到它未加密的地方(上图)
data的加密数据在t0x这个调用栈前面是没有的所以我们就能确定数据是在这个调用栈中被加密然后我们ctrl+ F 直接搜索params就定位到了加密的函数
四.扣取js的加密源码
var bVi6c = window.asrsea(JSON.stringify(i0x), bse6Y(["流泪", "强"]), bse6Y(Qu1x.md), bse6Y(["爱心", "女孩", "惊恐", "大笑"]));
e0x.data = j0x.cr1x({
params: bVi6c.encText,
encSecKey: bVi6c.encSecKey
})
根据上一步我们找到的源码我们可以看出
我们所需的两个加密数据 params,encSecKey都是由 bVi6c产生的
bVi6c是由 这个函数生成的所以只需要取出这个函数的加密过程就能模拟出我们的加密数据
window.asrsea(JSON.stringify(i0x), bse6Y(["流泪", "强"]), bse6Y(Qu1x.md), bse6Y(["爱心", "女孩", "惊恐", "大笑"]));
1.加密函数参数分析
我们先给函数打上断点然后刷新网页
和上面的步骤一样还是需要点击三角知道找到我们所需的接口
①.JSON.stringify(i0x)
JSON.stringify()
JSON.stringify 是 JavaScript 中的一个全局函数,用于将一个 JavaScript 值(如 JavaScript 对象或数组)转换为 JSON 格式的字符串。这对于将数据保存到文件、在网页之间传递数据、或发送到服务器(例如通过 AJAX 请求)非常有用,因为 JSON 是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。
这个函数的作用就是将i0x转换为json格式
我们点击控制台然后输入i0x就可以得到i0x的值
{
"rid": "R_SO_4_28188263",
"threadId": "R_SO_4_28188263",
"pageNo": "1",
"pageSize": "20",
"cursor": "-1",
"offset": "0",
"orderType": "1",
"csrf_token": "71bfd80a0c48b6dbdef22c1499cd480c"
}
rid与threadId就是歌的id
csrf_token是用户的登录状态
剩下的就是关于评论的参数
使用JSON.stringify()函数将i0x转换后我们就得到了第一个参数
②bse6Y([“流泪”, “强”])
第二个参数我们可以看出他是一个函数的返回值他的参数是[“流泪”, “强”]所以我们认为他的值是固定的
我们只需要在控制台输入bse6Y([“流泪”, “强”])就可以得到第二个参数的值
bse6Y([“流泪”, “强”])=‘010001’
③bse6Y(Qu1x.md)
首先我们要获取Qu1x.md的值我们直接控制台输入Qu1x.md
['色', '流感', '这边', '弱', '嘴唇', '亲', '开心', '呲牙', '憨笑', '猫', '皱眉', '幽灵', '蛋糕', '发怒', '大哭', '兔子', '星星', '钟情', '牵手', '公鸡', '爱意', '禁止', '狗', '亲亲', '叉', '礼物', '晕', '呆', '生病', '钻石', '拜', '怒', '示爱', '汗', '小鸡', '痛苦', '撇嘴', '惶恐', '口罩', '吐舌', '心碎', '生气', '可爱', '鬼脸', '跳舞', '男孩', '奸笑', '猪', '圈', '便便', '外星', '圣诞']
所以bse6Y(Qu1x.md)值也是固定的
bse6Y(Qu1x.md)=‘00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7’
④.bse6Y([“爱心”, “女孩”, “惊恐”, “大笑”])
和参数二一样他也是一个固定值直接控制台输出
bse6Y(["爱心", "女孩", "惊恐", "大笑"])='0CoJUm6Qyw8W8jud'
1.加密函数分析
window.asrsea(JSON.stringify(i0x), bse6Y(["流泪", "强"]), bse6Y(Qu1x.md), bse6Y(["爱心", "女孩", "惊恐", "大笑"]))
我们已经得到了window.asrsea()的参数现在只有找到此函数的源码就可以模拟加密了
我们直接ctrl+F搜索window.asrsea
我们直接把这个函数复制下来
!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,
}();
这就是加密函数
五.在本地环境使用此函数
1.直接将js代码复制并用js环境运行
python如何执行js代码: https://blog.csdn.net/python_9k/article/details/138562809
Node.js安装与配置(详细步骤): https://blog.csdn.net/qq_42006801/article/details/124830995
2.将js代码翻译为python代码
如果读不懂js代码直接复制给ai来使用
这段JavaScript代码定义了一系列函数,用于生成随机字符串、使用AES加密、使用RSA加密,以及组合这些加密步骤来形成最终的加密数据。下面是对这些函数及其作用的详细解释:
1. a(a)
• 功能:生成一个长度为a的随机字符串,字符串由字母(大小写)和数字组成。
• 实现:使用Math.random和Math.floor生成随机索引,从字符集b中选取字符,最终拼接成一个随机字符串。
2. b(a, b)
• 功能:使用AES算法加密字符串a,密钥是b。
• 实现:使用CryptoJS库中的AES加密方法,采用CBC模式,IV(初始化向量)固定为0102030405060708,加密后返回加密字符串的Base64编码。
3. c(a, b, c)
• 功能:使用RSA算法加密字符串a,公钥由b和c组成(b是公钥的指数,c是模数)。
• 实现:使用RSAKeyPair创建一个RSA公钥,然后使用encryptedString方法加密字符串a。
4. d(d, e, f, g)
• 功能:组合AES和RSA加密,首先使用AES加密d,然后对加密后的AES密钥再进行一次AES加密,并使用RSA加密生成的随机字符串i。
• 实现:
• 生成16位随机字符串i。
• 使用g作为AES密钥加密d。
• 使用i作为新的AES密钥再次加密前一步的输出。
• 使用RSA公钥(e和f)加密i。
• 返回一个对象,包含encText(两次AES加密后的结果)和encSecKey(RSA加密后的结果)。
5. e(a, b, d, e)
• 功能:使用RSA算法加密字符串a加上e,公钥由b和d组成。
• 实现:与c函数相似,但是将a + e作为要加密的字符串。
6. window.asrsea = d
• 功能:将d函数绑定到window对象的asrsea属性上,使其在全局范围内可用。
加密步骤总结
• 首先,生成一个随机字符串i。
• 使用g作为AES密钥加密原始数据d。
• 再次使用i作为AES密钥加密上一步的结果。
• 最后,使用RSA公钥加密i,生成encSecKey。
• 返回一个包含encText(两次AES加密的结果)和encSecKey(RSA加密的结果)的对象。
这种加密策略通常用于网络请求中,尤其是音乐流媒体服务或API接口,以保护数据在传输过程中的安全。
六.python代码
main.py
from Crypto.Cipher import AES
import base64
from Crypto.Util.Padding import pad
import requests
from jiami import Aj
import re
def gdata(decrypt_str):
key_str = '0CoJUm6Qyw8W8jud'
iv_str = "0102030405060708"
a = "ouPO61mhRSoLJEwz"
encrypt_str = aes_cbc_encrypt_text(decrypt_str, key_str, iv_str)
encrypt_str = aes_cbc_encrypt_text(encrypt_str, a, iv_str)
return encrypt_str
def idh():
name = input('请输入歌名: ')
i1x = {
'csrf_token': "",
'limit': "8",
's': name
}
data = i1x
aj = Aj(data)
url = 'https://music.163.com/weapi/search/suggest/web'
res = requests.post(url, data={'params': aj.getparams(data), 'encSecKey': aj.getenseckey()})
return str(res.json()["result"]["songs"][0]["id"])
def aes_cbc_encrypt_text(decrypt_text: str, key: str, iv: str) -> str:
"""
加密AES_CBC的明文
:param decrypt_text: 明文
:param key: 密钥
:param iv: 密钥偏移量
:return: 密文
"""
aes2 = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
encrypt_text = aes2.encrypt(pad(decrypt_text.encode('utf-8'), AES.block_size, style='pkcs7'))
encrypt_text = str(base64.encodebytes(encrypt_text), encoding='utf-8').replace("\n", "")
return encrypt_text
idd=idh()
rid='R_SO_4_'+idd
# AES_CBC加密模式
decrypt_str = "{\"ids\":\"["+str(idd)+"]\",\"level\":\"standard\",\"encodeType\":\"aac\"}"
decrypt_str2='{\"rid\": \"%s\",\"threadId\": \"%s\",\"pageNo\": \"1\",\"pageSize\": \"20\",\"cursor\": \"-1\",\"offset\": \"0\",\"orderType\": \"1\"}'%(rid,rid)
decrypt_str3='{\"id\":\"%s\",\"lv\": \"-1\",\"tv\":\"-1\"}'%idd
params=gdata(decrypt_str)
params2=gdata(decrypt_str2)
params3=gdata(decrypt_str3)
encSecKey="c13dc213bd5889986ef8a7af39406c640bf77c6a5a34fe86324b648708b3af49919368a2d555771ea5453a605be7bfeaca0860696a9a9889e24a925bfeb56455f7275e534e733eaa5f94392d7b75da0701b8f38a3006bd6cd10cce66d64daa39f6dd12f970421d79b91abc8116012ee4f2dc8c7648c527abc22158b0338a9171"
url='https://music.163.com/weapi/song/enhance/player/url/v1?'
url2='https://music.163.com/weapi/comment/resource/comments/get?'
url3='https://music.163.com/weapi/song/lyric'
data={
'params': params,
'encSecKey': encSecKey
}
data2={
'params': params2,
'encSecKey': encSecKey
}
data3={
'params': params3,
'encSecKey': encSecKey
}
res=requests.post(url,data=data)
res2=requests.post(url2,data=data2)
res3=requests.post(url3,data=data3)
print("评论:")
for i in res2.json()["data"]["comments"]:
print(i["content"])
#print(res.text)
print('___________________________________________________________________')
print("歌曲url:")
print(res.json()['data'][0]['url'])
print('___________________________________________________________________')
print("歌词:")
txt=res3.json()["lrc"]
list=re.findall(r'\[(.*?)\](.*?)\\n',str(txt),re.S)
for i in list:
print(i[1])
jiami.py
from Crypto.Cipher import AES
from base64 import b64encode
import json
class Aj:
g='0CoJUm6Qyw8W8jud'
i="7HCEonr7xwebMhJV"
def __init__(self,data):
self.data=data
def getenseckey(self):
return "774c6922c77e2d39c9ec18d91210752d86517dee1ab731b3f8a41306ef5b92bc5809105f6cd679375d557dbf59746e1ace6b5a8a313efd70d3c645d198e3e509dbb61776926032eaccfa92c6f5921c8672baf3a2fd5d6d08c1a6869200fbb5e3191902026144060d6e58afcd9ef95f8be592fa80a25791c5b51a1264905026cf"
def jiamiAES(self,data, key):
# data=data.encode("utf-8")
iv = '0102030405060708'
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")
def getparams(self,data):
data = json.dumps(data)
data = self.to_16(data)
t1 = self.jiamiAES(data,self.g)
t2 = self.jiamiAES(self.to_16(t1),self.i)
return t2
def to_16(self,data):
pad = 16 - len(data) % 16
data += chr(pad) * pad
return data