一键获取网易云歌单文本(爬取网易云音乐歌曲对应id并剔除无版权歌曲)
目录1. 在https://music.163.com/search中搜索林俊杰可得到下图结果:,下面我们就来说一说关于一键获取网易云歌单文本?我们一起去了解并探讨一下这个问题吧!
一键获取网易云歌单文本
目录
1. 在https://music.163.com/search中搜索林俊杰可得到下图结果:
2. 我们查看源代码,并对照浏览器渲染后的代码,可以发现歌曲是后续加载的:
3. 查看XHR与JS文件,确定数据包来源:
4. 找到构造请求的方法:
5. 找到d函数的参数:
6. 构造请求:
7. 剔除无版权歌曲
8. 运行测试
我们的如果想要播放网易云音乐上的音频,只需拿到其mp3的url即可,对应的url格式如下:
http://music.163.com/api/playlist/detail?id=???
其中,id为音乐对应编号,如林俊杰的Always Online的对应id为108485。
故若要根据用户的要求播放指定的音频,就要找到指定音频的对应id。
由此,我们的分析步骤如下:
1. 在https://music.163.com/search中搜索林俊杰可得到下图结果:
2. 我们查看源代码,并对照浏览器渲染后的代码,可以发现歌曲是后续加载的:
浏览器渲染代码:
网页源代码:
3. 查看XHR与JS文件,确定数据包来源:
最终来源为https://music.163.com/weapi/cloudsearch/get/web?csrf_token=,该请求的返回信息包含搜索到的歌曲与id的对应关系:
通过查看该url的请求头部,可知其请求方式为POST,且有特定的请求参数params与encSecKey:
故而若我们要请求到目标数据,需求构造特定的请求头与请求参数。
4. 找到构造请求的方法:
由于该请求是通过AJAX技术发送的,即是由客户机发送的,故而构造的方法一定在服务器发送回的某个脚本的函数中,通过查找关键字SecKey,我们找到了位于https://s3.music.126.net/web/s/core_c24658b266780ad771d7dff4e097e475.js?c24658b266780ad771d7dff4e097e475中(返回的js文件中的第一个或其他core开头的js文件)的一段生成h.encSecKey的方法:
首先可知该段是一段立即执行函数,格式化该段代码后可知该段代码主要有四个函数(函数中调用的有关密码学的函数均在同一js文件中有所定义):
a. 函数a的作用即生成指定字节的随机数:
b. 函数b的作用为AES加密:
c. 函数c的作用为RSA加密:
d. 函数d为主要逻辑控制函数,生成params与encSecKey:
除以上函数之外,该立即执行函数中还设置了两个参数分别指向函数d与函数e:
总的而言,由以上四个函数,尤其是函数d,我们可以总结出网易云音乐生成params与encSecKey的主要逻辑以及服务器解析的主要逻辑如下图所示(d为要加密的文本,AES的初始密钥为g,f为RSA的大模数,e为RSA的公钥,i为生成的随机数):
故而如果我们需要知道上述d函数的参数,才能得到特定的请求参数。
5. 找到d函数的参数:
由于该段是一个立即执行的函数,那么我们可以通过调试该js文件即可找到d函数的参数,故而我们直接监控该文件,在第88行下断点,查看其调用栈:
第一次直接运行,可以看到运行到88行时该d函数已经调用了a函数,而d函数是由v8n.bl9c调用,我们点击d函数,查看下面的调用信息:
由此,我们便拿到了第一次运行到d函数的4个调用参数。
我们点击继续运行,发现网页还没有显示歌单,且会第二次运行到88行,此时d函数被第二次调用:
可以看到e、f、g三个参数均没有发生改变,而d发生了改变;
我们点击继续运行,发现网页依然没有显示歌单,且会第三次运行到88行,此时d函数被第三次调用:
嗯,歌单依然没有出来,但我们现在基本可以确认e、f、g三个参数是固定的,我们继续运行,直到第七次调用:
可以看到,d参数中出现了不一样的数据,我们查找的内容即林俊杰,对应参数中的keyword,我们继续运行,但歌单依然没有刷新……所以继续运行,到了第八次调用:
好,这一次依然是有关键信息的,如id、s与type,均与我们的查找参数对应:
故而我们单步调试该次调用,记录该次生成的params与encSecKey:
点击继续运行后,我们可以在Network中可以看到其对目标发出了请求:
但是歌单信息还是没有请求到,继续运行:
再次继续运行,发现歌单终于出来了,且请求参数与先前不同:
故我们可以确定的是整个调用过程共请求了目标
https://music.163.com/weapi/cloudsearch/get/web?csrf_token=
两次,且两次参数并不相同,故而后续我们以上述两次请求的参数来构造请求,以验证能否请求得到信息。
但是,在实际测试中,只有第一次构造的参数才能够请求到对应的数据。
6. 构造请求:
如以上分析,我们只需构造类似于a,b,c,d四个函数的相应操作,即按照此前分析的基本请求头构造流程来构造请求即可:
a. 生成随机数,即相当于a函数,由于我们的调试中可以知道a函数的调用参数为16,故而我们直接默认生成16字节的随机数即可:
b. AES加密,相当于b函数,其中需要对text与key进行padding,padding的规则在原JS代码中的parse代码中定义:
c. RSA加密,相当于c函数:
d. 主逻辑控制函数,相当于d函数:
在这些函数的基础上,我们还需要构造请求头,初始化参数信息等,在此不在赘述。
7. 剔除无版权歌曲
此外,我们还需要判断网易云音乐中的版权问题,如一些歌曲需要vip权限或者网易云音乐没有响应的版权,整个过程相对比较复杂枯燥,不再赘述,基本思路就是单步调试找到js渲染的方法(如无版权是灰色而不是黑色):
我们也并没有完全将其解析方式进行完全剖析,由于原js代码经过了混淆,可读性较差,我们粗略分析判断如下:
随后在收到的数据包进行一层过滤即可:
感兴趣的朋友可以自行检测,可以在原js代码中(与上述同一个)以fee为关键词找到相应的处理过程,或以compareFee为关键字搜索也可以,处理的基本过程就在该函数的后面的一段代码中。
8. 运行测试
python版本:3.6.4
平台:windows 10
依赖库:pycrptodome
具体代码如下:
import requests
import random
import base64
from Crypto.Cipher import AES
import json
import binascii
class Music_api():
# 设置从JS文件提取的RSA的模数、协商的AES对称密钥、RSA的公钥等重要信息
def __init__(self):
self.modulus = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
self.nonce = '0CoJUm6Qyw8W8jud'
self.pubKey = '010001'
self.url = "https://music.163.com/weapi/cloudsearch/get/web?csrf_token="
self.HEADER = {}
self.setHeader()
self.secKey = self.getRandom()
# 生成16字节即256位的随机数
def getRandom(self):
string = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
res = ""
for i in range(16):
res = string[int(random.random()*62)]
return res
# AES加密,用seckey对text加密
def aesEncrypt(self, text, secKey):
pad = 16 - len(text) % 16
text = text pad * chr(pad)
encryptor = AES.new(secKey.encode('utf-8'), 2, '0102030405060708'.encode('utf-8'))
ciphertext = encryptor.encrypt(text.encode('utf-8'))
ciphertext = base64.b64encode(ciphertext).decode("utf-8")
return ciphertext
# 快速模幂运算,求 x^y mod mo
def quickpow(self, x, y, mo):
res = 1
while y:
if y & 1:
res = res * x % mo
y = y // 2
x = x * x % mo
return res
# rsa加密
def rsaEncrypt(self, text, pubKey, modulus):
text = text[::-1]
a = int(binascii.hexlify(str.encode(text)), 16)
b = int(pubKey, 16)
c = int(modulus, 16)
rs = self.quickpow(a, b, c)
return format(rs, 'x').zfill(256)
# 设置请求头
def setHeader(self):
self.HEADER = {
'Accept': '*/*',
'Accept-Encoding': 'gzip,deflate,sdch',
'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Host': 'music.163.com',
'Referer': 'https://music.163.com/search/',
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36'
}
# 设置相应的请求参数,从而搜索列表
# 总体的密码加密步骤为:
# 首先用nonce对text加密生成密文1
# 然后用随机数seckey加密密文1生成密文2
# 随后,用公钥加密seckey生成密文3
# 其中,密文2作为请求参数中的params,密文3作为encSeckey字段
# 这样,接收方可以通过私钥解密密文3获得seckey(随机数)
# 然后用seckey解密密文2获得密文1
# 最终用统一协商的密钥nonce解密密文1最终获得text
def search(self, s,offset,type="1"):
text = {"hlpretag": "<span class=\"s-fc7\">",
"hlposttag": "</span>",
"#/discover": "",
"s": s,
"type": type,
"offset": offset,
"total": "true",
"limit": "30",
"csrf_token": ""}
text = json.dumps(text)
params = self.aesEncrypt(self.aesEncrypt(text,self.nonce),self.secKey)
encSecKey = self.rsaEncrypt(self.secKey,self.pubKey,self.modulus)
data = {
'params': params,
'encSecKey': encSecKey
}
result = requests.post(url=self.url,
data=data,
headers = self.HEADER).json()
return result
# 获取指定音乐列表(相当于主函数)
def get_music_list(self, keywords):
music_list = []
for offset in range(1):
result = Music_api().search(keywords, str(offset))
result = result['result']['songs']
for music in result:
# if music['copyright'] == 1 and music['fee'] == 8:
if (music['privilege']['fee'] == 0 or music['privilege']['payed']) and music['privilege']['pl'] > 0 and music['privilege']['dl'] == 0:
continue
if music['privilege']['dl'] == 0 and music['privilege']['pl'] == 0:
continue
# if music['fee'] == 8:
music_list.append(music)
return music_list
print(Music_api().get_music_list("像我这样的人"))#测试
测试结果:
[{'name': '像我这样的人', 'id': 569213220, 'pst': 0, 't': 0, 'ar': [{'id': 12138269, 'na
————————————————
版权声明:本文为CSDN博主「如梦如幻似清茶」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36779888/article/details/90738012
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com