利用 Python 一鍵下載網(wǎng)易云音樂 10W+ 樂庫

文 |?豆豆
來源:Python 技術(shù)「ID: pythonall」

如果你常聽音樂的話,肯定繞不開網(wǎng)易云,作為一款有情懷的音樂 App,我對(duì)網(wǎng)易云也是喜愛有加。雖然說現(xiàn)在都已經(jīng)是 5G 時(shí)代了,大家的手機(jī)流量都綽綽有余,但在線播放還是不如本地存著音樂文件靠譜,今天我們就用 Python 來一鍵下載網(wǎng)易云音樂樂庫。
先來看下最終的效果。


其實(shí)下載音樂不難,只需要獲取到音樂文件播放的地址就可以通過文件流讀取的方式直接下載下來。那么問題就轉(zhuǎn)化為如何獲取音樂文件的播放地址了。
榜單分析
我們可以打開網(wǎng)易云排行榜?https://music.163.com/#/discover/toplist?id=19723756,仔細(xì)分析我們發(fā)現(xiàn)該網(wǎng)頁左邊一列全是排行榜,每個(gè)排行榜都對(duì)應(yīng)這不同的排行榜 ID,具體 ID 是多少,直接調(diào)開開發(fā)者工具即可清晰的看到。

由上圖我們可以看到榜單是放在一個(gè)?class='f-cb'?的?ul?列表里面的,所以只需要獲取到該?ul?列表的?li?標(biāo)簽即可。而對(duì)于每一個(gè)?li?標(biāo)簽來說,其?data-res-id?屬性則是榜單 id,而榜單名稱則是屬于該?li?標(biāo)簽下的?div?中?class='name'?的?p?標(biāo)簽下的?a?標(biāo)簽的內(nèi)容。因此我們獲取到?li?標(biāo)簽的集合之后,遍歷該集合依次取出榜單 id 和榜單名稱即可。
于是我們有了下面的函數(shù),獲取所有的榜單,該函數(shù)返回值是一個(gè)字典,key 為 榜單 id,值為榜單名稱。
url?=?'https://music.163.com/discover/toplist'
hd?=?{
????'User-Agent':?'Mozilla/5.0?(Macintosh;?Intel?Mac?OS?X?10_15_0)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/86.0.4240.111?Safari/537.36'
}
def?get_topic_ids():
????r?=?requests.get(url,?headers=hd)
????html?=?etree.HTML(r.text)
????nodes?=?html.xpath("http://ul[@class='f-cb']/li")
????logger.info('{}??{}'.format('榜單?ID',?'榜單名稱'))
????ans?=?dict()
????for?node?in?nodes:
????????id?=?node.xpath('./@data-res-id')[0]
????????name?=?node.xpath("./div/p[@class='name']/a/text()")[0]
????????ans[id]?=?name
????????logger.info('{}??{}'.format(id,?name))
????return?ans
歌曲分析
上面我們獲取到了所有的榜單數(shù)據(jù),那么針對(duì)單個(gè)榜單來說,就是要獲取其下的所有歌曲了。
分析頁面原屬可知,歌曲列表是在一個(gè)?table?中的,但是通過?requests.get(url,headers=hd)?方式獲取返回的網(wǎng)頁文本內(nèi)容的話,貌似是獲取不到?table?元素的。于是我們將其返回值輸出后做了仔細(xì)分析,發(fā)現(xiàn)歌曲是在?class="f-hide"?的?ul?標(biāo)簽中。與獲取榜單類似,同樣需要先獲取所有的?li?標(biāo)簽,然后在逐個(gè)獲取歌曲 id 和歌曲 name 就可以了。
def?get_topic_songs(topic_id,?topic_name):
????params?=?{
????????'id':?topic_id
????}
????r?=?requests.get(url,?params=params,?headers=hd)
????html?=?etree.HTML(r.text)
????nodes?=?html.xpath("http://ul[@class='f-hide']/li")
????ans?=?dict()
????logger.info('{}?榜單?{}?共有歌曲?{}?首?{}'.format('*'?*?10,?topic_name,?len(nodes),?'*'?*?10))
????for?node?in?nodes:
????????id?=?node.xpath('./a/@href')[0].split('=')[1]
????????name?=?node.xpath('./a/text()')[0]
????????ans[id]?=?name
????????logger.info('{}??{}'.format(id,?name))
????return?ans
同樣該函數(shù)返回一個(gè)字典,key 為歌曲 id,value 為歌曲名稱。
下載歌曲
我們還需要一個(gè)下載歌曲的函數(shù),該函數(shù)接收歌曲 id,然后以文件流的形式直接讀取到本地。
def?down_song_by_song_id_name(id,?name):
????if?not?os.path.exists(download_dir):
????????os.mkdir(download_dir)
????url?=?'http://music.163.com/song/media/outer/url?id={}.mp3'
????r?=?requests.get(url.format(id),?headers=hd)
????is_fail?=?False
????try:
????????with?open(download_dir?+?name?+?'.mp3',?'wb')?as?f:
????????????f.write(r.content)
????except:
????????is_fail?=?True
????????logger.info("%s?下載出錯(cuò)"?%?name)
????if?(not?is_fail):
????????logger.info("%s?下載完成"?%?name)
最后將所有的操作組合到 main 函數(shù)中,作為程序的入口函數(shù)。
def?main():
????ids?=?get_topic_ids()
????while?True:
????????print('')
????????logger.info('輸入?Q?退出程序')
????????logger.info('輸入?A?下載全部榜單歌曲')
????????logger.info('輸入榜單?Id?下載當(dāng)前榜單歌曲')
????????id?=?input('請(qǐng)輸入:')
????????if?str(id)?==?'Q':
????????????break
????????elif?str(id)?==?'A':
????????????for?id?in?ids:
????????????????down_song_by_topic_id(id,?ids[id])
????????else:
????????????print('')
????????????ans?=?get_topic_songs(id,?ids[id])
????????????print('')
????????????logger.info('輸入?Q?退出程序')
????????????logger.info('輸入?A?下載全部歌曲')
????????????logger.info('輸入歌曲?Id?下載當(dāng)前歌曲')
????????????id?=?input('請(qǐng)輸入:')
????????????if?str(id)?==?'Q':
????????????????break
????????????elif?id?==?'A':
????????????????down_song_by_topic_id(id,?ans[id])
????????????else:
????????????????down_song_by_song_id_name(id,?ans[id])
if?__name__?==?"__main__":
????main()
總結(jié)
今天我們以網(wǎng)易云網(wǎng)頁版為數(shù)據(jù)源來下載音樂文件,其中下載操作是最簡單的,比較麻煩的是分析榜單 id 和獲取榜單下的歌曲列表,但榜單下的歌曲列表其實(shí)遠(yuǎn)不止 10 條,而我們獲取歌曲的函數(shù)?get_topic_songs?每次只可以獲取 10 條歌曲,這是因?yàn)槲覀儧]有在?headers?添加?cookie?導(dǎo)致的,因?yàn)橹挥械卿浿蟛艜?huì)顯示所有的歌曲。小伙伴們可以登錄自己的賬戶然后添加?cookie?做下嘗試。
PS:公號(hào)內(nèi)回復(fù)「Python」即可進(jìn)入Python 新手學(xué)習(xí)交流群,一起 100 天計(jì)劃!
老規(guī)矩,兄弟們還記得么,右下角的 “在看” 點(diǎn)一下,如果感覺文章內(nèi)容不錯(cuò)的話,記得分享朋友圈讓更多的人知道!


【代碼獲取方式】
