- 源由
- 原理
- 代碼解決
- 思路
- Ajax 請求函數
- 擷取 id 和歌名
- 點選下載下傳
- 利用 a 标簽
- 利用 audio 标簽
- 更改歌曲名
- a 标簽的 download 屬性
- 利用 Ajax 請求歌曲内容
- 利用背景
- 設定下載下傳前詢問
- 利用剪貼闆
- 代碼邏輯
- 操作流程
- 總結
源由
每一次放假回家的時候,都會幫家裡面的人下載下傳歌曲,當然差不多用的都是網易雲音樂網頁版,但是隻有用戶端才提供下載下傳按鍵,是以每次都是從 Chrome 的開發者工具的 Network 中找連結,進行下載下傳。是以我就想着能不能用 JS 代碼來實作一個按鍵,一鍵就可以下載下傳音樂。其實我可以直接用用戶端來進行下載下傳的,但随便為了學習一下,用 JS 來實作。
原理
其實原理很簡單,先打開 Chrome 的開發者工具的 Network 中,資料類型選為 Media,再打開網易雲音樂的界面,點選一首歌,即可看到資料的 URL 位址,下載下傳即可。

但這裡的缺點是,重複操作,而且下載下傳也沒有歌曲名,你需要一個一個的下載下傳,一個一個的更改歌名,是以接下來我打算解決兩個點:
1. 點選下載下傳。
2. 自動複制歌曲名。
代碼解決
思路
大概看雲音樂界面情況,可知播放控制欄為固定的,則可以肯定,資料是通過 Ajax 發送,擷取歌曲的 URL 位址,再進行加載播放。
在開發者的 Network 中記錄了所有的請求資料,請求的資料是按照時間來排序的,我們按照剛才的原理可以擷取到歌曲的 URL 位址,是以在該歌曲的請求資料上面,肯定會有一個發送 Ajax 的請求,來請求歌曲的 URL 位址資訊。
是以我的思路是這樣的,找到該 Ajax 請求函數,模拟發送歌曲資訊請求,擷取歌曲的 URL 位址,然後進行下載下傳。
我們把 Network 中的資料類型選中為 All,即可看到發送的 Ajax 歌曲資訊請求。
在 Network 的 Name 欄可看到 55ea198fb 開頭(最後一個)的請求,是發送的歌曲請求,是以在上面會有一個歌曲資訊的請求,我們邊往上找邊看請求的傳回資料,大概知道該歌曲資訊請求的傳回值中肯定有歌曲的 URL 位址。在往上的第四條我們發現了該請求。
将該條目的具體資訊關掉,也就是點選 Headers 左邊的小叉叉,即可看到:
發送該 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
當看到 type: “json” 這個字段時,我們大膽的猜測 v5A.bl5q() 即為發送 Ajax 的函數。
Ajax 請求函數
我們再一次的全局搜尋 v5A.bl5q 的定義代碼,發送有一處代碼為:
在該代碼中我們發現:
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,可知道這個是一個變量,我猜測這裡應該是音質的選擇:
嗯,對的,就是音質的選擇,那我們可以将該 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 對象:
這裡面有一個 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:%; font-size:px; left:px; 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:%; width:px; left:-px;}
</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,這樣下載下傳的時候,就會自帶名字。
設定下載下傳前詢問
我們也可以在下載下傳的時候,更改名字,Chrome 預設的是,一點選就下載下傳,這裡我們就無法進行更改名字了,是以應該把 Chrome 下載下傳前詢問打開,友善我們進行修改名字。
打開 Chrome 設定 -> 進階 -> 下載下傳内容 -> 打開下載下傳前詢問每個檔案的儲存位置
具體操作。
利用剪貼闆
我們更改檔案名是采用設定下載下傳前詢問,
到目前為止我們已經解決了點選下載下傳,就剩下 自動複制歌曲名了。
每次上 github 下載下傳項目時,都會有一個點選即可複制下載下傳連結,我通過搜尋大概知道了原理,利用的是剪貼闆實作的功能,使用者點選時,自動把内容複制。
我們采用clipboard 這個庫,具體使用方法點選,大概如下:
. 引用該 JS 檔案。
. 設定可自動複制的 class,運作 new ClipboardJS('.btn'); btn 為class。
. 設定需要點選複制的超連結或者按鍵的 class、data-clipboard-text 屬性,前者就是第二步設定的可自動複制的 class,後者就是複制的内容。
代碼邏輯
1. 雲音樂頁面中插入 a 标簽,并有點選事件。
2. 點選後,暫停播放,生成 audio 标簽,并複制了歌名。
具體代碼點選代碼。
操作流程
- 點選 複制裡面的全部 JS 代碼。
- 複制到遊覽器雲音樂界面的開發者工具的 Console 中,回車運作。關閉開發者工具。
- 打開 Chrome 下載下傳檔案前詢問,設定下載下傳前詢問。
- 播放需要下載下傳的歌,然後點選加載,再點選 audio 的下載下傳圖示,粘貼(Ctrl+C)更改名字後下載下傳即可。
總結
多嘗試,多觀察,就能知道哪些能做,哪些不能做。