在网易云音乐网页版上加下载按键进行下载歌曲

源由

每一次放假回家的时候,都会帮家里面的人下载歌曲,当然差不多用的都是网易云音乐网页版,但是只有客户端才提供下载按键,所以每次都是从 Chrome 的开发者工具的 Network 中找链接,进行下载。所以我就想着能不能用 JS 代码来实现一个按键,一键就可以下载音乐。其实我可以直接用客户端来进行下载的,但随便为了学习一下,用 JS 来实现。

原理

其实原理很简单,先打开 Chrome 的开发者工具的 Network 中,数据类型选为 Media,再打开网易云音乐的界面,点击一首歌,即可看到数据的 URL 地址,下载即可。
network
但这里的缺点是,重复操作,而且下载也没有歌曲名,你需要一个一个的下载,一个一个的更改歌名,所以接下来我打算解决两个点:

1.  点击下载。
2.  自动复制歌曲名。

代码解决

思路

大概看云音乐界面情况,可知播放控制栏为固定的,则可以肯定,数据是通过 Ajax 发送,获取歌曲的 URL 地址,再进行加载播放。

在开发者的 Network 中记录了所有的请求数据,请求的数据是按照时间来排序的,我们按照刚才的原理可以获取到歌曲的 URL 地址,所以在该歌曲的请求数据上面,肯定会有一个发送 Ajax 的请求,来请求歌曲的 URL 地址信息。

所以我的思路是这样的,找到该 Ajax 请求函数,模拟发送歌曲信息请求,获取歌曲的 URL 地址,然后进行下载。

我们把 Network 中的数据类型选中为 All,即可看到发送的 Ajax 歌曲信息请求。
network
在 Network 的 Name 栏可看到 55ea198fb 开头(最后一个)的请求,是发送的歌曲请求,所以在上面会有一个歌曲信息的请求,我们边往上找边看请求的返回数据,大概知道该歌曲信息请求的返回值中肯定有歌曲的 URL 地址。在往上的第四条我们发现了该请求。

将该条目的具体信息关掉,也就是点击 Headers 左边的小叉叉,即可看到:
发送 Ajax 的文件
发送该 Ajax 请求的是云音乐的 core.js 文件。

将 core.js 文件下载下来,再格式化代码,为什么我会把该代码下载下来呢,我是这么想的他发送 Ajax 请求,可能会是先填入发送的相关 URL 地址,然后需要的时候再加上参数调用发送,所以我们还需要知道发送该 Ajax 的具体 URL 地址是什么,在 Network 中点击发送获取歌曲信息的条目,可看到为:

Request URL: http://music.163.com/weapi/song/enhance/player/url?csrf_token=

将该 URL 在格式化后的 core.js 代码中进行查找。我们首先查找 weapi/song/enhance/player/url 无果,再次去除一些,再查找 song/enhance/player/url
core.js 代码片段
当看到 type: “json” 这个字段时,我们大胆的猜测 v5A.bl5q() 即为发送 Ajax 的函数。

Ajax 请求函数

我们再一次的全局搜索 v5A.bl5q 的定义代码,发送有一处代码为:
Ajax 请求的代码
在该代码中我们发现:

j5o["csrf_token"] = v5A.gC7v("__csrf");
Y5d = Y5d.replace("api", "weapi");

我们很肯定这个就是云音乐发送 Ajax 的代码,只不过被混淆了,不过我们可以进行黑箱操作,我们把该代码块提取出来,也就是 (function() { … })(); 中的代码,我们把提取出来的代码,复制在游览器云音乐页面的开发者工具 Console 中,但报错了:
错误信息
点击最右的超链接,发现原来代码在 if (v5A.bl5q.redefine) return; 返回了,这里应该是为了避免重复定义 Ajax 接口设置的,在这里不管,我们直接把他去掉,即可运行成功了,此时我们就可以在 Console 中,调用 v5A.bl5q() 发送 Ajax 请求了。

v5A.bl5q("/api/song/enhance/player/url", {
            type: "json",
            query: {
                ids: JSON.stringify([this.cP6J.id]),
                br: DEFAULT_BR
            },
            onload: this.bGl1x.f5k(this),
            onerror: this.bGl1x.f5k(this)
        })

可以在 Console 中输入这个,即可获得歌曲的 URL 地址,但这里要发送有两个参数,一个是 ids、br。ids 应该是歌曲的 id 进行加密,所以this.cP6J.id 为歌曲的 id。全局找到 DEFAULT_BR,可知道这个是一个变量,我猜测这里应该是音质的选择:

var DEFAULT_BR = 128e3;

音质的选择
嗯,对的,就是音质的选择,那我们可以将该 DEFAULT_BR = 320e3 获取极高的音质。

onload 请求成功时调用,onerror 请求失败是调用,我们可以用下面这个函数来打印响应的数据。

var a = function() {console.log(arguments)} 

ids: JSON.stringify([id]),id 为歌曲的 id,点击一首歌的主页可看见 URL 为 http://music.163.com/#/song?id=418602116, 即 id 为418602116,br 为320e3,将该请求代码复制到 Console 中,可将 onerror 函数去掉。
测试请求
即可看到,该请求成功,在返回的数据中,已经得到了该 id 对应歌曲的 URL 地址。

此时我们已经知道获取歌曲 URL 地址的 Ajax 函数。

若参数还无法确定时,可采用在线调试云音乐的 core.js 代码,利用我之前写的在线调试 JS 的方法(点击即可访问),获取 Ajax 发送的请求数据,即可以在代码中输出对应的参数信息,即可观察到。

   b4f.bFx0x = function() {
        this.zJ3x();
        console.log("************debug***********请求数据")
        console.log(this.cP6J.id)
        console.log(DEFAULT_BR)
        console.log("************debug***********请求数据")
        v5A.bl5q("/api/song/enhance/player/url", {
            type: "json",
            query: {
                ids: JSON.stringify([this.cP6J.id]),
                br: DEFAULT_BR
            },
            onload: this.bGl1x.f5k(this),
            onerror: this.bGl1x.f5k(this)
        })
    };

获取 id 和歌名

在偶然间我发现,云音乐有一个 window.player 对象:
player 对象
这里面有一个 getPlaying() 函数,里面记录了当前播放音乐的信息:
当前的信息
所以我们可以通过这个来获取到 id 和歌名

var track = window.player.getPlaying().track
// 歌曲 id
var id = track.id
// 歌手名
var singer = track.artists.map(e => e.name).join('/');
// 歌曲名
var song = track.name;

var name = `${singer} - ${song}.mp3`;

点击下载

我们通过上面,已经得到发送 Ajax 的函数和需要的参数,所以我们可以在页面中插入一个固定位置的超链接,点击即可下载。

// 添加 css
var css = `
        <style>
        .ztz-easyd {position:relative; top:70%; font-size:15px; left:40px; color:black;}
         </style>`
var html = `<a class="ztz-easyd" 
        hidefocus="true"
        onclick="downloadCurrent(event)">下载</a>`

document.head.insertAdjacentHTML('beforeEnd', css)
document.body.insertAdjacentHTML('beforeEnd', html);

然后再 downloadCurrent() 加入下载的逻辑,先获取到歌曲 id,再发送 Ajax 请求歌曲信息,然后得到的歌曲 URL 地址,再用 window.open(mp3Url) 打开。

经过尝试后,发现一个问题,Chrome 游览器会拦截这个弹窗,这样不太友好。

利用 a 标签

window.open(mp3Url) 替换为设置该下载也就是 a 标签的 href 属性值的后,可弹出。

有个问题,点击第一次时,并没有弹出,因为点击时,href 属性没有值,href 的值是点击函数中赋值的,点击第二次时,可访问但是访问的是前一次的歌曲 URL,这次的歌曲 URL 还未赋值给 href。这是一个问题,但我们也可以解决的。

当点开 a 标签时,新的页面是一个 audio 标签播放界面。所以我就想,直接在云音乐页面中插入 audio 标签,并且是可视的,这样一点击就可以下载了,就可以避免上面的 href 赋值问题了。

利用 audio 标签

所以该下载的 a 标签,改名为加载,意思就是加载 audio 标签。

云音乐的播放过程是这样的,请求下来的歌曲 URL,放入 audio 标签中,即可播放,而在 Chrome 游览器中,默认的 audio 的控制栏中有下载按键。

// css 属性
var css = `
     <style>
        .ztz-down {position:relative; top:75%; width:200px; left:-30px;}
     </style>`
document.head.insertAdjacentHTML('beforeEnd', css)

// 停止页面的播放
window.player.pause();

var audio = new Audio();
audio.src = mp3Url;
audio.controls = true;
audio.setAttribute('class', 'ztz-down');
audio.removeAttribute('preload');
document.body.appendChild(audio);

更改歌曲名

a 标签的 download 属性

a 标签中有一个 download 属性,可以支持设置下载名,但仅限于同源的,而他这个歌曲 URL 是不同源的。

利用 Ajax 请求歌曲内容

我又想着,再通过 Ajax 请求歌曲 URL,这样就能把返回的歌曲内容转化为 base64,再加到 a 标签的 src 中,这样就不受同源策略影响了,download 属性就可以用了。

但尝试了无解,Ajax 禁止非同源加载数据,而且请求歌曲 URL,不能使用 CORS(跨域资源共享 )。

这里也发现了一个问题,数据时请求下来的,受游览器的同源安全策略,数据只是没有在游览器中显示。

利用后台

其实这里用后台来解决的话,很方便,弄一个支持 CORS 的后台,请求的时候把歌曲的 URL 地址和歌曲的名字(FILENAME)发过去,然后返回数据,在响应头中设置 MIME,这样下载的时候,就会自带名字。

Content-Disposition: attachment; filename=FILENAME

设置下载前询问

我们也可以在下载的时候,更改名字,Chrome 默认的是,一点击就下载,这里我们就无法进行更改名字了,所以应该把 Chrome 下载前询问打开,方便我们进行修改名字。

打开 Chrome 设置 -> 高级 -> 下载内容 -> 打开下载前询问每个文件的保存位置 

具体操作

利用剪贴板

我们更改文件名是采用设置下载前询问

到目前为止我们已经解决了点击下载,就剩下 自动复制歌曲名了

每次上 github 下载项目时,都会有一个点击即可复制下载链接,我通过搜索大概知道了原理,利用的是剪贴板实现的功能,用户点击时,自动把内容复制。

我们采用clipboard 这个库,具体使用方法点击,大概如下:

1.  引用该 JS 文件。
2.  设置可自动复制的 class,运行 new ClipboardJS('.btn'); btn 为class
3.  设置需要点击复制的超链接或者按键的 classdata-clipboard-text 属性,前者就是第二步设置的可自动复制的 class,后者就是复制的内容。

代码逻辑

1.  云音乐页面中插入 a 标签,并有点击事件。
2.  点击后,暂停播放,生成 audio 标签,并复制了歌名。

具体代码点击代码

操作流程

  1. 点击 复制里面的全部 JS 代码。
  2. 复制到游览器云音乐界面的开发者工具的 Console 中,回车运行。关闭开发者工具。
  3. 打开 Chrome 下载文件前询问,设置下载前询问
  4. 播放需要下载的歌,然后点击加载,再点击 audio 的下载图标,粘贴(Ctrl+C)更改名字后下载即可。

操作界面

总结

多尝试,多观察,就能知道哪些能做,哪些不能做。

猜你喜欢

转载自blog.csdn.net/zwt520123/article/details/80379244