前言

永远相信美好的事情即将发生


背景

一直想做一个在线的音乐播放器,这个想法最早可以追溯到做毕设的那会,那时候做了个在线的商城系统, 里面有个在线听歌的模块,其实就是调用大佬们封装好的API进行搜索和播放。当时一直想着自己去找接口进行封装,但奈何一直没有时间(其实就是惰性),这段时间终于不怎么忙了,于是决定完成这个拖延了一年的 “需求”


准备

开发环境:Python 3.8 64位
开发工具:Pycharm
浏览器:Chrome


历程

接口获取

首先我们需要在浏览器中打开网易云音乐,别问我为什么要使用网易云


打开之后随便搜索一首歌曲,然后 F12 打开开发者模式


依次对这几条请求进行分析,其中有一条 web?csrf_token= 响应的数据比较可疑,对数据进行json格式化后如图


凭借我小学三年级的英语水平来看,基本就可以断定这条请求就是歌曲搜索的请求。然后

JS分析

接下来我们对请求的数据进行分析,点开 Headers 后我们可以发现,除了传统的请求头参数外,本次请求还携带了一个 Form 表单,其中有两个参数,分别是 paramsencSecKey


经过对其他参数携带的信息分析后我们可以发现,这两个参数就是进行搜索的关键参数,于是我们对 encSecKey 参数进行如下搜索


双击第一个js,进行格式化后使用 Ctrl+F 进行搜索,如下图所示


不难发现, 这两个参数都是通过一个名为 bVZ7S(asrsea) 的函数获取到的,那我们现在就对这个 asrsea 进行搜索

搜索如图,可以看出 asrsea 函数其实就是一个名为 d 的函数

现在对这个 d 函数进行分析,首先它执行了一次 a 函数,然后又连续执行了两次 b 函数,最后执行了一次 c 函数,然后拿到一个最终的结果

现在我们依次对这几个函数进行解析:

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}

a 函数的逻辑还是比较简单的,就是从大小写英文字母以及10个数字中随机抽取16个字符拼接成一个新的字符串返回结果

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()}

b 函数的逻辑还是稍微复杂,通过对关键词进行分析我们可以判断是通过 CBC 模式进行 AES 加密,将传入的 a 参数和 b 参数分别作为需要加密的内容和密钥,iv偏移量为一个固定的字符串 0102030405060708,完成加密

function c(a, b, c) {var d, e;return setMaxDigits(131),d = new RSAKeyPair(b,"",c),e = encryptedString(d, a)}

c 函数算是个大坑了,当时看到里面有个 RSA 字样,就自然而然的以为是一个RSA加密,等到找公钥私钥的时候才发现不对劲,这咋啥都没有啊,于是苦苦搜索js文件,发现就是一连串的字符串操作,和 RSA 没一点关系,代码部分参考了大佬的写法,大家可以研究一下js


对主函数 d 的分析就算结束了,接下来我们再返回js文件对传入的参数进行分析

首先,我们在网易云音乐的搜索框中输入需要搜索的歌曲名,然后在js中 d 函数处打上断点,多打几个

可以看到,d 函数分别传入了以下参数:

d:{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"Lily","type":"1","offset":"0","total":"true","limit":"30","csrf_token":""}
e:"010001"
f:"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g:"0CoJUm6Qyw8W8jud"

通过多首歌曲的测试我们发现,参数 e,f,g 始终保持不表,由此可见它们是3个常量,只是参数 d 中的歌曲名称在变化

接下来我们在测试一下歌曲播放时的请求参数

依旧对传入的几个参数进行获取

d:{"ids":"[1333159921]","level":"standard","encodeType":"aac","csrf_token":""}
e:"010001"
f:"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g:"0CoJUm6Qyw8W8jud"

可以看出,播放歌曲其实和搜索歌曲用的是同一个方法,只是传入的参数不同,准确点说只是传入的d参数不同,一个传的是歌曲的名称,而另一个则是歌曲的id

OK,到此我们就算完成了对网易云音乐Web版整个逻辑的梳理,接下来就是用Python代码去实现

Python模拟

#!D:/Code/python
# -*- coding: utf-8 -*-
# @Time : 2020/8/22 12:32
# @Author : Am0xil
# @Description : 网易云音乐模拟
import base64
import binascii
import json
import random
import string
from urllib import parseimport requests
from Crypto.Cipher import AES# 从a-z,A-Z,0-9中随机获取16位字符
def get_random():random_str = ''.join(random.sample(string.ascii_letters + string.digits, 16))return random_str# AES加密要求加密的文本长度必须是16的倍数,密钥的长度固定只能为16,24或32位,因此我们采取统一转换为16位的方法
def len_change(text):pad = 16 - len(text) % 16text = text + pad * chr(pad)text = text.encode("utf-8")return text# AES加密方法
def aes(text, key):# 首先对加密的内容进行位数补全,然后使用 CBC 模式进行加密iv = b'0102030405060708'text = len_change(text)cipher = AES.new(key.encode(), AES.MODE_CBC, iv)encrypted = cipher.encrypt(text)encrypt = base64.b64encode(encrypted).decode()return encrypt# js中的 b 函数,调用两次 AES 加密
# text 为需要加密的文本, str 为生成的16位随机数
def b(text, str):first_data = aes(text, '0CoJUm6Qyw8W8jud')second_data = aes(first_data, str)return second_data# 这就是那个巨坑的 c 函数
def c(text):e = '010001'f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'text = text[::-1]result = pow(int(binascii.hexlify(text.encode()), 16), int(e, 16), int(f, 16))return format(result, 'x').zfill(131)# 获取最终的参数 params 和 encSecKey 的方法
def get_final_param(text, str):params = b(text, str)encSecKey = c(str)return {'params': params, 'encSecKey': encSecKey}# 通过参数获取搜索歌曲的列表
def get_music_list(params, encSecKey):url = "https://music.163.com/weapi/cloudsearch/get/web?csrf_token="payload = 'params=' + parse.quote(params) + '&encSecKey=' + parse.quote(encSecKey)headers = {'authority': 'music.163.com','user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36','content-type': 'application/x-www-form-urlencoded','accept': '*/*','origin': 'https://music.163.com','sec-fetch-site': 'same-origin','sec-fetch-mode': 'cors','sec-fetch-dest': 'empty','referer': 'https://music.163.com/search/','accept-language': 'zh-CN,zh;q=0.9',}response = requests.request("POST", url, headers=headers, data=payload)return response.text# 通过歌曲的id获取播放链接
def get_reply(params, encSecKey):url = "https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token="payload = 'params=' + parse.quote(params) + '&encSecKey=' + parse.quote(encSecKey)headers = {'authority': 'music.163.com','user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36','content-type': 'application/x-www-form-urlencoded','accept': '*/*','origin': 'https://music.163.com','sec-fetch-site': 'same-origin','sec-fetch-mode': 'cors','sec-fetch-dest': 'empty','referer': 'https://music.163.com/','accept-language': 'zh-CN,zh;q=0.9'}response = requests.request("POST", url, headers=headers, data=payload)return response.textif __name__ == '__main__':song_name = input('请输入歌曲名称,按回车键搜索:')d = {"hlpretag": "<span class=\"s-fc7\">", "hlposttag": "</span>", "s": song_name, "type": "1", "offset": "0","total": "true", "limit": "30", "csrf_token": ""}d = json.dumps(d)random_param = get_random()param = get_final_param(d, random_param)song_list = get_music_list(param['params'], param['encSecKey'])print('搜索结果如下:')if len(song_list) > 0:song_list = json.loads(song_list)['result']['songs']for i, item in enumerate(song_list):item = json.dumps(item)print(str(i) + ":" + json.loads(str(item))['name'])d = {"ids": "[" + str(json.loads(str(item))['id']) + "]", "level": "standard", "encodeType": "","csrf_token": ""}d = json.dumps(d)param = get_final_param(d, random_param)song_info = get_reply(param['params'], param['encSecKey'])if len(song_info) > 0:song_info = json.loads(song_info)song_url = json.dumps(song_info['data'][0]['url'], ensure_ascii=False)print(song_url)else:print("该首歌曲解析失败,可能是因为歌曲格式问题")else:print("很抱歉,未能搜索到相关歌曲信息")

手机端经常出现代码被吞的情况,各位看官可以移步我的 GitHub , 不胜荣幸

感谢大佬 @一只不会爬的虫子 的指点,最开始的 AES 填充方法有点问题,导致部分歌曲搜索不到,经修改后正常,如果后期有什么新的问题欢迎各位留言

测试

接下来我们启动程序进行测试,测试结果如下:

可以看到,已经获取到歌曲 酷爱 的部分搜索结果,我们随机复制一条播放链接到浏览器(记得删掉前后的双引号)进行测试,结果如图

OK,大功告成

其实最开始是做成直接播放的模式,但Python好像只支持播放本地文件,不支持URL的形式,当然还有可能是因为我太菜了,如果谁有好的方案也欢迎各位大佬赐教,万分感谢


总结

爬取过程中碰到过如下几个比较耗费时间的问题,特此记录

1.进行AES加密时需要将加密内容填充满16位,尽量不要使用其他字符,否则会生成无效的params和encSecKey参数,使用该参数请求接口将返回空数据;
2.在访问接口时,切记对params和encSecKey参数进行URL编码,将其中的 “=” 转换为 “%3D” ,否则返回结果也将为空;
3.请求歌曲播放地址时目前只能请求到MP3格式,M4A格式的歌曲暂时无法获取,应该是参数设置的问题,后面有时间在解决,当然也欢迎各位大佬指点一二
4.搜索歌曲时偶尔会存在搜索失败的情况,重新搜索后又正常,暂时未发现是什么Bug,后面有时间的话再改吧

『Python』网易云音乐API爬虫(音乐搜索音乐播放)相关推荐

  1. Python 将网易云歌单迁移到QQ音乐

    最近,网易云音乐越来越多的歌曲变灰了,许多我以前喜欢听的音乐都消失在云端,非常可惜.最近我还是想听回以前的歌曲,于是只能使用有那些歌曲版权的QQ音乐,麻烦的是,QQ音乐没有我在网易云音乐的歌单,两个软 ...

  2. Python 学习笔记 第三篇 Python实现网易云评论网页爬虫+词云展示 (Pycharm+Mysql)

    初始条件,具体可见我的其他文章. 1.安装Python.Python 学习笔记 第一篇 Python的安装与配置 2.安装Pycharm,并导入第三方包.Python 学习笔记 第二篇 Python ...

  3. Python的网易云音乐数据分析系统 爬虫 echarts可视化 Flask框架 音乐推荐系统 源码下载

    Python的网易云音乐数据分析系统 爬虫 echarts可视化 Flask框架 音乐推荐系统 一.技术说明 网易云音乐数据(歌单.用户.歌词.评论)Python爬取Flask框架搭建ECharts. ...

  4. Python爬虫实战,matplotlib模块,Python实现网易云音乐歌单数据可视化

    前言 利用Python实现网易云音乐歌单数据可视化.废话不多说. 让我们愉快地开始吧~ 开发工具 Python版本: 3.6.4 相关模块: requests模块 pandas模块 matplotli ...

  5. 破解网易云js加密,爬虫获取网易云评论

    破解网易云js加密,爬虫获取网易云评论 抓包 这里是对网页版的网易云音乐进行抓包,分析网络请求,url https://music.163.com/#/song?id=36229055 然后可以发现 ...

  6. python爬取音乐_利用Python对网易云音乐进行爬取!无所不爬的爬虫啊!

    今天,我们利用Python 中的selenium爬取网易云音乐中林俊杰<将故事写成我们>的评论,具体界面如下: 私信小编01 获取数十套PDF!爬虫 web都有的呢! 具体代码如下: #此 ...

  7. 爬虫python代码网易云_python爬取网易云音乐热歌榜实例代码

    首先找到要下载的歌曲排行榜的链接,这里用的是: https://music.163.com/discover/toplist?id=3778678 然后更改你要保存的目录,目录要先建立好文件夹,例如我 ...

  8. Python Pyside2 Pinger新手QQ音乐网易云酷狗爬虫下载

    此文记录了Pinger优化2.0版本,仅此作为学习Python的动力: Pinger 1.0请看前一篇的博客! Pyside2新手1400行代码QQ音乐网易云酷狗轻松下载 优化一: 更改Pinger的 ...

  9. python爬网易云_如何用爬虫获取网易云音乐歌单中的歌曲?

    --------------------------------- 泻药,以我抓取了307835首网易云音乐的歌单歌曲的经验,讲一下这个问题. 喜欢用Github的可以直接看我的项目源码,代码简单.具 ...

最新文章

  1. php5.3源码,php5.3介绍
  2. Unix 文件系统读写时权限校验
  3. XML和JSON的比较
  4. spring mvc 伪静态处理
  5. 基于mysql的全文索引
  6. ATP-EMTP电缆LCC模型中相数与电缆数的设置
  7. c语言常见头文件大全,C语言头文件大全
  8. 家政服务微信小程序开发
  9. 基于产生式系统方法实现动物识别系统
  10. ArcGIS多种面积计算方法的区别
  11. 淘宝联盟官方APi在小程序云函数中的使用教程(附案例)
  12. html5页面设计技术,H5页面设计技巧有哪些?-鱼爪网
  13. 讯飞 AIUI 集成
  14. 变身成为互联网设计师
  15. 豪能转债上市价格预测
  16. 四时之气,汇聚英豪胆略
  17. settimeout一定要清除么?
  18. 功放前级的左右_都是功放,前级和后级差别甚大!千万别买错了
  19. 1万块钱存入银行,10年后,连本带利能取多少钱?
  20. 王者荣耀小游戏3.0更新完成!

热门文章

  1. 工业4.0大背景下的自动套料软件
  2. android开发第一次心的
  3. 第六届互联网大会大佬都说点啥?5G、AI干货满满!
  4. 99Report报表工具软件
  5. android app初始化sdk,Android SDK使用系列教程——2.SDK初始化和常用类介绍
  6. ZTE/中兴U807N(NFC) root教程_方法
  7. html怎么快速收录,如何让百度快速收录网站或文章?
  8. 360杀毒软件下载2015官方版最新版
  9. 宇视网络视频录像机重要录像如何保存
  10. 实验2 用MATLAB 绘制二维、三维图形