毛人凤 發表於 2022-12-6 08:30:00

python爬虫爬取网易云音乐(超详细教程,附源码)

<h1 id="一-前言">一、 前言</h1>
<p>先说结论,目前无法下载无损音乐,也无法下载vip音乐。<br>
此代码模拟web网页js加密的过程,向api接口发送参数并获取数据,仅供参考学习,如果需要下载网易云音乐,不如直接在客户端下载,客户端还可以下载无损音乐。<br>
代码还是半成品,打算再做个音乐播放器,直接打包成exe,等有时间做好了再传到github上去,现在先把解析过程记录下来发布。<br>
至于音乐搜索器,我所知道的有一个,地址:https://iw233.cn/music/<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707803-528620837.png" alt="在这里插入图片描述" loading="lazy"><br>
上面这个网页直接返回所有的搜索结果和音乐信息,如要使用,请自行解析(很简单的一个页面)。<br>
网上流传的网易云音乐外链地址:http://music.163.com/song/media/outer/url?id=534544522.mp3,我也不知道怎么来的,输入音乐id即可获得下载链接,本着学习的态度,我认为还是从头到尾解析下载链接才好,因此不考虑使用该外链。<br>
接口文档已发布<br>
链接: https://25ukpfkme3.apifox.cn访问密码: qtlXaZPH</p>
<h1 id="二解析过程">二、解析过程</h1>
<h2 id="1音乐搜索">1、音乐搜索</h2>
<h3 id="1获取链接">(1)获取链接</h3>
<p>来到网易云音乐首页,输入音乐名称,得到搜索结果<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707888-908459308.png" alt="搜索结果" loading="lazy"><br>
按F12,打开开发者工具,重新刷新一下界面,点击网络,在筛选器里只筛选XHR和Fetch数据,点击预览,一个一个链接往下找,直到找到我们需要的数据为止。<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155708049-1234607108.png" alt="返回数据" loading="lazy"><br>
得到音乐搜索的api接口:<code>https://music.163.com/weapi/cloudsearch/get/web?csrf_token=</code><br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707744-1991846083.png" alt="api" loading="lazy"></p>
<p><strong>csrf_token</strong>只有你登录时才有,有没有这个值不影响返回的结果。<br>
<strong>需要注意,这个api接口的POST请求,后面所有的api接口都是POST请求</strong>。</p>
<h3 id="2分析参数">(2)分析参数</h3>
<p>点击负载,查看我们需要传入哪些数据<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707878-1596437614.png" alt="参数" loading="lazy"><br>
可以看到我们需要传入params和encSecKey两个参数,才能获取数据,否则得到的数据为空。<br>
那么如何获取这两个参数呢?点击发起程序,随便点击一个,进入js脚本界面<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707756-1315490674.png" alt="发起程序" loading="lazy"><br>
点下面的{},将js代码格式化,这样我们更好查看代码<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707916-451986072.png" alt="js" loading="lazy"><br>
在js页面里按ctrl+f,直接搜索encSecKey,总共有三个结果,<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155708061-2119863764.png" alt="encSecKey" loading="lazy"><br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707851-740020279.png" alt="在这里插入图片描述" loading="lazy"></p>
<p>可以看到,在执行window.asrsea()函数后,生成了params和encSecKey,<br>
这里我们贴一下js源码,后面还会用到</p>
<pre><code class="language-javascript">var bMr5w = window.asrsea(JSON.stringify(i8a), bsg1x(["流泪", "强"]), bsg1x(TH5M.md), bsg1x(["爱心", "女孩", "惊恐", "大笑"]));
            e8e.data = j8b.cr9i({
                params: bMr5w.encText,
                encSecKey: bMr5w.encSecKey
            })
</code></pre>
<p>那么window.asrsea()是什么呢,搜索asrsea,可以看到window.asrsea()=d<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707901-237830988.png" alt="asrsea" loading="lazy"><br>
找到d函数,就在window.asrsea()上面,<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707729-1160529098.png" alt="d" loading="lazy"><br>
把js代码复制出来</p>
<pre><code class="language-javascript">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
    }
</code></pre>
<p>其共涉及到三个函数a,b,c。我们先不急研究这三个函数的作用,先查看d函数传进去的四个参数d,e,f,g是什么。<br>
其实从前面的window.asrsea()函数里我们就知道,这四个参数分别为JSON.stringify(i8a), bsg1x(["流泪", "强"]), bsg1x(TH5M.md), bsg1x(["爱心", "女孩", "惊恐", "大笑"]),<br>
在这行打一个断点,重新刷新一下界面,进入调试模式。<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707746-51348346.png" alt="调试" loading="lazy"><br>
在控制台依次输入这四个参数,获得其值<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155708065-66559012.png" alt="控制台" loading="lazy"><br>
点击执行,继续执行下一步,再次查看四个参数的值,可以在监视里面输入四个参数,每次执行后会显示参数的值<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707775-748711275.png" alt="执行" loading="lazy"><br>
经过多次调试后我们发现,除了第一个参数有变化之外,后面三个参数都是固定的,window.asrsea()函数会将这四个参数传入到d函数中,也就是d,e,f,g</p>
<pre><code class="language-python">e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
</code></pre>
<p>那么d参数是什么呢?在d函数里打个断点,调试页面直至进入我们的api接口<code>https://music.163.com/weapi/cloudsearch/get/web?csrf_token=</code>为止<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707753-348979952.png" alt="接口" loading="lazy"></p>
<pre><code class="language-javascript">Y8Q = Y8Q.replace("api", "weapi");
</code></pre>
<p>仔细查看此段代码,这段代码是将链接里的api替换成weapi,因此可以根据Y8Q来定位到当前的链接地址,然后进行下一步调试。<br>
在该行打一个断点,开始调试。<br>
这里有个调试的小技巧,我们可以先在Y8Q那一行打一个断点,先进行调试执行,当Y8Q变为搜索的api接口后,再在window.asrsea()那里打一个断点,然后在d函数打一个断点,进行调试。<br>
<strong>记得在Y8Q那里调试时先刷新一下页面,但是后面调试不要</strong><br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707886-2133118837.png" alt="定位" loading="lazy"><br>
这里我们定位到了api接口,<strong>注意api还没有换成weapi,点击执行下一步后,api被替换了</strong><br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707902-265054086.png" alt="api" loading="lazy"><br>
然后在window.asrsea打一个断点,点击执行<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707801-1629435929.png" alt="在这里插入图片描述" loading="lazy"><br>
然后在d函数打一个断点,点击执行,得到d的值<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707901-1744373683.png" alt="在这里插入图片描述" loading="lazy"><br>
直接将d复制过来,<strong>注意字符串前要加个r,要不然字符串里的''会被当做转义字符处理。</strong></p>
<pre><code class="language-python">d = r'{"hlpretag":"&lt;span class=\"s-fc7\"&gt;","hlposttag":"&lt;/span&gt;","s":"自由の翅","type":"1","offset":"0","total":"true","limit":"30","csrf_token":""}'
e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'
</code></pre>
<p>可以看到,s里面的就是我们搜索的音乐名称了,后面可以更改s的值来改变搜索的音乐。</p>
<h3 id="3函数分析">(3)函数分析</h3>
<p>好了,讲了这么多,结果只分析出四个参数的值是什么,接下来我们发现a,b,c,d这四个函数的作用。</p>
<h4 id="a函数">a函数</h4>
<pre><code class="language-javascript">function a(a) {
      var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
      for (d = 0; a &gt; d; d += 1)
            e = Math.random() * b.length,
            e = Math.floor(e),
            c += b.charAt(e);
      return c
    }
</code></pre>
<p>根据我十分粗糙的js知识来看,这个函数返回的是一个b中的随机字符串,函数接收字符串的长度,我们将它改写成Python代码。</p>
<pre><code class="language-python"># 获取一个随意字符串,length是字符串长度
def generate_str(lenght):
    str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    res = ''
    for i in range(lenght):
      index = random.random() * len(str)# 获取一个字符串长度的随机数
      index = math.floor(index)# 向下取整
      res = res + str# 累加成一个随机字符串
    return res
</code></pre>
<p>其实这个随机字符串完全可以定死,但为了尽量还原js脚本的执行过程,我们还是直接照搬过来吧。</p>
<h4 id="b函数">b函数</h4>
<pre><code class="language-javascript">    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()
    }
</code></pre>
<p>b函数是一个<strong>AES加密过程</strong>,a是加密内容,也就是encText,b是一个key,是一个固定值,也就是上面四个参数中的g</p>
<pre><code class="language-javascript">g = '0CoJUm6Qyw8W8jud'
</code></pre>
<p>加密的模式为CBC,参照Python AES的加密过程,将js代码改写成了Python代码</p>
<pre><code class="language-python"># AES加密获得params
def AES_encrypt(text, key):
    iv = '0102030405060708'.encode('utf-8')# iv偏移量
    text = text.encode('utf-8')# 将明文转换为utf-8格式
    pad = 16 - len(text) % 16
    text = text + (pad * chr(pad)).encode('utf-8')# 明文需要转成二进制,且可以被16整除
    key = key.encode('utf-8')# 将密钥转换为utf-8格式
    encryptor = AES.new(key, AES.MODE_CBC, iv)# 创建一个AES对象
    encrypt_text = encryptor.encrypt(text)# 加密
    encrypt_text = base64.b64encode(encrypt_text)# base4编码转换为byte字符串
    return encrypt_text.decode('utf-8')
</code></pre>
<h4 id="c函数">c函数</h4>
<pre><code class="language-javascript">    function c(a, b, c) {
      var d, e;
      return setMaxDigits(131),
      d = new RSAKeyPair(b,"",c),
      e = encryptedString(d, a)
    }
</code></pre>
<p>c函数是<strong>RSA加密过程</strong>,其中a是随机字符串,b是一个key,也就是上面四个参数中的e,f也是四个参数中的f,返回的是encSeckey</p>
<pre><code class="language-python">e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
</code></pre>
<p>由于本人学习的js知识十分粗浅,因此难以看懂这段代码,参考网上流传的版本,改写了代码</p>
<pre><code class="language-python"># RSA加密获得encSeckey
def RSA_encrypt(str, key, f):
    str = str[::-1]# 随机字符串逆序排列
    str = bytes(str, 'utf-8')# 将随机字符串转换为byte类型的数据
    sec_key = int(codecs.encode(str, encoding='hex'), 16) ** int(key, 16) % int(f, 16)# RSA加密
    return format(sec_key, 'x').zfill(256)# RSA加密后字符串长度为256,不足的补x
</code></pre>
<p>RSA加密规则不是很熟悉,感兴趣的自行百度</p>
<h4 id="d函数">d函数</h4>
<pre><code class="language-javascript">    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
    }
</code></pre>
<p>最后就是d函数了,i是一个16位的随机字符串,可以定死,使用b函数先对d函数进行了AES加密,由于d,g都是固定值,所以得到的encText也是固定值,<strong>可以通过调试来获得第一次加密后的encText,然后在运行一下你的Python代码,查看encText是否一致,用来验证d是否正确。</strong><br>
第一次加密得到encText,再次对encText进行第二次加密,不过key换成了随机字符串i,两次加密后得到encText。</p>
<pre><code class="language-python"># 获取参数
def get_params(d, e, f, g):
    i = generate_str(16)    # 生成一个16位的随机字符串
    # i = 'aO6mqZksdJbqUygP'
    encText = AES_encrypt(d, g)
    # print(encText)    # 打印第一次加密的params,用于测试d正确
    params = AES_encrypt(encText, i)# AES加密两次后获得params
    encSecKey = RSA_encrypt(i, e, f)# RSA加密后获得encSecKey
    return params, encSecKey
</code></pre>
<p>至此,参数params和encSecKey都解析完毕,由于字符串是随机的,因此每次运行后得到的params和encSecKey都不一样。</p>
<h3 id="4分析返回结果">(4)分析返回结果</h3>
<p>知道参数和接口后,就可以向服务器发送请求,获取返回结果了。<strong>注意请求为post请求</strong>。<br>
由于后续api的解析过程基本一致,因此将代码封装起来。</p>
<pre><code class="language-python">e = '010001'
f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
g = '0CoJUm6Qyw8W8jud'

# 传入msg和url,获取返回的json数据
def get_data(msg, url):
    encText, encSecKey = get_params(msg, e, f, g)   # 获取参数
    params = {
      "params": encText,
      "encSecKey": encSecKey
    }
    re = requests.post(url=url, params=params, verify=False)    # 向服务器发送请求
    return re.json()    #返回结果

# 搜索返回的数据
serch_msg = r'{"hlpretag":"&lt;span class=\"s-fc7\"&gt;","hlposttag":"&lt;/span&gt;","s":"自由の翅","type":"1","offset":"0","total":"true","limit":"30","csrf_token":""}'
serch_url = 'https://music.163.com/weapi/cloudsearch/get/web?csrf_token='
print(get_data(serch_msg, serch_url))
</code></pre>
<p>三个参数e,f,g固定不变,只更改msg的值。<br>
<strong>注意msg字符串前要加个r,防止编译器将字符串里的\当做转义字符处理。</strong><br>
返回结果如下</p>
<pre><code class="language-bash">{
    "needLogin": true,
    "result": {
      "searchQcReminder": null,
      "songs": [
            {
                "name": "自由の翅",
                "id": 473403600,
                "pst": 0,
                "t": 0,
                "ar": [
                  {
                        "id": 17672,
                        "name": "佐藤ひろ美",
                        "tns": [
                            "佐藤裕美"
                        ],
                        "alias": [
                            "さとう ひろみ",
                            "Sato Hiromi"
                        ],
                        "alia": [
                            "さとう ひろみ",
                            "Sato Hiromi"
                        ]
                  }
                ],
                "alia": [
                  "PCゲーム『月影のシミュラクル -解放の羽-』OPテーマ"
                ],
                "pop": 85,
                "st": 0,
                "rt": null,
                "fee": 0,
                "v": 12,
                "crbt": null,
                "cf": "",
                "al": {
                  "id": 35377102,
                  "name": "月影のシミュラクル -解放の羽- オリジナルサウンドトラック",
                  "picUrl": "http://p3.music.126.net/jUm6aclu8k5fNUdwEODz0w==/18598239185710248.jpg",
                  "tns": [],
                  "pic_str": "18598239185710248",
                  "pic": 18598239185710250
                },
                "dt": 276866,
                "h": {
                  "br": 320000,
                  "fid": 0,
                  "size": 11077007,
                  "vd": -79678,
                  "sr": 44100
                },
                "m": {
                  "br": 192000,
                  "fid": 0,
                  "size": 6646222,
                  "vd": -77200,
                  "sr": 44100
                },
                "l": {
                  "br": 128000,
                  "fid": 0,
                  "size": 4430829,
                  "vd": -76001,
                  "sr": 44100
                },
                "sq": {
                  "br": 1052522,
                  "fid": 0,
                  "size": 36426061,
                  "vd": -79658,
                  "sr": 44100
                },
                "hr": null,
                "a": null,
                "cd": "1",
                "no": 1,
                "rtUrl": null,
                "ftype": 0,
                "rtUrls": [],
                "djId": 0,
                "copyright": 0,
                "s_id": 0,
                "mark": 262144,
                "originCoverType": 0,
                "originSongSimpleData": null,
                "tagPicList": null,
                "resourceState": true,
                "version": 12,
                "songJumpInfo": null,
                "entertainmentTags": null,
                "single": 0,
                "noCopyrightRcmd": null,
                "rtype": 0,
                "rurl": null,
                "mst": 9,
                "cp": 663018,
                "mv": 0,
                "publishTime": 1485446400000,
                "tns": [
                  "自由的翅膀"
                ],
                "privilege": {
                  "id": 473403600,
                  "fee": 0,
                  "payed": 0,
                  "st": 0,
                  "pl": 320000,
                  "dl": 999000,
                  "sp": 7,
                  "cp": 1,
                  "subp": 1,
                  "cs": false,
                  "maxbr": 999000,
                  "fl": 320000,
                  "toast": false,
                  "flag": 256,
                  "preSell": false,
                  "playMaxbr": 999000,
                  "downloadMaxbr": 999000,
                  "maxBrLevel": "lossless",
                  "playMaxBrLevel": "lossless",
                  "downloadMaxBrLevel": "lossless",
                  "plLevel": "exhigh",
                  "dlLevel": "lossless",
                  "flLevel": "exhigh",
                  "rscl": null,
                  "freeTrialPrivilege": {
                        "resConsumable": false,
                        "userConsumable": false,
                        "listenType": null
                  },
                  "chargeInfoList": [
                        {
                            "rate": 128000,
                            "chargeUrl": null,
                            "chargeMessage": null,
                            "chargeType": 0
                        },
                        {
                            "rate": 192000,
                            "chargeUrl": null,
                            "chargeMessage": null,
                            "chargeType": 0
                        },
                        {
                            "rate": 320000,
                            "chargeUrl": null,
                            "chargeMessage": null,
                            "chargeType": 0
                        },
                        {
                            "rate": 999000,
                            "chargeUrl": null,
                            "chargeMessage": null,
                            "chargeType": 1
                        }
                  ]
                }
            },
            {
                "name": "自由の翅 (BEAST-Ⅵ Bootleg)",
                "id": 1946185953,
                "pst": 0,
                "t": 0,
                "ar": [
                  {
                        "id": 49024337,
                        "name": "Nero",
                        "tns": [],
                        "alias": []
                  }
                ],
                "alia": [],
                "pop": 5,
                "st": 0,
                "rt": "",
                "fee": 0,
                "v": 3,
                "crbt": null,
                "cf": "",
                "al": {
                  "id": 144732637,
                  "name": "HyperRave01",
                  "picUrl": "http://p3.music.126.net/C3YZ8fAg8TJ1pgTdlAbxnA==/109951167398067153.jpg",
                  "tns": [],
                  "pic_str": "109951167398067153",
                  "pic": 109951167398067150
                },
                "dt": 182987,
                "h": {
                  "br": 320000,
                  "fid": 0,
                  "size": 7321644,
                  "vd": -85382,
                  "sr": 44100
                },
                "m": {
                  "br": 192000,
                  "fid": 0,
                  "size": 4393004,
                  "vd": -83101,
                  "sr": 44100
                },
                "l": {
                  "br": 128000,
                  "fid": 0,
                  "size": 2928684,
                  "vd": -81941,
                  "sr": 44100
                },
                "sq": null,
                "hr": null,
                "a": null,
                "cd": "01",
                "no": 10,
                "rtUrl": null,
                "ftype": 0,
                "rtUrls": [],
                "djId": 0,
                "copyright": 0,
                "s_id": 0,
                "mark": 262144,
                "originCoverType": 0,
                "originSongSimpleData": null,
                "tagPicList": null,
                "resourceState": true,
                "version": 3,
                "songJumpInfo": null,
                "entertainmentTags": null,
                "single": 0,
                "noCopyrightRcmd": null,
                "rtype": 0,
                "rurl": null,
                "mst": 9,
                "cp": 2707442,
                "mv": 0,
                "publishTime": 0,
                "privilege": {
                  "id": 1946185953,
                  "fee": 0,
                  "payed": 0,
                  "st": 0,
                  "pl": 320000,
                  "dl": 320000,
                  "sp": 7,
                  "cp": 1,
                  "subp": 1,
                  "cs": false,
                  "maxbr": 320000,
                  "fl": 320000,
                  "toast": false,
                  "flag": 128,
                  "preSell": false,
                  "playMaxbr": 320000,
                  "downloadMaxbr": 320000,
                  "maxBrLevel": "exhigh",
                  "playMaxBrLevel": "exhigh",
                  "downloadMaxBrLevel": "exhigh",
                  "plLevel": "exhigh",
                  "dlLevel": "exhigh",
                  "flLevel": "exhigh",
                  "rscl": null,
                  "freeTrialPrivilege": {
                        "resConsumable": false,
                        "userConsumable": false,
                        "listenType": null
                  },
                  "chargeInfoList": [
                        {
                            "rate": 128000,
                            "chargeUrl": null,
                            "chargeMessage": null,
                            "chargeType": 0
                        },
                        {
                            "rate": 192000,
                            "chargeUrl": null,
                            "chargeMessage": null,
                            "chargeType": 0
                        },
                        {
                            "rate": 320000,
                            "chargeUrl": null,
                            "chargeMessage": null,
                            "chargeType": 0
                        },
                        {
                            "rate": 999000,
                            "chargeUrl": null,
                            "chargeMessage": null,
                            "chargeType": 1
                        }
                  ]
                }
            }
      ],
      "songCount": 2
    },
    "code": 200
}
</code></pre>
<p>这里简单说明一下,code的响应状态,可以根据这个来判断请求是否成功,<br>
result里面,songCount代表搜索结果有几个,song里面是音乐的一些信息,<br>
name,id,ar是艺术家artist的意思,也就是歌手,al是所属专辑,包括名称,封面之类的。<br>
h, l,m,sq分别代表音质的等级,h是极高,l是较高,m的标准,sq是无损。<br>
privilege里面是一些音乐的音质信息,包括可下载的最大音质,会员下载信息等,chargeInfoList列出了各个音质下载所需的权限。</p>
<h2 id="2音乐信息">2、音乐信息</h2>
<h3 id="1歌词">(1)歌词</h3>
<p>点击一个音乐进入播放界面,打开F12,筛选后一个一个链接寻找<code>https://music.163.com/weapi/song/lyric?csrf_token=</code>,此链接返回歌词信息<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707879-2052073649.png" alt="歌词" loading="lazy"></p>
<pre><code class="language-javascript">{
    "sgc": false,
    "sfy": false,
    "qfy": false,
    "transUser": {
      "id": 2204059,
      "status": 99,
      "demand": 1,
      "userid": 76837043,
      "nickname": "烈焰中舞动的火花",
      "uptime": 1493803368844
    },
    "lyricUser": {
      "id": 2204040,
      "status": 99,
      "demand": 0,
      "userid": 114415020,
      "nickname": "another_tonary",
      "uptime": 1493803368844
    },
    "lrc": {
      "version": 5,
      "lyric": " 作词 : 羽生みいな\n 作曲 : Meis Clauson\n自由の翅\n月影のシミュラクル -解放の羽- 0P主題歌\n\nここから見る景色は 何故どこか狭く悲しく ah\n声にならない声で そう君を呼んでいたんだ ah\n逃げられない蝶のように\n最期を待つだけじゃないと\n温かい手 重ねた瞬間(とき)\n差し込んだ光\n絡みつくこの糸が 交わされた契約が\nどれほど命 縛ろうとも\nいつの日かこの翅(はね)を精一杯広げて\n君が傍に居てくれるなら\nきっと飛び立てるの あの空へと\n仕方のないことだと 何故諦めようとしてた ah\n紅く染まる暗闇から 抜け出せない気がして ah\nそれでもまだ君と生きたい\n繫いだ手は震えるけど\n熱い涙 溢れた瞬間(とき)\n湧き上がる勇気\n捕らわれた運命が 立ちはだかる試練が\nどれほどこの身操ろうとも\n立ち向かいたい 強く信じるの もっと強く\n独りじゃないと思えた 君となら\n飛び立てるの あの空へと\n忘れかけてた 遠い記憶 あの約束 思い出して\n取り戻せるの 二人ならば 広い世界を\n絡みつくこの糸が 交わされた契約が\nどれほど命 縛ろうとも\nいつの日かこの翅を 精一杯広げて\n君が傍に居てくれるなら\nきっと 光の向こう\n捕らわれた運命が 立ちはだかる試練が\nどれほどこの身操ろうとも\n立ち向かいたい 強く信じるの もっと強く\n独りじゃないと思えた 君となら\n飛び立てるの あの空へと\n"
    },
    "tlyric": {
      "version": 5,
      "lyric": "\n\n\n\n\n\n\n这里所看到的景色 为何会感到如此狭小又悲伤 ah\n以泣不成声的声音 不断呼喊着你 ah\n如同无法挣脱的蝴蝶一般\n只能默默等候终焉的到来\n温暖的双手重合的瞬间\n感受到了照射的光芒\n不管这纠缠不清的丝线与这被迫签下的契约\n究竟束缚了多少的生命\n总有一天要用这双翅膀 用尽全力展翅翱翔\n只要你能够陪伴在我身边\n一定就能够展翅高飞 向着那片天空\n为何要说着“这是无可奈何的事情”而准备去放弃一切呢 ah\n就算觉得无法从这渐渐染红的黑暗中逃脱出去 ah\n即便如此仍旧想要与你一同活下去\n虽然紧牵着的手止不住颤抖\n温热的泪水 满溢的瞬间\n心中所涌出的勇气\n不管这被囚禁的命运与这艰辛的试炼\n会让这幅身躯会承受多少伤害\n就算如此也想要奋发向上 不断坚信着 变得更加坚强\n与你在一起的话 就不会感到孤独\n向着那片天空展翅高飞\n从将要遗忘的记忆中找回了那个约定\n我们一起的话 就能夺回那个宽广的世界\n不管这纠缠不清的丝线与这被迫签下的契约\n究竟束缚了多少的生命\n总有一天要用这双翅膀 用尽全力展翅翱翔\n只要你能够陪伴在我身边\n肯定就在那光芒的彼岸\n不管这被囚禁的命运与这艰辛的试炼\n会让这幅身躯会承受多少伤害\n就算如此也想要奋发向上 不断坚信着 变得更加坚强\n与你在一起的话 就不会感到孤独\n向着那片天空展翅高飞"
    },
    "code": 200
}
</code></pre>
<p>其中transUser为歌词贡献者,lyricUser为歌词翻译贡献者,lrc里有原版歌词,tlyric里有歌词翻译。<br>
解析过程和上面一样,调试页面找到d的值即可。</p>
<pre><code class="language-javascript">d = '{"id":"473403600","lv":-1,"tv":-1,"csrf_token":""}'
</code></pre>
<p>id为音乐id,可更改。</p>
<pre><code class="language-python"># 歌词文件
lyric_msg = '{"id":"427419615","lv":-1,"tv":-1,"csrf_token":""}'
lyric_url = 'https://music.163.com/weapi/song/lyric?csrf_token='
print(get_data(lyric_msg, lyric_url))
</code></pre>
<h3 id="2评论">(2)评论</h3>
<p><code>https://music.163.com/weapi/comment/resource/comments/get?csrf_token=</code>返回用户评论信息,目前还不需要,不使用。<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707912-1062523794.png" alt="评论" loading="lazy"></p>
<h3 id="3音乐信息">(3)音乐信息</h3>
<p>点击蓝色的播放按钮,发现由多出了一些链接,一个一个找下来。<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707928-905470422.png" alt="链接" loading="lazy"><br>
<code>https://music.163.com/weapi/v3/song/detail?csrf_token=</code>返回音乐的详细信息。<br>
<strong>注意,这里调试时需要一点技巧,刷新页面,首先在源码里打个断点</strong><br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707766-1932658680.png" alt="断点" loading="lazy"><br>
点击播放,<strong>注意必须是蓝色的那个播放按钮</strong>。<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707940-2034096684.png" alt="在这里插入图片描述" loading="lazy"><br>
然后调试获得api地址<code>https://music.163.com/weapi/v3/song/detail</code>和d<br>
<strong>继续调试可获得音乐的下载地址。</strong><br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707760-2111627517.png" alt="地址" loading="lazy"></p>
<pre><code class="language-python"># 音乐详细信息,包含了音质等级和可下载权限
detail_msg = r'{"id":"473403600","c":"[{\"id\":\"473403600\"}]","csrf_token":""}'
detail_url = 'https://music.163.com/weapi/v3/song/detail?csrf_token='
print(get_data(detail_msg, detail_url))
</code></pre>
<p>注意msg要加上r,id为音乐id,可更改<br>
其实没有这个也行,在搜索时,返回的数据里就有音乐的详细信息了。</p>
<h3 id="4下载链接">(4)下载链接</h3>
<p><code>https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=</code>返回的是下载链接,<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707921-1053099607.png" alt="下载地址" loading="lazy"><br>
调试找到d,</p>
<pre><code class="language-python"># 音乐下载地址,level代表音质等级,encodeType代表编码类型,flac可存储无损音质,目前无法下载无损音乐
# 音质 standard标准 higher较高 exhigh极高 lossless无损 hires
# 编码类型 aac flac
song_msg = '{"ids":"","level":"lossless","encodeType":"flac","csrf_token":""}'
song_url = 'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token='
print(get_data(song_msg, song_url))
</code></pre>
<p>ids里面是音乐id,既然是一个数组,那想必可以多加几个音乐id,返回多个下载地址。level为音质水平,分四个等级,standard代表标准,higher代表较高,exhigh代表极高,lossless代表无损,还有hires,其中lossless和hires都无法下载,不知道加上有会员权限的csrf_token能不能下载。</p>
<h2 id="3音乐播放器外链">3、音乐播放器外链</h2>
<p>在网页版有一个功能叫生成外链播放器,点击一下<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155708091-806669062.png" alt="外链播放器" loading="lazy"><br>
它会叫你嵌入一段代码<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707946-809009044.png" alt="嵌入" loading="lazy"><br>
我们将src里面的内容复制出来,添加上头部组成https://music.163.com/outchain/player?type=2&amp;id=473403600&amp;auto=1&amp;height=66<br>
你可以嵌入自己的网站中去(如果你的位置支持嵌入ifram的话),也可以自己写一个前端播放器,然后爬音乐信息,将数据放进去。<br>
打开开发者工具,这里也有两个可用的api接口<br>
一个是音乐详细信息<code>https://music.163.com/weapi/song/detail</code><br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707850-283323070.png" alt="详情" loading="lazy"><br>
另外一个是音乐下载地址<code>https://music.163.com/weapi/song/enhance/player/url</code><br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707906-477262328.png" alt="在这里插入图片描述" loading="lazy"><br>
调试页面获取d</p>
<pre><code class="language-python"># # 通过外链播放器获取解析的链接
# # 音乐下载地址,br代表音质,依旧无法下载无损音乐
# br四个等级 标准128000 较高192000 极高320000 无损999000
song_msg = '{"ids":"","br":1052522,"csrf_token":""}'
song_url = 'https://music.163.com/weapi/song/enhance/player/url'
print(get_data(song_msg, song_url))
#
# # 音乐详情,有更加详细的信息
detail_msg = r'{"id":"473403600","ids":"[\"473403600\"]","limit":10000,"offset":0,"csrf_token":""}'
detail_url = 'https://music.163.com/weapi/song/detail'
print(get_data(detail_msg, detail_url))
</code></pre>
<p>返回的数据和前面的差不多,稍微有点出入。</p>
<h1 id="三其他api">三、其他api</h1>
<p>还记得上面提到过的一段代码吗?</p>
<pre><code class="language-javascript">Y8Q = Y8Q.replace("api", "weapi");
</code></pre>
<p>这段代码将链接里的api换成了weapi,如果我们用原来的链接会怎么样?<br>
以歌词文件的api为例<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707842-1433946523.png" alt="在这里插入图片描述" loading="lazy"><br>
补上前缀得到<code>https://music.163.com/api/song/lyric</code>,当然,现在这个链接还用不了,需要传递参数。<br>
展开query,发现里面一些关于音乐的参数,带入到链接里去,https://music.163.com/api/song/lyric?id=473403600&amp;lv=-1&amp;tv=-1<br>
此请求为get请求,自己获取返回数据。<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707733-1800755018.png" alt="在这里插入图片描述" loading="lazy"><br>
有没有发现这个lv=-1,tv=-1这么像d里面的参数?</p>
<pre><code class="language-python">d = '{"id":"427419615","lv":-1,"tv":-1,"csrf_token":""}'
</code></pre>
<p>对比一下就知道了。<br>
<img src="https://img2023.cnblogs.com/blog/2190492/202211/2190492-20221130155707938-776258230.png" alt="在这里插入图片描述" loading="lazy"><br>
几乎一致,通过更改参数的值,发现返回的结果,可以知道各个参数的含义。<br>
其他api类似,如果你不想模拟js的加密过程,可以使用这些api直接获取到数据。<br>
更多api接口请查看接口文档。注意如果要下载会员音乐和无损音乐(如果有的话)的话,要使用具有会员权限的账号cookie,post请求放cookies里,get请求放headers里。</p>
<h1 id="四最终实现代码">四、最终实现代码</h1>
<p>weapi接口的代码实现</p>
<pre><code class="language-python">"""
webapi接口
搜索结果:https://music.163.com/weapi/cloudsearch/get/web?csrf_token=(post)
评论:https://music.163.com/weapi/comment/resource/comments/get?csrf_token=
歌词:https://music.163.com/weapi/song/lyric?csrf_token=
详情(包括音质):https://music.163.com/weapi/v3/song/detail?csrf_token=
歌曲下载:https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=

iw233网站解析链接
https://iw233.cn/music/?name=コトダマ紬ぐ未来&amp;type=netease

外链:http://music.163.com/song/media/outer/url?id=534544522.mp3

音乐外链播放器:https://music.163.com/outchain/player?type=2&amp;id=473403600&amp;auto=1&amp;height=66
"""
import base64
import codecs
import json
import math
import random

import requests
from Crypto.Cipher import AES
from urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

'''

var bKB3x = window.asrsea(JSON.stringify(i3x), buU1x(["流泪", "强"]), buU1x(Rg7Z.md), buU1x(["爱心", "女孩", "惊恐", "大笑"]));
            e3x.data = j3x.cr3x({
                params: bKB3x.encText,
                encSecKey: bKB3x.encSecKey
            })


    window.asrsea = d,

    d: {"hlpretag":"&lt;span class="s-fc7"&gt;","hlposttag":"&lt;/span&gt;","s":"コトダマ紬ぐ未来","type":"1","offset":"0","total":"true","limit":"30","csrf_token":""}
    e:010001
    f:00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7
    g:0CoJUm6Qyw8W8jud
      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 a(a) {
      var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
      for (d = 0; a &gt; 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)
    }
'''

class wangyiyun:
    def __init__(self):
      self.e = '010001'
      self.f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
      self.g = '0CoJUm6Qyw8W8jud'

    # 获取一个随意字符串,length是字符串长度
    def generate_str(self, lenght):
      str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
      res = ''
      for i in range(lenght):
            index = random.random() * len(str)# 获取一个字符串长度的随机数
            index = math.floor(index)# 向下取整
            res = res + str# 累加成一个随机字符串
      return res

    # AES加密获得params
    def AES_encrypt(self, text, key):
      iv = '0102030405060708'.encode('utf-8')# iv偏移量
      text = text.encode('utf-8')# 将明文转换为utf-8格式
      pad = 16 - len(text) % 16
      text = text + (pad * chr(pad)).encode('utf-8')# 明文需要转成二进制,且可以被16整除
      key = key.encode('utf-8')# 将密钥转换为utf-8格式
      encryptor = AES.new(key, AES.MODE_CBC, iv)# 创建一个AES对象
      encrypt_text = encryptor.encrypt(text)# 加密
      encrypt_text = base64.b64encode(encrypt_text)# base4编码转换为byte字符串
      return encrypt_text.decode('utf-8')

    # RSA加密获得encSeckey
    def RSA_encrypt(self, str, key, f):
      str = str[::-1]# 随机字符串逆序排列
      str = bytes(str, 'utf-8')# 将随机字符串转换为byte类型的数据
      sec_key = int(codecs.encode(str, encoding='hex'), 16) ** int(key, 16) % int(f, 16)# RSA加密
      return format(sec_key, 'x').zfill(256)# RSA加密后字符串长度为256,不足的补x

    # 获取参数
    def get_params(self, d, e, f, g):
      i = self.generate_str(16)    # 生成一个16位的随机字符串
      # i = 'aO6mqZksdJbqUygP'
      encText = self.AES_encrypt(d, g)
      # print(encText)    # 打印第一次加密的params,用于测试d正确
      params = self.AES_encrypt(encText, i)# AES加密两次后获得params
      encSecKey = self.RSA_encrypt(i, e, f)# RSA加密后获得encSecKey
      return params, encSecKey

    # 传入msg和url,获取返回的json数据
    def get_data(self, msg, url):
      encText, encSecKey = self.get_params(msg, self.e, self.f, self.g)   # 获取参数
      params = {
            "params": encText,
            "encSecKey": encSecKey
      }
      re = requests.post(url=url, params=params, verify=False)    # 向服务器发送请求
      return re.json()    #返回结果

    # 返回搜索数据
    def get_search_data(self, s='', type=1, offset=0, total='true', limit=30, csrf_token=''):
      msg = r'{"hlpretag":"&lt;span class=\"s-fc7\"&gt;","hlposttag":"&lt;/span&gt;",' + f'"s":"{s}","type":"{type}","offset":"{offset}","total":"{total}","limit":"{limit}","csrf_token":"{csrf_token}"' + '}'
      url = f'https://music.163.com/weapi/cloudsearch/get/web?csrf_token={csrf_token}'
      return self.get_data(msg, url)

    # 返回歌词数据
    def get_lyric_data(self, id, lv=-1, tv=-1, csrf_token=''):
      msg = '{' + f'"id":"{id}","lv":"{lv}","tv":"{tv}","csrf_token":"{csrf_token}"' + '}'
      url = f'https://music.163.com/weapi/song/lyric?csrf_token={csrf_token}'
      return self.get_data(msg, url)

    # 返回音乐详情,包含了音质等级和可下载权限
    def get_detail_data(self, id, csrf_token=''):
      msg = '{' + f'"id":"{id}",' + r'"c":"[{\"id\":\"' + str(id) + r'\"}]",' + f'"csrf_token":"{csrf_token}"' + '}'
      url = f'https://music.163.com/weapi/v3/song/detail?csrf_token={csrf_token}'
      returnself.get_data(msg, url)

    # 返回下载数据,level代表音质等级,encodeType代表编码类型,flac可存储无损音质,目前无法下载无损音乐
    # # 音质 standard标准 higher较高 exhigh极高 lossless无损 hires
    # # 编码类型 aac flac
    def get_download_data(self, id, level='exhigh', encodeType='flac', csrf_token=''):
      msg = '{' + f'"ids":"{id}","level":"{level}","encodeType":"{encodeType}","csrf_token":"{csrf_token}"' + '}'
      url = f'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token={csrf_token}'
      return self.get_data(msg, url)

    # 通过播放器外链的方式返回的音乐详情数据
    def get_detail_outdata(self, id, limit=10000, offset=0, csrf_token=''):
      msg = '{' + f'"id":"{id}",' + r'"ids":"[\"' + str(id) + r'\"]",' + f'"limit":{limit},"offset":{offset},"csrf_token":"{csrf_token}"' + '}'
      url = 'https://music.163.com/weapi/song/detail'
      return self.get_data(msg, url)

    # 通过播放器外链的方式返回的音乐下载数据
    # br代表音质,四个等级 标准128000 较高192000 极高320000 无损999000
    def get_download_outdata(self, id, br=320000, csrf_token=''):
      msg = '{' + f'"ids":"{id}","br":{br},"csrf_token":"{csrf_token}"' + '}'
      url = 'https://music.163.com/weapi/song/enhance/player/url'
      return self.get_data(msg, url)
</code></pre>
<p>api接口的代码实现</p>
<pre><code class="language-python">'''
api接口
'''

import json
import requests
from urllib3.exceptions import InsecureRequestWarning

requests.packages.urllib3.disable_warnings(InsecureRequestWarning)


class wangyiyun:
    # 获取数据
    def get_data(self, url, data):
      re = requests.post(url=url, data=data, verify=False)
      return re.json()
    # 返回搜索数据
    def get_search_data(self, s='', type=1, offset=0, total='true', limit=30, csrf_token=''):
      url = 'https://music.163.com/api/cloudsearch/get/web'
      data = {
            'hlpretag': '&lt;span class="s-fc7"&gt;',
            'hlposttag': '&lt;/span&gt;',
            's': s,
            'type': type,
            'offset': offset,
            'total': total,
            'limit': limit,
            'csrf_token': csrf_token
      }
      return self.get_data(url, data)

    # 返回歌词数据
    def get_lyric_data(self, id, lv=-1, tv=-1, csrf_token=''):
      url = 'https://music.163.com/api/song/lyric'
      data = {
            'id': id,
            'lv': lv,
            'tv': tv,
            'csrf_token': csrf_token
      }
      return self.get_data(url, data)

    # 返回音乐详情,包含了音质等级和可下载权限
    def get_detail_data(self, id, csrf_token=''):
      url = 'https://music.163.com/api/v3/song/detail'
      c = '[{' + f'"id":"{id}"' + '}]'
      data = {
            'id': id,
            'c': c,
            'csrf_token': csrf_token
      }
      returnself.get_data(url, data)

    # 返回下载数据,level代表音质等级,encodeType代表编码类型,flac可存储无损音质,目前无法下载无损音乐
    # # 音质 standard标准 higher较高 exhigh极高 lossless无损 hires
    # # 编码类型 aac flac
    def get_download_data(self, id, level='exhigh', encodeType='flac', csrf_token=''):
      url = 'https://music.163.com//api/song/enhance/player/url/v1'
      data = {
            'encodeType': encodeType,
            'ids': str(id),
            'level': level,
            'csrf_token': csrf_token
      }
      return self.get_data(url, data)

    # 通过播放器外链的方式返回的音乐详情数据
    def get_detail_outdata(self, id, limit=10000, offset=0, csrf_token=''):
      url = 'https://music.163.com/api/song/detail'
      data = {
            'id': id,
            'ids': f'[{str(id)}]',
            'limit': limit,
            'offset': offset,
            'csrf_token': csrf_token
      }
      return self.get_data(url, data)

    # 通过播放器外链的方式返回的音乐下载数据
    # br代表音质,四个等级 标准128000 较高192000 极高320000 无损999000
    def get_download_outdata(self, id, br=320000, csrf_token=''):
      url = 'https://music.163.com/api/song/enhance/player/url'
      data = {
            'br': br,
            'ids': str(id),
            'csrf_token': csrf_token
      }
      return self.get_data(url, data)
</code></pre>
<h1 id="五总结">五、总结</h1>
<p>全部过程爬取下来,发现网易云对数据的加密方式还是挺单调的,只要弄懂了原理就好办了,基本上都一致。<br>
后续考虑做个音乐播放器,添加网易云,QQ,酷狗,百度等源,等做好了再发个教程和项目地址,不过感觉用处也不大,毕竟不能下载vip音乐和无损音乐,就当做是学习了。<br>
小伙伴们有什么不懂的地方可以私信我,也可以在评论区留言。</p><br><br>
来源:https://www.cnblogs.com/pikeduo/p/16938738.html
頁: [1]
查看完整版本: python爬虫爬取网易云音乐(超详细教程,附源码)