手把手教你网易云音乐JS逆向
手把手教你网易云音乐JS逆向
1. 目标数据
- 1.搜索音乐,获取音乐id
- 2.获取url,下载音乐
2. 文章目录
目录
-
手把手教你网易云音乐JS逆向
- 1. 目标数据
- 2. 文章目录
- 3. 页面分析
- 4. 模拟加密,获取参数
- 5.获取url,下载音乐
- 6. 歌曲搜索
-
- 完美收工~~~
- [下一篇 打造网易云图形界面](https://blog.csdn.net/zly/article/details/)
3. 页面分析
import requests url = 'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=' headers = {
'origin': 'https://music.163.com', 'referer': 'https://music.163.com/', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36' } data = {
'params': '+i3P/nsviot5Ms6bDX+wUmyKAFRm7u3J7pdECwtbeVV10k41za7DjYIXSgCbNngYOO7GH+ADMz/pULLM6bItfAGGfjatL9xirPAWnwzGbr9uTul31ITnC9es8lu01n5eyP8svVHDCecGH57SU0u+hA==', 'encSecKey': '21c157c5cf0cecee8b518ee8726bba93e2dcb6e280d4d0e4c6ceea3107e5eaab6ad55ca53c713cd9893d5cf11491dae043bebccacc1c95680fff17ba34ccdfc3e3c4863eca69b671ef510a44e3f68d6fcd6e55b6d0fa320bfc82aa6a2adb641d60e7f480d5809f9c7e1963f884c9c5cf80cd81e5e08' } res = requests.post(url, headers=headers, data=data) print(res.json())

但是问题来了,params和encSeckey是啥玩意儿?这么长,一看就知道是加过密的,接下来要开启解密模式了
点击面板右上角的全局搜索,搜索encSeckey

有3个js文件,一个一个去找吧

先别着急找,像下面这样,结合起来,在第一个js文件中找到的可能性大一些,既然这样,那就开始找吧

小插曲:上面为什要搜encSeckey,而不搜params呢?因为用params命名的使用范围要比encSeckey广,所以用encSeckey搜好一些(经验)
点击第一个js,进去后点击格式化,搜索encSeckey,发现有3处,
滑到第二处,为什么是第二处呢?很简单,因为params和encSeckey在一起嘛,不在这你说在哪?好了,到这里,另外的两个js可以忽略了。

接下来就要分析参数了,我们往上嫖,发现两个参数分别是由bWv7o的encText、encSecKey两个属性。
接下来才是真正的js逆向了,敲黑板了!!!
既然params和encKeckey是由bWv7o产生的,那么我们就要分析bWv7o是什么了。不难发现bWv7o是由window.asrsea(JSON.stringify(i1x), bsK8C(["流泪", "强"]), bsK8C(XR1x.md), bsK8C(["爱心", "女孩", "惊恐", "大笑"]));产生的,那么接下来就找找window.asrsea是啥玩意儿
在同一作用域内的就这么点,就一个d函数
!function() {
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0", 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("00708") , 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, window.ecnonasr = e }();


再点一首歌,发现e,f,g三个参数是固定的

综上,d参数是变化的,而且变化的地方是id,分析的时候就当它是固定的,接着往后走
原来是AES加密,熟悉AES的,肯定敲开心,不熟悉也没关系,很容易

function b(a, b) {
// urf-8编码 var c = CryptoJS.enc.Utf8.parse(b) , d = CryptoJS.enc.Utf8.parse("00708") // 固定值 , e = CryptoJS.enc.Utf8.parse(a) // AES加密,e加密文本,c秘钥,d偏移量,CBC模式 , f = CryptoJS.AES.encrypt(e, c, {
iv: d, mode: CryptoJS.mode.CBC }); return f.toString() }
function c(a, b, c) {
// a,b,c是固定值 var d, e; return setMaxDigits(131), // 跳进去,调试... 创建长度为132的数组 d = new RSAKeyPair(b,"",c), // e = encryptedString(d, a) }
由此可知,encSeckey是经过RSA加密的,但是这里的a,b,c三个参数是固定值,因此这个参数也可以用固定值
好了,到这里js逆向分析就结束了
4. 模拟加密,获取参数
class Encrypt: def __init__(self, text): self.data = {
'encSecKey': '01ec48cbaa77f993a988cc1f5bcd75f49eddc581f2fe2aaf564b2d4b1312cf6e0bbaddce5a4c81b38b89abd100b0f1865d22d2a8e5dd8be208eb5d6eb2f71309a165daeffe95355e1e44edd65bdf28088fe4f5e835a7d9f7569fc2530f9d17c00b51cfafbe421ebea3' } self.text = text self.key = '0CoJUm6Qyw8W8jud' def get_form_data(self): """生成表单参数""" # 随机秘钥参数,可以用固定值 i = "4JknCzx6uEXUwxpU" # 两次加密 first_encrypt = self.AES_encpyt(self.text, self.key) self.data['params'] = self.AES_encpyt(first_encrypt, i) return self.data def AES_encpyt(self, text, key): """AES加密""" # AES加密明文必须为16的整数倍 padding = 16 - len(text.encode()) % 16 text += padding * chr(padding) aes = AES.new(key.encode(), AES.MODE_CBC, b'00708') enctext = aes.encrypt(text.encode()) return b64encode(enctext).decode('utf-8')
5.获取url,下载音乐
class NeteaseCloudMusic: def __init__(self, song): self.url = 'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=' reqstr = ''' authority: music.163.com method: POST path: /weapi/song/enhance/player/url/v1?csrf_token= scheme: https accept: */* accept-encoding: gzip, deflate, br accept-language: zh-CN,zh;q=0.9 cache-control: no-cache content-length: 434 content-type: application/x-www-form-urlencoded cookie: __root_domain_v=.163.com; _qddaz=QD.28yaab.3jymc6.kf0ihnaf; _ntes_nnid=e7c5f90265b4d5a5bcb511efebf7a890,95; _ntes_nuid=e7c5f90265b4d5a5bcb511efebf7a890; _iuqxldmzr_=32; WM_TID=OlHvFOuIVclAQFQUAEJvJZyLuh3MwtGb; NMTID=00ODCot1Uq8CvcXIUIMmKBlPfRiyfoAAAF3NHwibw; WM_NI=%2BWiHzgkFWg%2BON3YYI0rQzlpsOW8x4BPGt%2FWRNpkD3r2Utv8U1gx6RZgvmmJQ0IpSBgdk1GvY9uIQW6BfIN7lVoHo8z1BIoa%2FdLUgKwpx6twUKJtgDlexKOu7LqWGuYApZzg%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6eeb9c844a3b1aba3b24489eb8eb6d15b929a9baaaa5cace70087b64e8ab18299d02af0fea7c3b92ae989a7a9f96da99a9988aa458eed97bacc3cb28fb68df3798d89f899b74a9499bcd0d65a8eb0a5a5b27af28bbc97bb5ff3b9b8d7d152a5aaa38ec95bf497c0b4c16da8b5ffa8f553fbab87b2d63e82ba87afb66896b18890bb72f39e8790e425a8949b88ca7db4a8fa95f65f8996bc88c768a7a885b0f83d90af99a8f85383b0969be637e2a3; hb_MA-9F44-2FC2BD04228F_source=www.baidu.com; JSESSIONID-WYYY=bERBG86BVbD29X%5C35acjg8ndIoGYPEZvQ8fc0t7WUnMu3KTujvG1zqfSMIG%2By4%2FZRz9hC%2FwBN0Mf%2B%2B1RJBK2TeR96X7l%2BmS%2FHhuuqBwl7yxwe4jQ%5ChzFoFgKylb3ZdOnw6%2FqsqaUYUrJ12EVVy0m66JVlQez0T5ijmgZuOsk0KcMnUe4%3A23; WEVNSM=1.0.0; WNMCID=kctjbv.55.01.0 origin: https://music.163.com pragma: no-cache referer: https://music.163.com/ sec-fetch-dest: empty sec-fetch-mode: cors sec-fetch-site: same-origin user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 ''' self.headers = HeaderPrettyDict().pretty(reqstr) self.text = '{"ids":"[' + str(song['song_id']) + ']","level":"standard","encodeType":"aac","csrf_token":""}' self.name = song['song_name'] self.singer = song['singer'] def music(self): """获取音乐的url""" data = Encrypt(self.text).get_form_data() res = requests.post(self.url, headers=self.headers, data=data) song_url = res.json()['data'][0]['url'] self.save(self.download(song_url)) def download(self, url): """下载音乐""" headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'} res = requests.get(url, headers=headers) return res.content def save(self, content): """保存音乐""" # 当前文件目录 path = os.path.dirname(__file__) # 检查'data'目录是否存在,不存在则创建目录 if not os.path.exists(path+'\\data'): os.mkdir(path+'\\data') # 音乐保存路径 music_path = path+'\\data'+f'\\{self.name} {self.singer}.m4a' # 保存 if not os.path.exists(music_path): with open(music_path, 'wb') as f: f.write(content)
6. 歌曲搜索
以上只能实现单曲下载,下面将实现歌曲搜索功能
| 数字 | 类型 |
|---|---|
| 1 | 单曲 |
| 10 | 专辑 |
| 100 | 歌手 |
| 1014 | 视频 |
| 1006 | 歌词 |
| 1000 | 歌单 |
| 1009 | 声音主播 |
| 1002 | 用户 |
开心吧!!!但是事与愿违,虽然参数一样,但是用前面的生成参数却无法get到数据,别慌,方法还是一样,断点调试
经过调试之后,我们发现就是d参数不同(d={"hlpretag":"","hlposttag":"","s":"冬眠","type":"1","offset":"0","total":"true","limit":"30","csrf_token":""}),因此只要构造d参数就能请求到数据了

直接上代码吧
class SearchMusic: def __init__(self, text): self.url = 'https://music.163.com/weapi/cloudsearch/get/web?csrf_token=' reqstr = ''' authority: music.163.com method: POST path: /weapi/song/enhance/player/url/v1?csrf_token= scheme: https accept: */* accept-encoding: gzip, deflate, br accept-language: zh-CN,zh;q=0.9 cache-control: no-cache content-length: 434 content-type: application/x-www-form-urlencoded cookie: __root_domain_v=.163.com; _qddaz=QD.28yaab.3jymc6.kf0ihnaf; _ntes_nnid=e7c5f90265b4d5a5bcb511efebf7a890,95; _ntes_nuid=e7c5f90265b4d5a5bcb511efebf7a890; _iuqxldmzr_=32; WM_TID=OlHvFOuIVclAQFQUAEJvJZyLuh3MwtGb; NMTID=00ODCot1Uq8CvcXIUIMmKBlPfRiyfoAAAF3NHwibw; WM_NI=%2BWiHzgkFWg%2BON3YYI0rQzlpsOW8x4BPGt%2FWRNpkD3r2Utv8U1gx6RZgvmmJQ0IpSBgdk1GvY9uIQW6BfIN7lVoHo8z1BIoa%2FdLUgKwpx6twUKJtgDlexKOu7LqWGuYApZzg%3D; WM_NIKE=9ca17ae2e6ffcda170e2e6eeb9c844a3b1aba3b24489eb8eb6d15b929a9baaaa5cace70087b64e8ab18299d02af0fea7c3b92ae989a7a9f96da99a9988aa458eed97bacc3cb28fb68df3798d89f899b74a9499bcd0d65a8eb0a5a5b27af28bbc97bb5ff3b9b8d7d152a5aaa38ec95bf497c0b4c16da8b5ffa8f553fbab87b2d63e82ba87afb66896b18890bb72f39e8790e425a8949b88ca7db4a8fa95f65f8996bc88c768a7a885b0f83d90af99a8f85383b0969be637e2a3; hb_MA-9F44-2FC2BD04228F_source=www.baidu.com; JSESSIONID-WYYY=bERBG86BVbD29X%5C35acjg8ndIoGYPEZvQ8fc0t7WUnMu3KTujvG1zqfSMIG%2By4%2FZRz9hC%2FwBN0Mf%2B%2B1RJBK2TeR96X7l%2BmS%2FHhuuqBwl7yxwe4jQ%5ChzFoFgKylb3ZdOnw6%2FqsqaUYUrJ12EVVy0m66JVlQez0T5ijmgZuOsk0KcMnUe4%3A23; WEVNSM=1.0.0; WNMCID=kctjbv.55.01.0 origin: https://music.163.com pragma: no-cache referer: https://music.163.com/ sec-fetch-dest: empty sec-fetch-mode: cors sec-fetch-site: same-origin user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 ''' self.headers = HeaderPrettyDict().pretty(reqstr) self.text = text def search(self): """搜索音乐,返回音乐列表""" data = Encrypt(self.text).get_form_data() res = requests.post(self.url, headers=self.headers, data=data) songlist = [] songs = res.json()['result']['songs'] for song in songs: item = {
} # id、歌名、歌手、封面 item['song_id'] = song['id'] item['song_name'] = song['name'] item['singer'] = song['ar'][0]['name'] # item['song_pic_url'] = song['al']['picUrl'] songlist.append(item) return songlist
完美收工~~~
下一篇 打造网易云图形界面
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/225744.html原文链接:https://javaforall.net
