尋找數據
打開F12中的網絡頁面,播放音樂后,篩選媒體,會發現當前這首歌曲音頻鏈接地址,打開后,點擊“標頭”就能能看到請求URL
截取“.mp3”前面的一部分進行搜索,搜索出來了很多數據包,但都是重復的,其實只有兩個。一個就是我們已經找到的音頻鏈接,另一個就是網易云音樂的接口文件
打開這個“v1”數據包,在標頭可以看到他的請求URL,在負載
可以看到有兩個表單數據分別是params
和encSecKey
在預覽頁面中,可以看到請求的返回值,是一個JSON格式的數據,而我們要的音樂鏈接就是url所對應的值
代碼實現
有了上面這些請求數據,我們可以用代碼來向網易云音樂發送請求,然后下載歌曲到本地。其中,headers里的cookie需要在標頭中的“請求標頭”中復制,這里我就不展示我的cookie了。代碼模擬瀏覽器給網易云音樂發送請求,獲取到一個json數據,從中找到所需要的url,接著向這個url發送請求進一步獲取二進制的音頻內容,然后以二進制寫入的方式打開文件,把音樂保存到了本地。
import requestsheaders = {"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0","referer":"https://music.163.com/","cookie":your_cookie
}
def download_music():url = "https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=528a132e63865dc4c681934d2a7bb31f"data = {"params":"sZ3h9aF5g8SsP4JiHaXuJqi4E+V+aP/ut4FZfUkzOi0bJbr2N7/PvLx3xTcrAeu05Bcb+LG2c77NKfZ01ShNSMYBd8iVxGggg2QFkM8Enes/2kqHwYziVSFB0dHl3NgY2SSBadA2UwJrt28eDXNDsiIATRORvGkCCFmXDEJXCb83sqJWJixEB2sE57L2jZ5oesC9Dsv1mHczuPyC7+OZYw==","encSecKey":"16c8e6d77d5831c34d374bf7c4c9fbf1993cdd0145eb9dcf2eeca8cdf037edda15c0f58c36c60b3765ee7087d6df32e3cf37976e0fe4bc4dfd4e4acf06e45e73317d8b9d4f27941076c5bf334f5456f687854797e2966a14a2fe0bc27592dc5a5553d6ad8339b4fd0e9094726d8633c06f2fdf16a0f90b94103ce79dab78c5f7"}response = requests.post(url=url,data=data,headers=headers)json_data = response.json()music_url = json_data["data"][0]["url"]music_content = requests.get(url=music_url,headers=headers).content# 向音頻鏈接發起請求,獲取二進制的音頻內容with open(f"music.mp3","wb") as f:# 以二進制寫入的方式打開文件,寫入音頻內容f.write(music_content)print("下載成功")
download_music()
逆向解密
上面的代碼是我們在已知param
和encSecKey
這兩個數據的情況下實現下載音頻文件。通過嘗試下載其他歌曲也不難看出,只要提供正確的param
和encSecKey
我們就能下載到所對應的音樂了。而這兩個值到底從何而來呢?
在搜索框中搜索"encSecKey",我們找到了一個JS文件
點擊后在響應面板中右鍵,在源面板中打開
在源代碼頁面進行搜索,找到了一段代碼
var bVz9q = window.asrsea(JSON.stringify(i9b), bsC6w(["流淚", "強"]), bsC6w(BA5F.md), bsC6w(["愛心", "女孩", "驚恐", "大笑"]));
e9f.data = j9a.cr0x({params: bVz9q.encText,encSecKey: bVz9q.encSecKey
})
從這個代碼可以看出,我們要找的params
和encSecKey
來自于一個名為bVz9q
的對象中。而他又來自于一個window.asrsea
的函數。
給這行代碼打上斷點,鼠標懸停在asrsea上。
我們直接定位到了這個函數所在的地方,點擊藍色下劃線的文字跳轉
跳轉到了一個名為d
的函數,原來這個window.asrsea
就是d
函數d
中需要傳入四個參數分別是a,b,c,d,而在剛剛調用部分的代碼中,我們可以看到他傳入的四個參數分別為
- JSON.stringify(i9b)
- bsC6w([“流淚”, “強”])
- bsC6w(BA5F.md)
- bsC6w([“愛心”, “女孩”, “驚恐”, “大笑”])
其中,在控制臺輸入后面四個參數,我們得到的都是如下的定值
而第一個參數中的i9b比較特殊,通過斷點調試發現這是一個變化的值。網易云音樂的所有接口都會經過這一行代碼,而在播放音樂后我們找到了一個i9b的值如下圖
這個值就是在調用音頻接口時候的值,不難看出,這個i9b中的編號就是歌曲網址中最后的那一串數字。至于csrf_token,這其實是一個固定值,他就是請求URL最后的那串東西。這么一來問題就迎刃而解了。
我們只要有歌曲的id就能得到i9b
,然后把JSON.stringify(i9b)
和其余三個參數傳入到asrsea
獲得加密的數據——params
和encSecKey
然后作為負載發送請求給網易云音樂,就能下載到歌曲了。
在這個js代碼中,把相應加密的代碼復制下來到本地的js文件。
最后我們定義如下函數方便通過python中的execjs模塊調用。
function json_encode(i9b){return JSON.stringify(i9b);
}
完整的js文件太大了就不展示了。
批量下載
現在已經可以實現通過歌曲id下載到對應的歌曲了,而如果想實現批量下載也非常簡單。只要獲取到歌單的url然后發送請求,通過re正則表達式提取出頁面中的歌曲超鏈接中的歌曲id,然后分別下載這些歌曲id對應的音頻文件就行了。
完整代碼
下面附上完整的代碼
import requests
import execjs
import reheaders = {"user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0","referer":"https://music.163.com/","cookie":your_cookie
}
def download_music(music_id,music_name):# 編譯js代碼js_code = execjs.compile(open("爬網易云/網易.js",encoding = 'utf-8').read())#加密參數i9b = {"ids": '', "level": 'exhigh', "encodeType": 'aac', "csrf_token": '528a132e63865dc4c681934d2a7bb31f'}i9b['ids'] = f"[{music_id}]"# 調用解密函數e = '010001';f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7';g = '0CoJUm6Qyw8W8jud';i9b = js_code.call("json_encode",i9b)rdata = js_code.call("asrsea", i9b,e,f,g)url = "https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=528a132e63865dc4c681934d2a7bb31f"data = {"params":rdata["encText"],"encSecKey":rdata["encSecKey"]}response = requests.post(url=url,data=data,headers=headers)json_data = response.json()music_url = json_data["data"][0]["url"]music_content = requests.get(url=music_url,headers=headers).content# 向音頻鏈接發起請求,獲取二進制的音頻內容with open(f"爬網易云/music/{music_name}.mp3","wb") as f:# 以二進制寫入的方式打開文件,寫入音頻內容f.write(music_content)def get_info_list(list_id):list_url = f"https://music.163.com/playlist?id={list_id}"response = requests.get(url=list_url,headers=headers)html = response.text# <a href="/song?id=1360122230">花月</a>info = re.findall('<a href="/song\?id=(\d+)">(.*?)</a>',html)info_list = [[music_id,music_name] for music_id,music_name in info]return info_listdef get_song_name(music_id):url = f"https://music.163.com/song?id={music_id}"response = requests.get(url=url,headers=headers)html = response.text# <em class="f-ff2">花月</em>name = re.findall('<em class="f-ff2">(.*?)</em>',html)[0]return namemode = input("選擇爬取模式:\n 1.單曲下載 \n 2.批量下載\n")if __name__ == '__main__':if mode == "1":music_id = input("請輸入歌曲ID:")music_name = get_song_name(music_id)download_music(music_id,music_name)print(f"《{music_name}》下載完成")elif mode == "2":list_id = input("請輸入歌單ID:")info_list = get_info_list(list_id)for music_id,music_name in info_list:download_music(music_id,music_name)print(f"《{music_name}》下載完成")print("批量下載完成")