一键获取网易云歌单文本(爬取网易云音乐歌曲对应id并剔除无版权歌曲)

目录1. 在https://music.163.com/search中搜索林俊杰可得到下图结果:,下面我们就来说一说关于一键获取网易云歌单文本?我们一起去了解并探讨一下这个问题吧!

一键获取网易云歌单文本(爬取网易云音乐歌曲对应id并剔除无版权歌曲)

一键获取网易云歌单文本

目录

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

    分享
    投诉
    首页