【7】实战:爬取网易云音乐歌曲对应id并剔除无版权歌曲 您所在的位置:网站首页 网易云音乐的id在哪 【7】实战:爬取网易云音乐歌曲对应id并剔除无版权歌曲

【7】实战:爬取网易云音乐歌曲对应id并剔除无版权歌曲

2024-07-13 07:46| 来源: 网络整理| 查看: 265

目录

1. 在https://music.163.com/search中搜索林俊杰可得到下图结果:

2. 我们查看源代码,并对照浏览器渲染后的代码,可以发现歌曲是后续加载的:

3. 查看XHR与JS文件,确定数据包来源:

4. 找到构造请求的方法:

5. 找到d函数的参数:

6. 构造请求:

7. 剔除无版权歌曲

8. 运行测试

我们的如果想要播放网易云音乐上的音频,只需拿到其mp3的url即可,对应外链的url格式如下:(完整格式可从网上搜索,后缀+ ‘.mp3‘)

*/song/media/outer/url?id=

其中,id为音乐列表对应编号。

故若要根据用户的要求播放指定的音频,就要找到指定音频的对应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": "", "hlposttag": "", "#/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, 'name': '毛不易', 'tns': [], 'alias': []}], 'alia': [], 'pop': 100.0, 'st': 0, 'rt': None, 'fee': 8, 'v': 97, 'crbt': None, 'cf': '', 'al': {'id': 39483040, 'name': '平凡的一天', 'picUrl': 'http://p2.music.126.net/vmCcDvD1H04e9gm97xsCqg==/109951163350929740.jpg', 'tns': [], 'pic_str': '109951163350929740', 'pic': 109951163350929740}, 'dt': 207466, 'h': {'br': 320000, 'fid': 0, 'size': 8301758, 'vd': -2.0}, 'm': {'br': 192000, 'fid': 0, 'size': 4981072, 'vd': -2.0}, 'l': {'br': 128000, 'fid': 0, 'size': 3320729, 'vd': -2.0}, 'a': None, 'cd': '2', 'no': 1, 'rtUrl': None, 'ftype': 0, 'rtUrls': [], 'djId': 0, 'copyright': 2, 's_id': 0, 'rtype': 0, 'rurl': None, 'mst': 9, 'cp': 755014, 'mv': 5959041, 'publishTime': 1530547200007, 'privilege': {'id': 569213220, 'fee': 8, 'payed': 0, 'st': 0, 'pl': 128000, 'dl': 0, 'sp': 7, 'cp': 1, 'subp': 1, 'cs': False, 'maxbr': 999000, 'fl': 128000, 'toast': False, 'flag': 68}}, {'name': '像我这样的人 (伴奏)', 'id': 569212215, 'pst': 0, 't': 0, 'ar': [{'id': 12138269, 'name': '毛不易', 'tns': [], 'alias': []}], 'alia': [], 'pop': 90.0, 'st': 0, 'rt': None, 'fee': 8, 'v': 93, 'crbt': None, 'cf': '', 'al': {'id': 39483040, 'name': '平凡的一天', 'picUrl': 'http://p2.music.126.net/vmCcDvD1H04e9gm97xsCqg==/109951163350929740.jpg', 'tns': [], 'pic_str': '109951163350929740', 'pic': 109951163350929740}, 'dt': 211818, 'h': {'br': 320000, 'fid': 0, 'size': 8475211, 'vd': 329.0}, 'm': {'br': 192000, 'fid': 0, 'size': 5085144, 'vd': 1069.0}, 'l': {'br': 128000, 'fid': 0, 'size': 3390111, 'vd': -2.0}, 'a': None, 'cd': '3', 'no': 9, 'rtUrl': None, 'ftype': 0, 'rtUrls': [], 'djId': 0, 'copyright': 2, 's_id': 0, 'rtype': 0, 'rurl': None, 'mst': 9, 'cp': 755014, 'mv': 0, 'publishTime': 1530547200007, 'privilege': {'id': 569212215, 'fee': 8, 'payed': 0, 'st': 0, 'pl': 128000, 'dl': 0, 'sp': 7, 'cp': 1, 'subp': 1, 'cs': False, 'maxbr': 999000, 'fl': 128000, 'toast': False, 'flag': 68}}, {'name': '나 같은 놈', 'id': 26087209, 'pst': 0, 't': 0, 'ar': [{'id': 236161, 'name': '100%', 'tns': [], 'alias': []}], 'alia': [], 'pop': 55.0, 'st': 0, 'rt': '', 'fee': 8, 'v': 28, 'crbt': None, 'cf': '', 'al': {'id': 2382015, 'name': '나 같은 놈', 'picUrl': 'http://p2.music.126.net/8qmCC45WMwx7McnWpcE6KQ==/6667438510932879.jpg', 'tns': [], 'pic': 6667438510932879}, 'dt': 205113, 'h': {'br': 320000, 'fid': 0, 'size': 8233530, 'vd': -3.23}, 'm': {'br': 160000, 'fid': 0, 'size': 4134874, 'vd': -2.81}, 'l': {'br': 96000, 'fid': 0, 'size': 2495011, 'vd': -2.87}, 'a': None, 'cd': '1', 'no': 1, 'rtUrl': None, 'ftype': 0, 'rtUrls': [], 'djId': 0, 'copyright': 1, 's_id': 0, 'rtype': 0, 'rurl': None, 'mst': 9, 'cp': 1410822, 'mv': 10800452, 'publishTime': 1354809600007, 'tns': ['像我这样的人'], 'privilege': {'id': 26087209, 'fee': 8, 'payed': 0, 'st': 0, 'pl': 128000, 'dl': 0, 'sp': 7, 'cp': 1, 'subp': 1, 'cs': False, 'maxbr': 320000, 'fl': 128000, 'toast': False, 'flag': 68}}, {'name': '像我这样的人 (伴奏) ', 'id': 1302084496, 'pst': 0, 't': 0, 'ar': [{'id': 12228050, 'name': 'Mc山迪', 'tns': [], 'alias': []}], 'alia': [], 'pop': 25.0, 'st': 0, 'rt': None, 'fee': 8, 'v': 5, 'crbt': None, 'cf': '', 'al': {'id': 72302966, 'name': '像我这样的人', 'picUrl': 'http://p2.music.126.net/dfMB1bIaESNgUck6effkMg==/109951163469679983.jpg', 'tns': [], 'pic_str': '109951163469679983', 'pic': 109951163469679983}, 'dt': 170631, 'h': {'br': 320000, 'fid': 0, 'size': 6827407, 'vd': 10643.0}, 'm': {'br': 192000, 'fid': 0, 'size': 4096462, 'vd': 11856.0}, 'l': {'br': 128000, 'fid': 0, 'size': 2730989, 'vd': 13092.0}, 'a': None, 'cd': '01', 'no': 0, 'rtUrl': None, 'ftype': 0, 'rtUrls': [], 'djId': 0, 'copyright': 0, 's_id': 0, 'rtype': 0, 'rurl': None, 'mst': 9, 'cp': 700012, 'mv': 0, 'publishTime': 1534694400007, 'privilege': {'id': 1302084496, 'fee': 8, 'payed': 0, 'st': 0, 'pl': 128000, 'dl': 0, 'sp': 7, 'cp': 1, 'subp': 1, 'cs': False, 'maxbr': 999000, 'fl': 128000, 'toast': False, 'flag': 256}}, {'name': '再见你个贱人(改编自《像我这样的人》)(Cover 毛不易)', 'id': 521753439, 'pst': 0, 't': 0, 'ar': [{'id': 4513, 'name': '馒头', 'tns': [], 'alias': []}], 'alia': [], 'pop': 25.0, 'st': 0, 'rt': None, 'fee': 8, 'v': 20, 'crbt': None, 'cf': '', 'al': {'id': 36867953, 'name': '再见你个贱人', 'picUrl': 'http://p2.music.126.net/nhtD8Wnke2AhR2EOuTmJKw==/109951163073850161.jpg', 'tns': [], 'pic_str': '109951163073850161', 'pic': 109951163073850161}, 'dt': 84071, 'h': {'br': 320000, 'fid': 0, 'size': 3367750, 'vd': -31500.0}, 'm': {'br': 192000, 'fid': 0, 'size': 2020667, 'vd': -28900.0}, 'l': {'br': 128000, 'fid': 0, 'size': 1347126, 'vd': -27200.0}, 'a': None, 'cd': '01', 'no': 1, 'rtUrl': None, 'ftype': 0, 'rtUrls': [], 'djId': 0, 'copyright': 0, 's_id': 0, 'rtype': 0, 'rurl': None, 'mst': 9, 'cp': 1415877, 'mv': 0, 'publishTime': 1512102811362, 'privilege': {'id': 521753439, 'fee': 8, 'payed': 0, 'st': 0, 'pl': 128000, 'dl': 0, 'sp': 7, 'cp': 1, 'subp': 1, 'cs': False, 'maxbr': 320000, 'fl': 128000, 'toast': False, 'flag': 2}}, {'name': '像我这样的人', 'id': 1300452511, 'pst': 0, 't': 0, 'ar': [{'id': 28390581, 'name': '帅帅宏', 'tns': [], 'alias': []}], 'alia': [], 'pop': 25.0, 'st': 0, 'rt': None, 'fee': 8, 'v': 3, 'crbt': None, 'cf': '', 'al': {'id': 72071459, 'name': '像我这样的人', 'picUrl': 'http://p2.music.126.net/3AZyp5Njcv3wjGBvULQFWQ==/109951163456667681.jpg', 'tns': [], 'pic_str': '109951163456667681', 'pic': 109951163456667681}, 'dt': 173871, 'h': {'br': 320000, 'fid': 0, 'size': 6956974, 'vd': 0.0}, 'm': {'br': 192000, 'fid': 0, 'size': 4174202, 'vd': 0.0}, 'l': {'br': 128000, 'fid': 0, 'size': 2782816, 'vd': 0.0}, 'a': None, 'cd': '', 'no': 0, 'rtUrl': None, 'ftype': 0, 'rtUrls': [], 'djId': 0, 'copyright': 0, 's_id': 0, 'rtype': 0, 'rurl': None, 'mst': 9, 'cp': 700012, 'mv': 0, 'publishTime': 1534089600007, 'privilege': {'id': 1300452511, 'fee': 8, 'payed': 0, 'st': 0, 'pl': 128000, 'dl': 0, 'sp': 7, 'cp': 1, 'subp': 1, 'cs': False, 'maxbr': 999000, 'fl': 128000, 'toast': False, 'flag': 256}}, {'name': '像我这样的人', 'id': 864255570, 'pst': 0, 't': 0, 'ar': [{'id': 27693855, 'name': '曾泽佑', 'tns': [], 'alias': []}], 'alia': [], 'pop': 25.0, 'st': 0, 'rt': None, 'fee': 8, 'v': 5, 'crbt': None, 'cf': '', 'al': {'id': 71720089, 'name': '像我这样的人', 'picUrl': 'http://p2.music.126.net/RA4S9Ry7sQ5b0jpLCoVWSg==/109951163416450553.jpg', 'tns': [], 'pic_str': '109951163416450553', 'pic': 109951163416450553}, 'dt': 170251, 'h': None, 'm': None, 'l': {'br': 128000, 'fid': 0, 'size': 2725137, 'vd': 1.0}, 'a': None, 'cd': '1', 'no': 1, 'rtUrl': None, 'ftype': 0, 'rtUrls': [], 'djId': 0, 'copyright': 0, 's_id': 0, 'rtype': 0, 'rurl': None, 'mst': 9, 'cp': 700012, 'mv': 0, 'publishTime': 1532016000007, 'privilege': {'id': 864255570, 'fee': 8, 'payed': 0, 'st': 0, 'pl': 128000, 'dl': 0, 'sp': 7, 'cp': 1, 'subp': 1, 'cs': False, 'maxbr': 128000, 'fl': 128000, 'toast': False, 'flag': 256}}, {'name': '像我这样的人', 'id': 863515299, 'pst': 0, 't': 0, 'ar': [{'id': 12321026, 'name': '余龙', 'tns': [], 'alias': []}], 'alia': [], 'pop': 5.0, 'st': 0, 'rt': None, 'fee': 8, 'v': 6, 'crbt': None, 'cf': '', 'al': {'id': 71723689, 'name': '像我这样的人', 'picUrl': 'http://p2.music.126.net/3Z_tvBVBq-BLErYkS73wwg==/109951163446696436.jpg', 'tns': [], 'pic_str': '109951163446696436', 'pic': 109951163446696436}, 'dt': 171781, 'h': {'br': 320000, 'fid': 0, 'size': 6873382, 'vd': -66400.0}, 'm': {'br': 192000, 'fid': 0, 'size': 4124047, 'vd': -63800.0}, 'l': {'br': 128000, 'fid': 0, 'size': 2749379, 'vd': -62100.0}, 'a': None, 'cd': '01', 'no': 0, 'rtUrl': None, 'ftype': 0, 'rtUrls': [], 'djId': 0, 'copyright': 0, 's_id': 0, 'rtype': 0, 'rurl': None, 'mst': 9, 'cp': 700012, 'mv': 0, 'publishTime': 1533571200007, 'privilege': {'id': 863515299, 'fee': 8, 'payed': 0, 'st': 0, 'pl': 128000, 'dl': 0, 'sp': 7, 'cp': 1, 'subp': 1, 'cs': False, 'maxbr': 999000, 'fl': 128000, 'toast': False, 'flag': 256}}]

 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有