如果你常聽音樂的話,肯定繞不開網易云,作為一款有情懷的音樂 App,我對網易云也是喜愛有加。雖然說現在都已經是 5G 時代了,大家的手機流量都綽綽有余,但在線播放還是不如本地存著音樂文件靠譜,今天我們就用 Python/ target=_blank class=infotextkey>Python 來一鍵下載網易云音樂樂庫。
其實下載音樂不難,只需要獲取到音樂文件播放的地址就可以通過文件流讀取的方式直接下載下來。那么問題就轉化為如何獲取音樂文件的播放地址了。
榜單分析
我們可以打開網易云排行榜 https://music.163.com/#/discover/toplist?id=19723756,仔細分析我們發現該網頁左邊一列全是排行榜,每個排行榜都對應這不同的排行榜 ID,具體 ID 是多少,直接調開開發者工具即可清晰的看到。
由上圖我們可以看到榜單是放在一個 class='f-cb' 的 ul 列表里面的,所以只需要獲取到該 ul 列表的 li 標簽即可。而對于每一個 li 標簽來說,其 data-res-id 屬性則是榜單 id,而榜單名稱則是屬于該 li 標簽下的 div 中 class='name' 的 p 標簽下的 a 標簽的內容。因此我們獲取到 li 標簽的集合之后,遍歷該集合依次取出榜單 id 和榜單名稱即可。
于是我們有了下面的函數,獲取所有的榜單,該函數返回值是一個字典,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("//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
歌曲分析
上面我們獲取到了所有的榜單數據,那么針對單個榜單來說,就是要獲取其下的所有歌曲了。
分析頁面原屬可知,歌曲列表是在一個 table 中的,但是通過 requests.get(url,headers=hd) 方式獲取返回的網頁文本內容的話,貌似是獲取不到 table 元素的。于是我們將其返回值輸出后做了仔細分析,發現歌曲是在 class="f-hide" 的 ul 標簽中。與獲取榜單類似,同樣需要先獲取所有的 li 標簽,然后在逐個獲取歌曲 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("//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
同樣該函數返回一個字典,key 為歌曲 id,value 為歌曲名稱。
下載歌曲
我們還需要一個下載歌曲的函數,該函數接收歌曲 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 下載出錯" % name) if (not is_fail): logger.info("%s 下載完成" % name)
最后將所有的操作組合到 main 函數中,作為程序的入口函數。
def main(): ids = get_topic_ids() while True: print('') logger.info('輸入 Q 退出程序') logger.info('輸入 A 下載全部榜單歌曲') logger.info('輸入榜單 Id 下載當前榜單歌曲') id = input('請輸入:') 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 下載當前歌曲') id = input('請輸入:') 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()
總結
今天我們以網易云網頁版為數據源來下載音樂文件,其中下載操作是最簡單的,比較麻煩的是分析榜單 id 和獲取榜單下的歌曲列表,但榜單下的歌曲列表其實遠不止 10 條,而我們獲取歌曲的函數 get_topic_songs 每次只可以獲取 10 條歌曲,這是因為我們沒有在 headers 添加 cookie 導致的,因為只有登錄之后才會顯示所有的歌曲。小伙伴們可以登錄自己的賬戶然后添加 cookie 做下嘗試。