Netease cloud music download
目录
- 前言
- 思路
- 代码
前言
想写个能下载网易云歌曲的那种东西来逗逗女孩子开心,当然付费的那种也行。就是抓了某个网易云某接口吧,不知道是不是。朋友先写点后来瞎翻东西的时候看到的,后面也自己也写了一个单是反现之前那个太烂了。果断重写
思路
由于那个接口下载歌曲要获取歌曲的ID才能下载,类似于以下这样:
(拿全职猎人的departure这首歌的URL来说,url后面的id就是歌曲下载的id)
全职猎人departure!:https://music.163.com/#/song?id=496618
访问下载接口:http://music.163.com/song/media/outer/url?id=496618.mp3
得到最终下载地址:http://m10.music.126.net/20190212143936/506150fe805d1a57e81122e0f2e2bf00/ymusic/973f/38a2/f492/d3ff0469103cdb1ecba8c4864ec2659c.mp3
得到思路:
1.先写个搜索歌曲的功能,通过抓包可以看到json返回的结果里有歌曲名称和歌曲id
2.然后在进行访问接口
3.最终得到 完整的下载地址然后进行下载
4.由于这个下载地址一打开就会自动放歌,添加一个试听的功能
代码
PS:解密脚本,由于网易云搜索是有加密的,这里我看了半天看不懂直接CV别人的来用了
分析网易云音乐加密文章来源:
网易云音乐Ajax Post参数加密方法 - 知乎
netEaseMuiscEncrypot.py来源
netEaseMuiscEncrypot.py
import json
from Crypto.Cipher import AES
import base64
from Crypto.PublicKey import RSA
import codecs
import random
#PKCS_7方式进行补位
def pad(data:bytes,blockbytes:int=16):
"""
data:需要补位的bytes
blockbytes:块bytes大小
"""
_data = data
#判断是否是字符串,如果是则按utf8编码为bytes
if isinstance(_data,str):
_data = _data.encode('utf8')
length = len(_data)
pad_length = blockbytes - length % blockbytes
padding = (chr(pad_length)*pad_length).encode('utf8')
_data += padding
return _data
#dict序列化
def dictSerialize(data:dict):
if not isinstance(data,dict):
_data = dict(data)
_data = data
_data = json.dumps(_data,ensure_ascii=False)
return _data
#生成随机值用于AES第二阶段加密的密钥
def rand_a(a=16):
b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
c = ""
length = len(b)
for _ in range(a):
c += b[int(random.random()*length)]
return c
#aes加密
def aes_encrypt(data,key,iv,mode=AES.MODE_CBC):
"""
data:明文
key:密钥
iv:偏移量
mode:模式,默认为CBC模式
"""
encryptor=AES.new(key.encode('utf-8'),mode=mode,IV=iv.encode('utf-8'))
#补位操作
_data = pad(data,16)
encrypted_data = encryptor.encrypt(_data)
return base64.b64encode(encrypted_data)
#aes解密
def aes_decrypt(data,key,iv,mode=AES.MODE_CBC):
"""
data:密文
key:密钥
iv:偏移量
mode:模式,默认为CBC模式
"""
decryptor = AES.new(key,mode=mode,IV=iv)
return decryptor.decrypt(data)
def netEase_AES_encrypt(data,key1,key2,iv,mode=AES.MODE_CBC):
"""
data:明文
key1:第一阶段密钥
key2:第二阶段密钥
iv:偏移量
mode:模式,默认为CBC模式
"""
if isinstance(data,dict):
_data = dictSerialize(data)
_data = str(data)
_encryptedData = aes_encrypt(_data,key1,iv,mode)
_encryptedData = aes_encrypt(_encryptedData,key2,iv,mode)
return _encryptedData.decode('utf8')
def netEase_AES_decryot(data,key1,key2,iv,mode=AES.MODE_CBC):
"""
data:密文
key1:第一阶段加密函数
key2:第二阶段加密函数
iv:偏移量
mode:模式,默认为CBC模式
"""
_data = base64.b64decode(data)
plaintext = aes_decrypt(_data,key2,iv,mode)
plaintext = base64.b64decode(plaintext)
plaintext = aes_decrypt(plaintext,key1,iv,mode)
plaintext = plaintext.decode('utf8')
return plaintext
def netEase_RSA_encrypt_NoPadding(data:str,publickey:str,module:str):
"""
RSA采用Nopadding方式,即明文先倒排,再在前面补0
data:明文
publickey:公钥
module:RSA中两个大质数的乘积
"""
#网易云云音乐中js加密采用的算法中的参数是以16进制来操作的
_publickey = int(publickey,16)
_module = int(module,16)
_data = data[::-1]
_data = int(codecs.encode(_data.encode('utf8'),'hex'),16)
#RSA加密过程
rs = _data**_publickey%_module
#返回16进制解码,前面补0补足256位的字符串
return format(rs,'x').zfill(256)
search.py
#author:九世
#time:2019/2/11
import requests
from netEaseMusicEncrypt import rand_a, netEase_AES_encrypt,netEase_RSA_encrypt_NoPadding,dictSerialize,aes_encrypt
import webbrowser
import os
musica={}
print('作者:By 九世')
print('网易云音乐搜索')
print('')
user = input('输入你要搜索的歌手或歌曲名称:')
query = {"s": "{}".format(user), "limit": "8", "csrf_token": ""}
key1 = "0CoJUm6Qyw8W8jud"
iv = "0102030405060708"
# 公钥对,hex16进制编码
module = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68a\
ce615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
publickey = "010001"
# 序列化查询dict
queryStr = dictSerialize(query)
aaa = aes_encrypt(queryStr, key1, iv)
# AES加密随机量
key2 = rand_a()
secretStr = netEase_AES_encrypt(queryStr, key1, key2, iv) # 加密后的params
rsaSec = netEase_RSA_encrypt_NoPadding(key2, publickey, module) # 加密后的key
class Search:
def __init__(self,headers,url,data):
self.headers=headers
self.url=url
self.data=data
def reqt(self):
try:
rqt=requests.post(url=self.url,headers=self.headers,data=self.data)
jsons=rqt.json()
result=jsons['result']
songs=result['songs']
for s in songs:
print('歌曲名称:{} 下载ID:{}'.format(s['name'],s['id']))
musica[s['name']]=s['id']
except Exception as r:
print('[-] 报错:{}'.format(r))
return ''
return 1
def music(self):
print('')
ids=input('请输入歌曲的名称,q退出:')
if ids in musica.keys():
print('[+] 自动打开默认浏览器,在线试听')
value=musica.get(ids)
url='http://music.163.com/song/media/outer/url?id={}'.format(value)
webbrowser.open(url)
elif ids=='q':
print('退出...')
exit()
else:
print('[-] 找不到歌曲')
def download(self):
print('')
ids = input('请输入歌曲的名称,q是退出:')
if ids in musica.keys():
print('[+] 下载中')
value = musica.get(ids)
url = 'http://music.163.com/song/media/outer/url?id={}'.format(value)
rst=requests.get(url=url,headers=self.headers)
rst2=requests.get(url=rst.url,headers=self.headers)
xj=open('file/{}.mp3'.format(ids),'wb')
xj.write(rst2.content)
xj.close()
if os.path.exists('file/{}.mp3'.format(ids)):
print('[+] {}.mp3 下载成功'.format(ids))
else:
print('[-] {}.mp3 下载失败'.format(ids))
elif ids=='q':
print('退出...')
exit()
else:
print('[-] 找不到歌曲')
if __name__ == '__main__':
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36','Content-Type': 'application/x-www-form-urlencoded'}
url='http://music.163.com/weapi/search/suggest/web?csrf_token='
data={'params':secretStr,'encSecKey':rsaSec}
obj=Search(headers=headers,url=url,data=data)
rs=obj.reqt()
while True:
if rs==1:
print('1.自动打开浏览器试听')
print('2.下载')
print('0.退出')
useq=input('请选择:')
if useq=='1':
while True:
obj.music()
elif useq=='2':
while True:
obj.download()
elif useq=='0':
print('退出...')
exit()
else:
print('[-] 没有这个选择')
最终测试结果:
然后打包成EXE扔给她:
pyinstaller -F <file>
转载请声明:转自422926799.github.io
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
文章标题:Netease cloud music download
本文作者:九世
发布时间:2019-02-12, 14:11:49
最后更新:2019-04-19, 20:36:16
原始链接:http://jiushill.github.io/posts/733bcb3d.html版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。