Netease cloud music download

  1. 目录
  2. 前言
  3. 思路
  4. 代码

目录


  • 前言
  • 思路
  • 代码

前言


想写个能下载网易云歌曲的那种东西来逗逗女孩子开心,当然付费的那种也行。就是抓了某个网易云某接口吧,不知道是不是。朋友先写点后来瞎翻东西的时候看到的,后面也自己也写了一个单是反现之前那个太烂了。果断重写
7d14bd14a8074bfb8fed5e78d6603510.jpeg

思路


由于那个接口下载歌曲要获取歌曲的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('[-] 没有这个选择')

最终测试结果:
kdT0ZF.png

kdTyGR.md.jpg

kdT6R1.png

然后打包成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" 转载请保留原文链接及作者。

目录