精华内容
下载资源
问答
  • Python爬虫:逆向分析某云音乐加密参数

    万次阅读 多人点赞 2020-09-05 09:54:07
    本篇博文通过对网易云音乐进行逆向分析,用Python代码模拟了AES和RSA加密过程,并在文章的末尾提供了一些参数,可以用这些参数来获取歌曲对应的歌词及用户的评论。

    前言

      免责声明:
        本篇博文的初衷是分享自己学习逆向分析时的个人感悟,所涉及的内容仅供学习、交流,请勿将其用于非法用途!!!任何由此引发的法律纠纷均与作者本人无关,请自行负责!!!
      版权声明:
        未经作者本人授权,禁止转载!!!

    在这里插入图片描述
      各大音乐平台是从何时开始收费的这个问题没有追溯过,印象中酷狗在16年就已经开始收费了,貌似当时的收费标准是付费音乐下载一首2元,会员一月8元,可以下载300首。虽然下载收费,但是还可以正常听歌。陆陆续续,各平台不仅收费,而且还更在乎版权问题,因为缺少版权,酷狗上以前收藏的音乐也不能听了,更过分的是,有些歌非VIP会员只能试听60秒(•́へ•́╬)。

    在这里插入图片描述

      版权问题重视起来当然是好事,但只是闲暇时来听听音乐放松一下自己的我来说,不会因为想听音乐而开通各个音乐平台的VIP的┗( ▔, ▔ )┛,所以渐渐就有了些想法:能不能将这些音乐整合起来,比如我去酷狗音乐听某一首歌,发现没有版权或只能试听,能不能自动去网易云音乐搜索下载到本地(干脆直接下载到酷狗对应的文件夹里),如果还没有就去QQ音乐、虾米音乐、百度音乐等等。

    在这里插入图片描述
      本篇就是在这样的背景下,通过对网易云音乐进行逆向分析,进而用代码的方式来*********(此处自己体会哦( ̄︶ ̄)↗)。
      目标:通过输入歌名或者歌手名,列出相应的音乐信息,然后通过选择某一项,将对应的音乐下载到本地指定目录。
      工具:Google Chrome、PyCharm
      这里以我最喜欢的歌手本兮为例,通过搜索网易云的Web端和PC端发现,Web端不支持下载,PC端需要RMB才能下载(不愧是我兮的歌(✪ω✪)),咳咳咳,OK,Fine,意料之中。

    在这里插入图片描述

    在这里插入图片描述

    1. 请求分析

      如果想要下载一首歌,我们首先要获取到这首歌所对应的 urlurl。随机选择一首歌进行播放,打开Chrome的开发者工具,刷新看一下对应的请求,找到我们想要的歌曲文件的 urlurl,就是下面这个:

    在这里插入图片描述

      然后找到该请求对应的 urlurl,分析一下该请求:

    在这里插入图片描述

      可知,获取数据的 urlurlhttps://music.xxx.com/weapi/song/enhance/player/url/v1?csrf_token=,请求方式为POST。继续往下滑,找到提交的数据:

    在这里插入图片描述

      POST提交了两个参数paramsencSecKey,很明显这两个参数都经过了加密处理,而且经过不断提交刷新发现,这两个参数值会变,可以猜测到加密时应该是有随机操作,但其长度始终不变,即参数params的长度为152,参数encSecKey的长度为256
      需要的 urlurl 及请求所需要的参数已经找到,下面需要确定一下两个参数是如何加密的。

    2. 参数分析

      通过Ctrl + Shift + F全局搜索参数encSecKey定位到了两个文件,然后在core_7a734ef25ee51b62727eb55c7f6eb1e8.js这个文件里通过Ctrl + F定位到了接口函数:

    在这里插入图片描述

      摘取这部分函数分析一下:

    var bVZ8R = window.asrsea(JSON.stringify(i0x), bqN0x(["流泪", "强"]), bqN0x(Wx5C.md), bqN0x(["爱心", "女孩", "惊恐", "大笑"]));
    e0x.data = j0x.cs1x({
        params: bVZ8R.encText,
        encSecKey: bVZ8R.encSecKey
    })
    

      函数window.asrsea()应该就是加密函数,传入四个参数,将加密后的结果赋值给变量bVZ8R,返回的结果有两个属性,即encTextencSecKey,也就是我们想要的参数paramsencSecKey。在这里设置一个断点,看一下这几个参数:

    在这里插入图片描述

      通过最右边的变量查看区Watch可以看到变量bVZ8R的值就是我们需要的参数的值,这证实了函数window.asrsea()就是加密函数,然后我们在控制台Console打印一下这几个变量:

    >JSON.stringify(i0x)
    <"{"csrf_token":""}"
    >bqN0x(["流泪", "强"])
    <"010001"
    >bqN0x(Wx5C.md)
    <"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
    >bqN0x(["爱心", "女孩", "惊恐", "大笑"])
    <"0CoJUm6Qyw8W8jud"
    

      即加密函数window.asrsea()所需的四个参数值已经确定,分别是字符串"{"csrf_token":""}""010001""00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7""0CoJUm6Qyw8W8jud",如果没有猜错的话第三个参数是十六进制的形式,其实也就是如此。通过几次刷新,这几个值不变。

    3. 加密分析

      百度搜索发现函数window.asrsea()不是JavaScript的原生函数,应该是开发者自己定义的,然后我通过搜索asrsea定位到了该函数的初始定义位置:

    在这里插入图片描述

      函数window.asrsea()就是函数d,它就是我们要找的加密函数,它接收的d、e、f、g四个参数对应的就是window.asrsea()函数的四个参数,即

    	d = "{\"csrf_token\":\"\"}"
    	e = "010001"
    	f = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
    	g = "0CoJUm6Qyw8W8jud"
    

      或许已经发现了吧,这里面的函数名、变量名及参数都是一个字母,而且它们有的还相同,没错,这是一种很常见的反爬虫手段------JS代码混淆。
      摘取这部分加密函数分析一下:

        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的作用是从字符串"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"中随机生成长度为a的字符串。

        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的作用是对数据a进行AES加密,模式为CBC,最后通过toString()方法将结果转成字符串。

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

      函数c的作用是对数据a进行RSA加密,返回的结果是十六进制形式的字符串。

        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
        }
    

      函数d的作用是对数据d进行加密,得到两个加密的结果encTextencSecKey,加密流程是通过函数a随机产生一个长度为16的字符串,然后通过函数b进行第一次AES加密,然后再通过函数b对第一次的加密结果进行一次AES加密,得到结果encText,即对应我们的params,最后通过函数c进行一次RSA加密,得到结果encSecKey

    4. 模拟加密

      这里使用一个非常强大的加密算法库-----PyCryptodome,具体使用方法请参考官方文档

      这里定义了一个EncryptText类,专门用来模拟JavaScript的加密过程:

    class EncryptText:
        def __init__(self):
            self.character = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
            self.iv = '0102030405060708'
            self.public_key = '010001'
            self.modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b' \
                           '5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417' \
                           '629ec4ee341f56135fccf695280104e0312ecbda92557c93' \
                           '870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b' \
                           '424d813cfe4875d3e82047b97ddef52741d546b8e289dc69' \
                           '35b3ece0462db0a22b8e7'
            self.nonce = '0CoJUm6Qyw8W8jud'
    

      在函数d中打上断点,来分析看一下abc三个函数返回的结果,方便比对我们模拟的结果:

    在这里插入图片描述

      程序执行到函数a处,在最右边变量作用域区Scope可以看到各个变量的值及函数a返回的的结果i: "mEXyqHtNW5dxT5IK"
      这里先模拟函数a来随机产生长度为16的字符串,首先使用的是官方提供的API:Crypto.Random.get_random_bytes(N),返回长度为N的随机字节串。

        def create16RandomBytes(self):
            """
            # 产生16位随机字符, 对应函数a
            :return:
            """
            generated_string = get_random_bytes(16)
            return generated_string
    

      我们需要将该字节串通过decode()方法转换成字符串,但是随机产生的字节串是这样的:b'\xe0\xda\xf9\x8fd\xb4M\xaa\xa7\x1fW\xaay\x12\x90@',在转换字符串时就会产生UnicodeDecodeError,所以这里就自己写了一个方法:

        def create16RandomBytes(self):
            """
            # 产生16位随机字符, 对应函数a
            :return:
            """
            generate_string = random.sample(self.character, 16)
            generated_string = ''.join(generate_string)
            return generated_string
    

      该方法产生的结果就是16位随机的字符串:

    在这里插入图片描述
      程序执行到函数b处,传入的参数dg的值我们已经知道,看一下加密后的结果:

    在这里插入图片描述

      加密后的结果为encText: "eHhjXckqrtZkqcwCalCMx0QuU6Lj9L7Wxouw1iMCnB4=",下面来用官方的API来模拟一下:

        def AESEncrypt(self, clear_text, key):
            """
            AES加密, 对应函数b
            :param clear_text: 需要加密的数据
            :return:
            """
            # 数据填充
            clear_text = pad(data_to_pad=clear_text.encode(), block_size=AES.block_size)
            key = key.encode()
            iv = self.iv.encode()
            aes = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
            cipher_text = aes.encrypt(plaintext=clear_text)
            # 字节串转为字符串
            cipher_texts = base64.b64encode(cipher_text).decode()
            return cipher_texts
    

      我们将需要加密的数据"{"csrf_token":""}"传入到该函数中,看一下模拟的结果:

    在这里插入图片描述

      很nice,结果一模一样,然后再进行一次AES加密,因为第二次加密用到了函数a产生的16位随机字符,为了结果一致,这里也使用相同的随机字符进行模拟。先看一下原始的结果:

    在这里插入图片描述

      第二次AES加密产生的结果为encText: "JWuA4mdNsTdrLdDkD9UWs8ShPCZNK0n4BLpdQEDSAaD/kFKKih8XQp8W/mICYPlN",然后对比一下自己模拟的结果:

    在这里插入图片描述

      哈哈哈哈(⁎˃ᴗ˂⁎)也是OK的,结果一样。

      AES具体的加密原理这里不做过多的介绍,感兴趣的话可以参考相关的书籍或自行百度,这里只介绍一些基本概念。
      高级加密标准(Advanced(Advanced EncryptionEncryption Standard,AES)Standard,AES)是一种分组密码算法,又称RijndaelRijndael算法,是对称密钥加密中最流行的算法之一。AES的分组长度固定为128位,密钥长度则可以是128、192或256位。
      密码分组链模式,即CBC,是分组密码工作模式之一,它需要一个初始向量(Initialization(Initialization Vector,IV)Vector,IV)组进行异或运算,而且CBC模式要求数据长度必须是密码分组长度的整数倍。因此数据长度不够的话需要进行填充。

      最后就是RSA加密了,看一下函数c返回的结果:

    在这里插入图片描述

      很长的一串,长度为256:encSecKey: "d58e873a2e908c0599b497456f1842d1734e1d17e834a221ed84d828b06b149d0bac2ddd449e38b7e5e9ce53dcb1aa43a241742a2b273434b67825743fbca6371aa143a4460477704ba3fd33b517619386daf8da4c7fe8d67a604ea0e461aedee5ae2698400a6c7340ab250c97622aa221d871b7352d81ea09262978facf5480"
      下面来模拟一下,我首先使用的是官方的API:Crypto.PublicKey.RSA产生密钥对,然后使用Crypto.Cipher.PKCS1_OAEP进行加密,加密后的数据长度是256位,通过它进行请求 urlurl 时请求状态码是200,但请求的内容为空,由于RSA每次加密得到数据都不一样,所以目前我还没有好的想法来确定问题出在哪里。

        def RSAEncrypt(self, session_key):
            """
            RSA加密的结果每次都不一样
            :param session_key:
            :return:
            """
            # n和e构成公钥
            # (n, e)
            # key = RSA.RsaKey(n=int(self.modulus, 16), e=int(self.public_key, 16))
            key = RSA.construct(rsa_components=(int(self.modulus, 16), int(self.public_key, 16)))
            public_key = key.publickey()
            rsa = PKCS1_OAEP.new(key=public_key)
            cipher_text = rsa.encrypt(message=session_key).hex()
            return cipher_text
    

    在这里插入图片描述

      根据RSA加密原理,我就自己写了一个函数来模拟RSA加密的过程:

        def RSAEncrypt(self, i, e, n):
            """
            RSA加密, 对应函数c
            :param i:
            :return:
            """
            # num = pow(x, y) % z
            # 加密C=M^e mod n
            num = pow(int(i[::-1].encode().hex(), 16), int(e, 16), int(n, 16))
            result = format(num, 'x')
            return result
    

    在这里插入图片描述

      没错,也是一模一样的(^_^)Y Ya!!

      RSA是由美国麻省理工学院的三名密码学者RivestRivestShamirShamirAdlemanAdleman提出的一种基于大合数因式分解困难性的公开密钥密码,简称RSA密码。RSA算法基于一个十分简单的数论事实,即将两个大素数相乘很容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。由于这次只用到了加密过程,所以RSA的解密过程不做过多的涉及。
      加密运算:C=MeC=M^e modmod nn,其中CC是加密后的数据,MM是被加密的数据,ee是随机的一个整数,1<e<ϕ(n)1<e<\phi (n)ϕ(n)\phi (n)是一个数论函数,称为欧拉函数,表示在比nn小的正整数中与nn互素的数的个数,nn是两个大素数的乘积,eenn是公开的,它们构成了用户的公钥。

      整个加密流程我们模拟完了,结果也是正确的,但是,这里还存在一个问题,我们模拟出来的encText,也就是参数params长度不够。这里可以确定的是加密算法是没有错误的,传入的参数中d、e、f、g后面三个值是固定的,所以问题就基本锁定了:参数d的值不对。
      我继续debug,然后发现了一些端倪:函数d又接收到了新的参数d,它的值是这样的:

    在这里插入图片描述

      将它进行两次AES加密后encText的数据长度达到了128,说明这个还不是正确的,而且Network面板并没有出现我们想要的v1?csrf_token=,然后继续debug,最终得到了参数d真正的值:d: "{"ids":"[35440198]","level":"standard","encodeType":"aac","csrf_token":""}",最后我们看一下最终的结果:

    在这里插入图片描述

    在这里插入图片描述

      使用模拟加密获取到的两个参数再次发起请求,便可以得到我们想要的数据:

    在这里插入图片描述
      歌曲的文件对应的 urlurl 我们已经找到,根据结果可知,它是一个字符串,准确来说是个json格式的,而且里面只有一条数据是我们需要的,所以直接提取:

    在这里插入图片描述
      然后再去用代码请求该 urlurl,将请求到的内容以二进制形式进行保存,文件名后缀为.mp3

    5. 获取ID

      上面实现的只是一首歌的下载,如果要实现我们的要求,还需要再修改一些参数d,有两个参数需要注意,即idslevel,一个是歌曲的id,另一个应该是歌曲的质量(有标准、无损等,我猜的),这里只关注一个,那就是歌曲的id。很容易猜到,一首歌对应一个id,我们选择哪首歌,就会得到哪首歌的id,那在哪选择呢???毫无疑问,肯定是在搜索结果中选择的。
      正常情况下,我们输入歌手名,会搜索出来许多歌手的音乐,就像下面这样:

    在这里插入图片描述

      我们通过代码直接访问https://music.xxx.com/#/search/m/?s=本兮&type=1并不会得到我们想要的信息,该 urlurl 请求得到的是网站的源代码,不包含数据在里面,很明显是通过 JavaScriptJavaScript 动态获得的,所以我们要找到请求数据的 urlurl。打开Chrome的开发者工具,刷新看一下对应的请求,找到我们想要的数据,就是下面这个:

    在这里插入图片描述

      然后找到对应的 urlurl,分析一下该请求:

    在这里插入图片描述

      可知,获取数据的 urlurlhttps://music.xxx.com/weapi/cloudsearch/get/web?csrf_token=,请求方式为依旧是POST。继续往下滑,找到提交的数据:

    在这里插入图片描述

      POST提交了两个参数paramsencSecKey,和我们获取歌曲 urlurl 时一样,但参数params的长度变为了280,参数encSecKey的长度依旧不变,为256。由此可以确定,又是参数d发生了变化。经过几次debug,最终确定了参数d的值:d = "{"hlpretag":"<span class=\"s-fc7\">","hlposttag":"</span>","s":"本兮","type":"1","offset":"0","total":"true","limit":"30","csrf_token":""}"

    在这里插入图片描述

      结果也是一样的:

    在这里插入图片描述

      使用模拟加密获取到的两个参数再次发起请求,发现得到的结果是空的,然后改了一下,将字典转为json格式,AES二次加密后参数params长度变为了300,然而却得到了数据。和我们在开发者模式下看到的结果一样,里面包含歌曲名、歌曲的id以及歌手名等信息。

    在这里插入图片描述
      从Network更容易看到json里面的数据结构:

    在这里插入图片描述

      提取到的结果如下,分别是歌手名、歌曲名、歌曲id、时长、专辑名、专辑图片的url:

    在这里插入图片描述
      这里简单分析一下参数d,关键字s表示你要搜索的内容,关键字type表示搜索的类型(见下面的表格),如果需要下载其他歌手的歌曲,只需要将参数d中的关键字s的值改一下即可,为了方便,可以用input()方法传递这个值。

    typetype 含义
    1 单曲
    100 歌手
    10 专辑
    1014 视频
    1006 歌词
    1000 歌单
    1009 主播电台
    1002 用户

    6. 代码框架

    # -*- coding: utf-8 -*-
    # @Time    : 2020/9/2 11:23
    # @Author  : XiaYouRan
    # @Email   : youran.xia@foxmail.com
    # @File    : wangyiyun_music2.py
    # @Software: PyCharm
    
    import requests
    from Crypto.Cipher import AES, PKCS1_OAEP
    from Crypto.Util.Padding import pad
    from Crypto.PublicKey import RSA
    from Crypto.Random import get_random_bytes
    import random
    import base64
    import json
    import os
    
    
    class EncryptText:
        def __init__(self):
            self.character = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
            self.iv = '0102030405060708'
            self.public_key = '010001'
            self.modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b' \
                           '5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417' \
                           '629ec4ee341f56135fccf695280104e0312ecbda92557c93' \
                           '870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b' \
                           '424d813cfe4875d3e82047b97ddef52741d546b8e289dc69' \
                           '35b3ece0462db0a22b8e7'
            self.nonce = '0CoJUm6Qyw8W8jud'
    
        def create16RandomBytes(self):
    
    
        def AESEncrypt(self, clear_text, key):
    
    
        def RSAEncrypt(self, i, e, n):
    
    
        def resultEncrypt(self, input_text):
            """
            对应函数d
            :param input_text:
            :return:
            """
            i = self.create16RandomBytes()
            encText = self.AESEncrypt(input_text, self.nonce)
            encText = self.AESEncrypt(encText, i)
            encSecKey = self.RSAEncrypt(i, self.public_key, self.modulus)
            from_data = {
                'params': encText,
                'encSecKey': encSecKey
            }
            return from_data
    
    
    class WangYiYunMusic(object):
        def __init__(self):
            self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                                          'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
    
        def get_html(self, url, method='GET', from_data=None):
            try:
                if method == 'GET':
                    response = requests.get(url, headers=self.headers)
                else:
                    response = requests.post(url, from_data, headers=self.headers)
                response.raise_for_status()
                response.encoding = 'utf-8'
                return response.text
            except Exception as err:
                print(err)
                return '请求异常'
    
        def parse_text(self, text):
            ids_list = json.loads(text)['result']['songs']
            count = 0
            info_list = []
            print('{:*^80}'.format('搜索结果如下'))
            print('{0:{5}<5}{1:{5}<20}{2:{5}<10}{3:{5}<10}{4:{5}<20}'.format('序号', '歌名', '歌手', '时长(s)', '专辑', chr(12288)))
            print('{:-^84}'.format('-'))
            for id_info in ids_list:
                song_name = id_info['name']
                id = id_info['id']
                time = id_info['dt'] // 1000
                album_name = id_info['al']['name']
                picture_url = id_info['al']['picUrl']
                singer = id_info['ar'][0]['name']
                info_list.append([id, song_name, singer])
                print('{0:{5}<5}{1:{5}<20}{2:{5}<10}{3:{5}<10}{4:{5}<20}'.format(count, song_name, singer, time, album_name, chr(12288)))
                count += 1
                if count == 8:
                    # 为了测试方便, 这里只显示了9条数据
                    break
            print('{:*^80}'.format('*'))
            return info_list
    
        def save_file(self, song_text, download_info):
            filepath = './download'
            if not os.path.exists(filepath):
                os.mkdir(filepath)
            filename = download_info[1] + '-' + download_info[2]
            music_url = json.loads(song_text)['data'][0]['url']
            response = requests.get(music_url, headers=self.headers)
            with open(os.path.join(filepath, filename) + '.mp3', 'wb') as f:
                f.write(response.content)
                print("下载完毕!")
    
    
    if __name__ == '__main__':
        id_url = 'https://music.163.com/weapi/cloudsearch/get/web?csrf_token='
        song_url = 'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token='
    
        id_d = {
            "hlpretag": "<span class=\"s-fc7\">",
            "hlposttag": "</span>",
            "s": input("请输入歌名或歌手: "),
            "type": "1",
            "offset": "0",
            "total": "true",
            "limit": "30",
            "csrf_token": ""
        }
    
        encrypt = EncryptText()
        id_from_data = encrypt.resultEncrypt(str(id_d))
    
        wyy = WangYiYunMusic()
        id_text = wyy.get_html(id_url, method='POST', from_data=id_from_data)
        info_list = wyy.parse_text(id_text)
    
        while True:
            input_index = eval(input("请输入要下载歌曲的序号(-1退出): "))
            if input_index == -1:
                break
            download_info = info_list[input_index]
            song_d = {
                "ids": str([download_info[0]]),
                "level": "standard",
                "encodeType": "aac",
                "csrf_token": ""
            }
            song_from_data = encrypt.resultEncrypt(str(song_d))
    
            song_text = wyy.get_html(song_url, method='POST', from_data=song_from_data)
            wyy.save_file(song_text, download_info)
    
    

      测试结果如下,等有时间了再做一个GUI٩(๑>◡<๑)۶ :

    在这里插入图片描述
    在这里插入图片描述

    结束语

      最后,加一个彩蛋吧,这个代码不仅可以download,还可以搜集用户的评论、歌曲对应的歌词等信息,只需要改一下参数d和请求的 urlurl 即可。这里给出这些参数:

    功能 参数dd urlurl
    搜索信息 “{“hlpretag”:”<span class=“s-fc7”>",“hlposttag”:"",“s”:"你要搜索的信息",“type”:"1",“offset”:“0”,“total”:“true”,“limit”:“30”,“csrf_token”:""}" https://music.xxx.com/weapi/cloudsearch/get/web?csrf_token=
    下载音乐 “{“ids”:”[歌曲id]",“level”:"standard",“encodeType”:“aac”,“csrf_token”:""}" https://music.xxx.com/weapi/song/enhance/player/url/v1?csrf_token=
    下载歌词 “{“id”:”歌曲id",“lv”:-1,“tv”:-1,“csrf_token”:""}" https://music.xxx.com/weapi/song/lyric?csrf_token=
    搜集用户评论 “{“rid”:“R_SO_4_歌曲id”,“threadId”:“R_SO_4_歌曲id”,“pageNo”:“1”,“pageSize”:“20”,“cursor”:”-1",“offset”:“0”,“orderType”:“1”,“csrf_token”:""}" https://music.xxx.com/weapi/comment/resource/comments/get?csrf_token=

      这些参数并不是一成不变的,如果网站更新了这些参数,那就需要重新做分析了。

    开源代码仓库


      如果喜欢的话记得给我的GitHub仓库点个Star哦!ヾ(≧∇≦*)ヾ

    展开全文
  • 雨滴网易云音乐插件
  • 网易云音乐爬虫

    万次阅读 2019-02-19 11:51:53
    首先打开网易云音乐网站 , 右键检查 , 进入network 然后刷新页面 这个文件是获取音乐地址的关键文件 打开请求发现 , 它是一个post请求而且参数进行了加密 没办法只有去看网易云的js文件了 检索encSecKey 发现...

    因最近写了一了音乐播放器 , 所以对各大知名音乐网站都写了爬虫程序 , 接下来我会将我的所有爬虫程序写成博客 , 学识有限,有些地方可能表述的不够准确 , 望大佬见谅

    由于最近在学PHP , 所以代码先由PHP实现 , 有时间我会将java , python的实现代码贴出来 , 好了下面进入正题

    首先打开网易云音乐网站 , 右键检查 , 进入network 然后刷新页面
    在这里插入图片描述
    这个文件是获取音乐地址的关键文件在这里插入图片描述
    打开请求发现 , 它是一个post请求而且参数进行了加密 没办法只有去看网易云的js文件了 检索encSecKey 发现关键代码在 下面这个文件中(这个地址经常改变,可能你的已经不是这一个了)https://s3.music.126.net/web/s/core_a5deb939c48df9f196db7386e2a72233.js?a5deb939c48df9f196db7386e2a72233

    进行格式化一下将关键代码找出来

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

    开始这段代码我看的很头疼 , 能力有限根本看不出来这段代码的执行流程 , 于是我借助fiddler工具将网易云的这个文件替换成本地的一个文件了 , 本地代码 :

    function() {
        function a(a) {
            console.log("a函数进入 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
        }
        function b(a, b) {
            console.log("b函数进入 a => " + a + "  b=>" + 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()
        }
        function c(a, b, c) {
            console.log("c函数进入 a => " + a + "  b=>" + b + " c=>" + c);
            var d, e;
            return setMaxDigits(131),
            d = new RSAKeyPair(b, "", c),
            e = encryptedString(d, a)
        }
        function d(d, e, f, g) {
            console.log("d函数进入 d => " + d + "  e=>" + e + " f=>" + f + "  g=>" + 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) {
            console.log("e函数进入 a => " + a + "  b=>" + b + " d=>" + d + "  e=>" + e);
            var f = {};
            return f.encText = c(a + e, b, d),
            f
        }
        window.asrsea = d,
        window.ecnonasr = e
    }
    

    每个函数里面都加了一个输出 , 然后清除一下浏览器缓存 , 再刷新网易云的网站 , 然后看控制台输出

    d函数进入 d => {"ids":"[1336866698]","br":128000,"csrf_token":""}  e=>010001 f=>00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7  g=>0CoJUm6Qyw8W8jud
    core_a5deb939c48df9f196db7386e2a72233.js?a5deb939c48df9f196db7386e2a72233:88 
    a函数进入 a => 16
    core_a5deb939c48df9f196db7386e2a72233.js?a5deb939c48df9f196db7386e2a72233:88 
    b函数进入 a => {"ids":"[1336866698]","br":128000,"csrf_token":""}  b=>0CoJUm6Qyw8W8jud
    core_a5deb939c48df9f196db7386e2a72233.js?a5deb939c48df9f196db7386e2a72233:88 
    b函数进入 a => NwoJca234EubXlkdSucwDVGT31vu5v2R1Kc1OhMC8VHhHWkT3h8b1ONNSns/t0KwXJ3Nnj17GnXlNqdbYdp+YQ==  b=>cui5UlB0Hp5Osovk
    core_a5deb939c48df9f196db7386e2a72233.js?a5deb939c48df9f196db7386e2a72233:88 
    c函数进入 a => cui5UlB0Hp5Osovk  b=>010001 c=>00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
    
    encSecKey:"b62d8aeac7f4cd0a2653997c76ec40103504a6b3ccd6df45230ba0c9ffba53f21574676726c37a4c3910d394e21e9a71604b084f4e1105d6c4db7e7da1af9d4e2357fb80ebcdeb13d8bc268288a5d9ae23bec681c1f15fa7eb9a03c9229e9a94efd887e46b332f4c3421567bf9236cf847cd7708b4d39a03bff95cb194879635"
    encText:"EPNWe5sbpNdQdH4pQGiu97wnuMUeIr4FmICM17IfGQE18M0vYINPrjrKOuoA9NjL8O9uVZIV/+rrWIVNSMr89khB7y4H//GtO7JHwG4azP6ZWmAf4Ge4W6+eDQc7vm9r"
    

    首先进入d函数 , 你多刷新几遍会发现d函数除了第一个参数后面三个参数都是死的 , 进行了两次b函数加密出params参数 b函数是AES加密 他的秘钥有一个是a函数生成的 另外几个秘钥都是死的, 进行一次c函数加密出encSecKey参数

    因为我对加密的了解不多 , 对我来说要模拟出c函数很困难,所以从加密这条路我已经走不通了 , 那我就从服务器解密来逆向思考一下

    首先他要给用户返回正确信息他就必须要解密出params参数 ,那么就得获取加密的秘钥 , 不难知道加密秘钥就藏在encSecKey中 , 所以我只要保证 加密params时的秘钥与encSecKey为一套就行了

    例如:
    我就用上面那个c函数的a参数cui5UlB0Hp5Osovk作为加密params的秘钥
    encSecKey就用刚刚生成的b62d8aeac7f4cd0a2653997c76ec40103504a6b3ccd6df45230ba0c9ffba53f21574676726c37a4c3910d394e21e9a71604b084f4e1105d6c4db7e7da1af9d4e2357fb80ebcdeb13d8bc268288a5d9ae23bec681c1f15fa7eb9a03c9229e9a94efd887e46b332f4c3421567bf9236cf847cd7708b4d39a03bff95cb194879635

    经过这样一分析发现除了进入d函数的第一个参数是活的其他的都是死的 , encSecKey就使用上面这一个 , 我们只要以cui5UlB0Hp5Osovk作为秘钥生成params参数就行了

    好了分析就到此结束 , 下面上代码 (以PHP为例)

    define("PARAME1" , "0CoJUm6Qyw8W8jud"); #相当于进入d函数的参数g
    define("PARAME2" , "0102030405060708");    #b函数中的一个死的秘钥
    $songData = '{"ids":"[1316563155]","br":128000,"csrf_token":""}' ; #歌曲信息
    //模拟浏览器访问头
    $header = array(
        'Referer'         => 'https://music.163.com/',
        'User-Agent'      => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/605.1.15 (KHTML, like Gecko)'
    );
    //将参数格式化
    $header = array_map(function($k,$v){
        return $k.": ".$v ;
    } , array_keys($header) , $header);
    //a函数生成的秘钥我写死为它了
    $a_back = "cui5UlB0Hp5Osovk" ;
    //进行一次AES加密
    $songData = openssl_encrypt($songData, 'aes-128-cbc', PARAME1 , false, PARAME2);
    //进行两次加密
    $params   = openssl_encrypt($songData, 'aes-128-cbc', $a_back , false, PARAME2);
    //encSecKey 我也写死 , 只要保证他和上面 $a_back一致就行了
    $encSecKey = "b62d8aeac7f4cd0a2653997c76ec40103504a6b3ccd6df45230ba0c9ffba53f21574676726c37a4c3910d394e21e9a71604b084f4e1105d6c4db7e7da1af9d4e2357fb80ebcdeb13d8bc268288a5d9ae23bec681c1f15fa7eb9a03c9229e9a94efd887e46b332f4c3421567bf9236cf847cd7708b4d39a03bff95cb194879635" ;
    //下面是curl的使用
    $curl = curl_init("https://music.163.com/weapi/song/enhance/player/url?csrf_token=");
    
    curl_setopt($curl , CURLOPT_HEADER , false);
    curl_setopt($curl , CURLOPT_SSL_VERIFYPEER , false);
    curl_setopt($curl , CURLOPT_RETURNTRANSFER , true);
    curl_setopt($curl , CURLOPT_POST , true);
    curl_setopt($curl , CURLOPT_HTTPHEADER , $header);
    curl_setopt($curl , CURLOPT_POSTFIELDS , http_build_query(array("params" => $params , "encSecKey" => $encSecKey)));
    
    $response = curl_exec($curl);
    
    echo $response;
    

    运行结果:
    {
    “data”: [{
    “id”: 1316563155,
    “url”: “http://m10.music.126.net/20190219121104/8ef5afe8bf60107442da3b392a85d853/ymusic/14c4/c25d/c027/5fdbf1159c6191ed158af3788b79cd52.mp3”,
    “br”: 128000,
    “size”: 3306519,
    “md5”: “5fdbf1159c6191ed158af3788b79cd52”,
    “code”: 200,
    “expi”: 1200,
    “type”: “mp3”,
    “gain”: 0.3218,
    “fee”: 0,
    “uf”: null,
    “payed”: 0,
    “flag”: 0,
    “canExtend”: false,
    “freeTrialInfo”: null
    }],
    “code”: 200
    }

    好了我知道的就这么多了 , 希望可以帮助到你
    我的GitHub:https://github.com/wangffei/PHP , 里面有更详细的代码

    展开全文
  • 网易云音乐里的正版音乐封面有时很想下下来收藏,无奈不知道如何下载,这款网易云音乐歌单获取封面软件就可以帮助你们简单快速的获取歌单封面,感兴趣的朋友可以试试。
  • 网易云音乐界面

    2017-12-28 21:46:12
    网易云音乐界面 网易云音乐界面
  • HTML5 音乐播放器,仿网易云音乐,QQ音乐
  • 网易云音乐 音乐外链

    千次阅读 2018-10-13 18:45:55
    获取网易云音乐外链 浏览器播放音乐1,复制网易云音乐ID2,音乐外链解析网站 1,复制网易云音乐ID 2,音乐外链解析网站 外链转换工具 参考: 在线网页制作】音乐外链获取教程 HHTJim’s部落格 Web App | 外链转换...

    获取网易云音乐外链 浏览器播放音乐

    1,复制网易云音乐ID

    在这里插入图片描述

    2,音乐外链解析网站

    外链转换工具

    参考:

    1. 音乐直链搜索
    2. HHTJim’s部落格 Web App | 外链转换工具 | 分享链接转直链
    展开全文
  • 网易云音乐官网

    2019-03-01 15:53:28
    网易云音乐官网 html5
  • 网易云音乐js代码

    2020-11-20 16:41:00
    网易云音乐js代码
  • 网易云音乐api

    2018-10-21 02:26:20
    网易云音乐api 自己看就懂了 就是这样
  • 网易云音乐歌曲评论爬虫

    万次阅读 2019-02-20 18:27:37
    上一篇文章我已经教大家一种比较偷懒的方式爬取网易云歌曲链接 ...通过上一篇的分析我们知道整个网易云音乐post参数的获取只需要一个变量就行了,其他的参数都是死的 , 好了继续使用之前的方法看看可变的那个参数是...

    上一篇文章我已经教大家一种比较偷懒的方式爬取网易云歌曲链接 , 接下来我们继续以这种方式爬取网易云的评论 , 歌单 , 专辑 和搜索结果 这一篇主要讲网易云的评论爬取 ,

    打开要爬的歌曲网页 , 右键检查 , 进入network选项卡 , 刷新页面
    在这里插入图片描述
    在这里插入图片描述
    通过上一篇的分析我们知道整个网易云音乐post参数的获取只需要一个变量就行了,其他的参数都是死的 , 好了继续使用之前的方法看看可变的那个参数是什么吧:

    d函数进入 d => {"rid":"R_SO_4_1342798229","offset":"0","total":"true","limit":"20","csrf_token":""}  e=>010001 f=>00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7  g=>0CoJUm6Qyw8W8jud
    a函数进入 a => 16
    b函数进入 a => {"rid":"R_SO_4_1342798229","offset":"0","total":"true","limit":"20","csrf_token":""}  b=>0CoJUm6Qyw8W8jud
    b函数进入 a => gHPVST6jk9PvEgIDjnZzQm5eol/ky4Ziny9q0k+2OJZ8nridUSTC/qZT9OPaG347Nzk60/ZG/6a8rRCQTf/49+EZtiKnkvi24KuSdDKia2sKLeFF28Zyj5GZBwWC260A  b=>i8hkVttH5BgdJMYw
    c函数进入 a => i8hkVttH5BgdJMYw  b=>010001 c=>00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
    
    encSecKey:"4db476c4059d59a35dbabe6c1e1588ffffc80d964f9da8b709056b193b0d72d4535a15cbb52287efd39addb51f9db0a059af317be1878235bccc38d77580a91416c791dccd199374251a10c0f1244a53a5cbc7499a36af0ae1a73a3c00a50bb99c4f79b1985aaac0856aa6268089ee379de3be2a249908676b46c25e4564561d"
    encText:"6ZNWq6L+AjYiujItwQE2lTCLJc2x/C5Z+hjodS9UaOZrpNyoKJXdMoFFrpnoU4JaEqSNs/ir5nv/0ajZWFxZ9HiasEY76XbDH4WvmvaoLfuZE69cHFUbhVpKUY7eS8cOJniKK82Blc0aVsgiad02qWx5WP4kdpIq7OtRtwIK0ZGNsn4RE6rtvBcFYwEj3zlr"
    

    可变参数是进入d函数的d参数 :
    {“rid”:“R_SO_4_1342798229”,“offset”:“0”,“total”:“true”,“limit”:“20”,“csrf_token”:""}

    接下来就是最核心的一步了 , 构造params参数 (以PHP为例):

    $songData = '{"rid":"R_SO_4_1342798229","offset":"0","total":"true","limit":"20","csrf_token":""}';
    //固定参数
    $v1 = '0CoJUm6Qyw8W8jud';
    $v2 = '0102030405060708' ;
    //下面这个参数使用上一篇中的cui5UlB0Hp5Osovk
    $v3 = 'cui5UlB0Hp5Osovk';
    $songData = openssl_encrypt($songData, 'aes-128-cbc', $v1 , false, $v2);
    $params   = openssl_encrypt($songData, 'aes-128-cbc', $v3, false, $v2);
    

    参数都已经获取了下一步就是构造请求了:

    $header = array(
    #    'Referer'         => 'https://music.163.com/song?id=1342798229',
        'User-Agent'      => 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36',
        "Host"            => "music.163.com"  ,
    );
    $encSecKey = "b62d8aeac7f4cd0a2653997c76ec40103504a6b3ccd6df45230ba0c9ffba53f21574676726c37a4c3910d394e21e9a71604b084f4e1105d6c4db7e7da1af9d4e2357fb80ebcdeb13d8bc268288a5d9ae23bec681c1f15fa7eb9a03c9229e9a94efd887e46b332f4c3421567bf9236cf847cd7708b4d39a03bff95cb194879635";
    
    $curl = curl_init("https://music.163.com/weapi/v1/resource/comments/R_SO_4_1342798229?csrf_token=");
    
    curl_setopt($curl , CURLOPT_HEADER , false);
    curl_setopt($curl , CURLOPT_SSL_VERIFYPEER , false);
    curl_setopt($curl , CURLOPT_RETURNTRANSFER , true);
    curl_setopt($curl , CURLOPT_POST , true);
    curl_setopt($curl , CURLOPT_HTTPHEADER , $header);
    curl_setopt($curl , CURLOPT_POSTFIELDS , http_build_query(array("params" => $params , "encSecKey" => $encSecKey)));
    
    $response = curl_exec($curl);
    
    echo $response;
    

    结果:
    在这里插入图片描述
    好了评论爬取到了 , 下面贴上全部代码:

    define("PARAME1" , "0CoJUm6Qyw8W8jud");
    define("PARAME2" , "0102030405060708");
    $songData = '{"rid":"R_SO_4_1342798229","offset":"0","total":"true","limit":"20","csrf_token":""}';
    
    $header = array(
    #    'Referer'         => 'https://music.163.com/song?id=1342798229',
        'User-Agent'      => 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36',
        "Host"            => "music.163.com"  ,
    );
    $header = array_map(function($k,$v){
        return $k.": ".$v ;
    } , array_keys($header) , $header);
    
    $a_back = "cui5UlB0Hp5Osovk" ;
    
    $songData = openssl_encrypt($songData, 'aes-128-cbc', PARAME1 , false, PARAME2);
    $params   = openssl_encrypt($songData, 'aes-128-cbc', $a_back , false, PARAME2);
    
    $encSecKey = "b62d8aeac7f4cd0a2653997c76ec40103504a6b3ccd6df45230ba0c9ffba53f21574676726c37a4c3910d394e21e9a71604b084f4e1105d6c4db7e7da1af9d4e2357fb80ebcdeb13d8bc268288a5d9ae23bec681c1f15fa7eb9a03c9229e9a94efd887e46b332f4c3421567bf9236cf847cd7708b4d39a03bff95cb194879635";
    
    $curl = curl_init("https://music.163.com/weapi/v1/resource/comments/R_SO_4_1342798229?csrf_token=");
    
    curl_setopt($curl , CURLOPT_HEADER , false);
    curl_setopt($curl , CURLOPT_SSL_VERIFYPEER , false);
    curl_setopt($curl , CURLOPT_RETURNTRANSFER , true);
    curl_setopt($curl , CURLOPT_POST , true);
    curl_setopt($curl , CURLOPT_HTTPHEADER , $header);
    curl_setopt($curl , CURLOPT_POSTFIELDS , http_build_query(array("params" => $params , "encSecKey" => $encSecKey)));
    
    $response = curl_exec($curl);
    
    echo $response;
    
    展开全文
  • 基于create-react-app2.x,仿网易云音乐【web】实现的音乐播放器
  • 高仿网易云音乐 API

    2018-05-23 11:45:30
    高仿网易云音乐 API 源码 网易云音乐 仅供学习 请勿商用
  • 非常实用的网易云音乐打卡,网易云音乐一键打卡300首歌曲 非常实用的网易云音乐打卡,网易云音乐一键打卡 定时完成网易云音乐打卡,也可托管完成。 非常实用的网易云音乐打卡 一键打卡及可完成听歌任务 升级速度杠杠...
  • kali安装网易云音乐

    万次阅读 2020-09-11 09:43:11
    kali安装网易云音乐 #下载安装包 http://music.163.com/#/download #安装 dpkg -i netease-cloud-music_1.2.1_amd64_ubuntu_20190428.deb apt-get -f install dpkg -i netease-cloud-music_1.2.1_amd64_ubuntu_...
  • 据记忆开始接触音乐软件的时候就是酷狗音乐,后来慢慢发现周围的人都是用网易云音乐。之前没有很在意,随着朋友圈分享歌曲越来越多的来自网易云音乐,自己忍不住也下载了看看。网易云音乐,感觉比较好的是界面相对...
  • 网易云音乐

    2018-03-06 15:41:07
    该产品2013年4月23日正式发布,2015年1月16日,网易云音乐荣膺百度中国好应用“年度优秀视觉设计奖”。2018年3月6日,阿里音乐和网易云音乐宣布,达成版权互授合作。 这是网易云音乐自发布后的一些历程,其实和大多...
  • Android仿网易云音乐播放器

    千人学习 2019-08-09 10:15:22
    Android仿网易云音乐播放器,学习本课程可以学习到音乐播放器开发流程,Activity与Service接口通信,音乐播放器逻辑处理,Android项目开发以及常用控件,单例设计模式的封装使用以及MediaPlayer的使用和封装,还有专...
  • 相信大家安装网易云音乐一定走了很多弯路,这里我刚刚找到正确的打开方式,分享给大家。 1、下载网易云音乐的安装包 下载网址:http://d1.music.126.net/dmusic/netease-cloud-music_1.1.0_amd64_ubuntu.deb 2、...
  • 网易云音乐 for linux

    2017-07-14 17:19:22
    网易云音乐 for linux
  • 简介: 模仿网易云音乐鲸云特效动效 更多:作者提 Bug 标签: # Android 粒子特效--网易云鲸云特效 文档没更新,建议阅读原文:https://www.jianshu.com/p/d2996afeb3e1 最近网易云音乐出了一个叫鲸云...
  • 三步教你手动破解网易云音乐加密

    万次阅读 多人点赞 2020-04-17 22:10:55
    首先打开网易云音乐随便缓存一首需要付费下载的歌曲 比如这一首 如果你想要下载是需要付费的 第二步 找到文件 点开设置-&amp;amp;gt;下载设置&amp;amp;nbsp;找到缓存目录 按照修改日期排序&amp;amp;...
  • 酷狗音乐、QQ音乐、网易云音乐API

    万次阅读 多人点赞 2018-07-28 22:09:13
    写在前面 ...​ 一开始现操期中课程设计想要做一个可以共享各大音乐平台的音乐播放器,而这些音乐平台都没有提供API接口(很正常ヾ(・ε・`*) ),所以研究了一下酷狗音乐、QQ音乐、网易云音乐的...
  • iPhone导出网易云音乐本地音乐

    千次阅读 2020-01-17 00:37:51
    恢复以后发现网易云音乐的本地音乐并没有包含在备份文件中,导致新手机网易云音乐里空空如也。此时萌生了导出旧手机中网易云音乐本地音乐的想法。 方案 ① 通过 Cydia impactor 和 FilzaEscaped 完成 iPhone 在...
  • VUE高仿移动端网易云音乐
  • 网易云音乐批量下载

    2018-08-13 16:33:40
    可批量下载网易云音乐,python开发实现的小工具,暂时只能下载免费音乐
  • web版的网易云音乐

    2019-08-11 22:40:33
    web版的网易云音乐
  • 网易云音乐ncm格式转换mp3格式工具

    万次阅读 多人点赞 2018-12-08 14:05:13
    链接:...提取码:z3rq 方法:解压缩之后会看到有一个exe文件,把需要转的ncm文件放进文件夹,然后拖动ncm文件到exe文件上,会生成对应文件的flac格式,再将.flac手动重命名改成.mp3就ok啦 ...
  • 网易云音乐项目

    2020-12-06 09:46:26
    网易云音乐项目 壹之章 - 准备 1. 项目描述 (1)此项目为一个前后台分离的仿网易云音乐项目的 SPA (2)包括 发现音乐 / 视频 / 朋友 / 直播 / 私人FM 等功能模块 (3)前端:使用 Vue 全家桶 + iView + Axios + ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,481
精华内容 3,792
热门标签
关键字:

云音乐