python 爬蟲 1 爬取酷狗音樂

TNT_God發表於2020-03-29

終於到了週末!怎麼能少得了我的每週一更的文章呢?:stuck_out_tongue_closed_eyes:

介紹

你是不是常常想要在各大音樂網站上下載音樂?但是網站卻逼迫你下載他們的應用?不要怪他們,這只是他們的賺錢的方式(你不下載他們應用,他們怎麼賺錢呢)然而,你下載了應用,它們卻逼迫你購買vip……沒關係,今天我們就來用爬蟲手段“制裁”這些網站!首先,就由最簡單的酷狗音樂開始爬!

功能概述

讓使用者輸入要搜尋的音樂名,然後把所有的音樂以及每一個音樂對應的資訊展示給使用者。再詢問使用者要不要下載任何音樂,如果要,則讓使用者輸入音樂對應的id號來下載(支援批量下載)。

找出思路

首先,在獲取多首歌曲的資訊和下載地址之前,我們需要知道如何獲取一首歌的下載地址。

開啟www.kugou.com ,在搜尋欄裡輸入你想要查詢的歌曲名。按下回車。切換網頁之後,點進一首歌曲的播放頁。按下F12,調出開發者工具。選擇network,然後點all。可以看到,目前是沒有任何東西顯示的。因為所有的檔案已經在你開啟開發者工具的時候載入完了,此時此刻,你只需要F5重新整理一下網頁。好了,現在
你就能看到類似這樣的頁面。
python 爬蟲音樂篇 1 爬取酷狗音樂
可以看到什麼js檔案啊,png檔案啊,音訊檔案啊,都沒有!因為我們在調出開發者工具之前,網站已經載入完了檔案,這個時候,我們只需要按下F5重新整理一下網站。好了,所有的檔案載入出來了。進入到一個叫做index.php?的檔案,然後進入到這個檔案的地址。

python 爬蟲音樂篇 1 爬取酷狗音樂

進入這個檔案地址之後,這實際上就是音樂的資訊(為了方便,我在文章後面就說是資訊地址)。我們還可以看到一個叫play_url的東西,這個play_url就是音訊的mp3檔案地址,可以看到,這些play_url都是把/變成了/。我們不用擔心這個,因為網址輸入欄會自動幫我們調整成/,但是在用程式碼實現爬蟲的時候,我們就需要把/變成/了。但短時間內,我們先不用管這個。讓我們進入到這個網址,咦?這不是我們剛剛播放的音樂嗎?

python 爬蟲音樂篇 1 爬取酷狗音樂

成功之後,我們就有了更大的信心和思路去爬蟲。我們只要把每首歌曲的資訊地址找出來,然後用正規表示式把每首歌曲的資訊和音樂地址獲取出來。再一次用爬蟲獲取到音樂的二進位制編碼,儲存在本地。

那我們如何獲取每首歌的資訊地址呢?通過拼接地址!讓我們看這兩首歌的url有啥不同,你就知道了。

Faded - https://wwwapi.kugou.com/yy/index.php?r=pl...

卡路里 - https://wwwapi.kugou.com/yy/index.php?r=pl...

可以看到除了hash值以外的東西,就沒有啥區別了。也就是說我們只需要通過https://wwwapi.kugou.com/yy/index.php?r=play/getdata&callback=jQuery19107782905097580941_1585453882045&hash=每首歌的hash值&album_id=9175221&dfid=2SSGs60RKO9P0bAzIe0xF4Us&mid=5a959954d2f99fc1438fe2efb7596511&platid=4&_=1585453882046

來拼接每首歌的資訊地址就行了。那歌曲的hash要去那裡找呢?回到酷狗的音樂搜尋欄,隨便搜一首歌按下回車。可以看到這裡有好多首歌。F12-NETWORK-ALL-F5,我們找出一個這樣的檔案。

python 爬蟲音樂篇 1 爬取酷狗音樂

我們進入這個網址,就可以看到剛剛所有歌曲的hash。那問題又來了,我們又要怎樣獲取到這個hash資訊網址呢?這個太簡單了,只需要通過https://songsearch.kugou.com/song_search_v2?callback=jQuery112406923427025623534_1585454373397&keyword=搜尋的歌曲名&page=1&pagesize=30&userid=-1&clientver=&platform=WebFilter&tag=em&filter=2&iscorrection=1&privilege_filter=0&_=1585454373399
拼接網址就行。

這個搜尋的歌曲名,我們程式碼用input讓使用者輸入歌曲名就行了。那麼,你找到思路了嗎?

思路:拼接出hash資訊網址,正規表示式獲取到所有歌曲的hash,再拼接出單首歌曲的url。最後再一次用正規表示式獲取歌曲的play_url即可。

開始寫程式碼

首先匯入我們的requests和re正規表示式庫。re用來找出音樂的資訊和下載地址,requests負責獲取文字和下載音樂。

import requests
import re

我們還要設定一些變數,這些變數在後面可是會派上大用場的。

timer = 0
song_urls = {}
names = {}

我們不是要拼接出多首歌曲的資訊網址嗎?那我們就先要讓使用者輸入歌曲名。接著再拼。

songs = input("請輸入歌曲名:")

url = 'https://songsearch.kugou.com/song_search_v2?callback=jQuery112409090559630919017_1585358668138&keyword=%s&page=1&pagesize=30&userid=-1&clientver=&platform=WebFilter&tag=em&filter=2&iscorrection=1&privilege_filter=0&_=1585358668140'%songs

現在,我們就可以用requests請求文字了!由於這個網址是get請求的而且我們請求的是文字,所以,我們也要用方法requests.get().text方法。

texts = requests.get(url).text

接著,你可以試著列印一下文字。列印出來的文字和我們拼接的網址的內容毫無區別(我這裡就不列印了,等下python卡死就完了)
在這些文字里,我們可以獲取到每首歌的hash值。用正規表示式查詢就行了。

song_hashes = re.findall('"FileHash":"(.*?)"',texts)

列印一下song_hashes,可以看到,他是個列表。所以我們要進行for遍歷。

for i in song_hashes:
    information_url = 'https://wwwapi.kugou.com/yy/index.php?r=play/getdata&callback=jQuery19104610954889760035_1585364074033&hash=%s&album_id=0&dfid=2SSGs60RKO9P0bAzIe0xF4Us&mid=5a959954d2f99fc1438fe2efb7596511&platid=4&_=1585364074034'%i
    information = requests.get(information_url).text
    song_url = re.findall('"play_url":"(.*?)"',information)
    song_names = bytes(re.findall('"audio_name":"(.*?)"',information)[0],encoding='ascii').decode('unicode-escape')
    singers = bytes(re.findall('"author_name":"(.*?)"',information)[0],encoding='ascii').decode('unicode-escape')
    if song_names not in names.values():
        names[str(timer)] = song_names
        print("%d.%s"%(timer,song_names))
        print("作者:%s"%singers)
        print()
        timer += 1
    if song_url[0] not in song_urls.values():
        song_urls[str(timer-1)] = song_url[0]

上段程式碼中,我們進行了每個hash的拼接操作,然後我們在從單首歌曲的資訊文字里找到了音樂名和作者和下載地址。由於音樂名和作者是進行ascii編碼過的,所以我們也要進行一個解碼。由於歌曲名和歌手有時候會重複列印,所以我們每一次列印音樂和作者之前,都會把音樂和作者名加入到一個字典。每一次列印都會進行一次是否存在字典的判斷。字典的key就由我們的timer變數的變化進行改變key名。另外,我們還把每首歌的下載地址儲存到了song_urls字典裡。
列印了音樂資訊之後,就要詢問使用者要下載那首歌了。

print('輸入n就不下載,若要下載多首歌曲,請用英文符號","隔開')
choice = input('請輸入要下載歌曲的編號:').split(',')
if choice == "n":
    exit()
else:
    path = input("請輸入要儲存的路徑:")
    for i in choice:
        song_url = song_urls[i].replace('\\/','/')
        song = requests.get(song_url).content
        save_name = names[i]
        with open(path + '/' + save_name + '.mp3','wb') as f:
            f.write(song)
    print("儲存完成!")

按以前的做法,用requests.get().content把音樂轉換成二進位制檔案再進行儲存。在get之前,我們還需要把網址的亂七八糟的\/變成/。之後,就能儲存下來了!
我們就拿一首叫做the day you went away的歌試一下
程式碼實現效果:
python 爬蟲1 爬取酷狗音樂

程式的不足

酷狗每隔一段時間都會弄個滑動驗證碼,這個時候我們的程式就不能獲取到資料。這種情況,用selenium就可以輕鬆解決。

完整程式碼:

#匯入庫
import requests
import re
import os
#設定好一些變數
timer = 0 #設定一個計算歌曲順序的機器
song_urls = {}
names = {}
songs = input("請輸入歌曲名:")

url = 'https://songsearch.kugou.com/song_search_v2?callback=jQuery112409090559630919017_1585358668138&keyword=%s&page=1&pagesize=30&userid=-1&clientver=&platform=WebFilter&tag=em&filter=2&iscorrection=1&privilege_filter=0&_=1585358668140'%songs
texts = requests.get(url).text
song_hashes = re.findall('"FileHash":"(.*?)"',texts)

print("請稍等...")
for i in song_hashes:
    information_url = 'https://wwwapi.kugou.com/yy/index.php?r=play/getdata&callback=jQuery19104610954889760035_1585364074033&hash=%s&album_id=0&dfid=2SSGs60RKO9P0bAzIe0xF4Us&mid=5a959954d2f99fc1438fe2efb7596511&platid=4&_=1585364074034'%i
    information = requests.get(information_url).text
    song_url = re.findall('"play_url":"(.*?)"',information)
    song_names = bytes(re.findall('"audio_name":"(.*?)"',information)[0],encoding='ascii').decode('unicode-escape')
    singers = bytes(re.findall('"author_name":"(.*?)"',information)[0],encoding='ascii').decode('unicode-escape')
    if song_names not in names.values():
        names[str(timer)] = song_names
        print("%d.%s"%(timer,song_names))
        print("作者:%s"%singers)
        print()
        timer += 1
    if song_url[0] not in song_urls.values():
        song_urls[str(timer-1)] = song_url[0]
print('輸入n就不下載,若要下載多首歌曲,請用英文符號","隔開')
choice = input('請輸入要下載歌曲的編號:').split(',')
if choice == "n":
    exit()
else:
    path = input("請輸入要儲存的路徑:")
    has_path = os.path.exists(path)
    while has_path == False:
        print("路徑不存在!!")
        path = input("請輸入要儲存的路徑:")
        has_path = os.path.exists(path)
    for i in choice:
        song_url = song_urls[i].replace('\\/','/')
        song = requests.get(song_url).content
        save_name = names[i]
        with open(path + '/' + save_name + '.mp3','wb') as f:
            f.write(song)
    print("儲存完成!")

本作品採用《CC 協議》,轉載必須註明作者和本文連結

Derek

相關文章