天天看點

在網易雲音樂網頁版上加下載下傳按鍵進行下載下傳歌曲源由原理代碼解決代碼邏輯操作流程總結

  • 源由
  • 原理
  • 代碼解決
    • 思路
    • 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 标簽,并複制了歌名。
           

具體代碼點選代碼。

操作流程

  1. 點選 複制裡面的全部 JS 代碼。
  2. 複制到遊覽器雲音樂界面的開發者工具的 Console 中,回車運作。關閉開發者工具。
  3. 打開 Chrome 下載下傳檔案前詢問,設定下載下傳前詢問。
  4. 播放需要下載下傳的歌,然後點選加載,再點選 audio 的下載下傳圖示,粘貼(Ctrl+C)更改名字後下載下傳即可。
在網易雲音樂網頁版上加下載下傳按鍵進行下載下傳歌曲源由原理代碼解決代碼邏輯操作流程總結

總結

多嘗試,多觀察,就能知道哪些能做,哪些不能做。