<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
寫這篇部落格的初衷是加深自己對網路請求傳送和響應的理解,僅供學習使用,請勿用於非法用途!文明爬蟲,從我做起。下面進入正題。
在酷我的搜尋方塊中輸入關鍵詞 aiko
,回車之後可以看到所有和 aiko
相關的歌曲。開啟開發者模式,在網路面板下按下 ctrl + f,搜尋 二人
,可以找到響應結果中包含 二人
的請求,這個請求就是用來獲取歌曲資訊列表的。
請求的具體格式如下圖所示,可以看到請求路徑為 http://www.kuwo.cn/api/www/search/searchMusicBykeyWord,請求引數包括:
key
: 搜尋關鍵詞,此處為 aiko
pn
: 頁碼,page number
的縮寫,此處為 1
rn
: 每頁條目數,應該是 row number
的縮寫,預設為 30
httpsStatus
:https 的狀態?感覺沒啥大用,看了原始碼裡面是直接寫死 t.url = t.url + "?reqId=".concat(n, "&httpsStatus=1")
reqId
:請求標識,重新整理頁面之後值會發生改變,不知道有啥用,待會兒模擬請求的時候試著不帶上他會怎麼樣開啟 Apifox(當然 postman 也行),新建一個介面,把請求路徑和引數設定為下圖所示的樣子,為了讓響應結果簡短點,這裡把每頁的條目數設定為 1
而非預設的 30
:
在沒有設定額外請求頭的情況下發個請求試試,發現 403 Forbidden 了,emmmmm,應該是防盜鏈所致:
可以看到瀏覽器發出的請求的請求頭中有設定 Referer
欄位,把它加上,應該不會再報錯了吧:
這次狀態碼為 200,但是沒有收到任何資料,success
為 false
說明請求失敗了,message
指明瞭失敗原因是缺少 CSRF token
。問題不大,接著把瀏覽器發出的請求中的 csrf
加到 Apifox 請求頭中,再發請求,還是報錯 CSRF token Invalid!
。算了,還是老老實實把 Cookie 也加上吧,但也不是全部加上,只加 kw_token=CCISYM2HV96
部分,因為 Cookie 裡面只有這個欄位和 token
有關係且它的值和 csrf
相同。
在原始碼面板按下 ctrl + shift + f,搜尋一下 csrf
,可以看到 csrf
本來就是來自 Object(h.b)("kw_token")
,這個函數用來取出 document.cookie
中的 kw_token
欄位值。至於 Cookie 中的 kw_token
怎麼計算得到的,那就是伺服器的事情了,咱們只管 CV 操作即可。
準備好引數和請求頭,重新傳送請求,可以得到想要的資料。如果去掉 reqId
引數,也可以拿到資料,但是會有略微的不同,這裡就不貼出來了:
{ "code": 200, "curTime": 1649482287185, "data": { "total": "741", "list": [ { "musicrid": "MUSIC_11690555", "barrage": "0", "ad_type": "", "artist": "aiko", "mvpayinfo": { "play": 0, "vid": 8530326, "down": 0 }, "nationid": "0", "pic": "http://img4.kuwo.cn/star/starheads/500/24/88/4146545084.jpg", "isstar": 0, "rid": 11690555, "duration": 362, "score100": "42", "ad_subtype": "0", "content_type": "0", "track": 1, "hasLossless": true, "hasmv": 1, "releaseDate": "1970-01-01", "album": "", "albumid": 0, "pay": "16515324", "artistid": 1907, "albumpic": "http://img4.kuwo.cn/star/starheads/500/24/88/4146545084.jpg", "originalsongtype": 0, "songTimeMinutes": "06:02", "isListenFee": false, "pic120": "http://img4.kuwo.cn/star/starheads/120/24/88/4146545084.jpg", "name": "戀をしたのは", "online": 1, "payInfo": { "play": "1100", "nplay": "00111", "overseas_nplay": "11111", "local_encrypt": "1", "limitfree": 0, "refrain_start": 89150, "feeType": { "song": "1", "vip": "1" }, "down": "1111", "ndown": "11111", "download": "1111", "cannotDownload": 0, "overseas_ndown": "11111", "refrain_end": 126247, "cannotOnlinePlay": 0 }, "tme_musician_adtype": "0" } ] }, "msg": "success", "profileId": "site", "reqId": "4b55cf4b0171253c33ce1d71b999c42f", "tId": "" }
響應結果的 data
欄位中有很多東西,這裡只提取需要的部分。在提取之前先來定義一下歌曲資訊實體類,這樣在其他函數中要一首歌曲的資訊時只要把實體類的範例傳入即可。
# coding:utf-8 from copy import deepcopy from dataclasses import dataclass class Entity: """ Entity abstract class """ def __setitem__(self, key, value): self.__dict__[key] = value def __getitem__(self, key): return self.__dict__[key] def get(self, key, default=None): return self.__dict__.get(key, default) def copy(self): return deepcopy(self) @dataclass class SongInfo(Entity): """ Song information """ file: str = None title: str = None singer: str = None album: str = None year: int = None genre: str = None duration: int = None track: int = None trackTotal: int = None disc: int = None discTotal: int = None createTime: int = None modifiedTime: int = None
上述程式碼顯示定義了實體類的基礎類別,並且重寫了 __getitem__
和 __setitem__
魔法方法,這樣我們可以像存取字典一樣來存取實體類物件的屬性。接著讓歌曲資訊實體類繼承了實體類基礎類別,並且使用 @dataclass
裝飾器,這是 python 3.7 引入的新特性,使用它裝飾之後的實體類無需實現建構函式、__str__
等常用函數,python 會幫我們自動生成。
在傳送請求的過程中可能會遇到各種異常,如果在程式碼裡面寫 try except
語句會顯得很亂,這裡同樣可以用裝飾器來解決這個問題。
# coding:utf-8 from copy import deepcopy def exceptionHandler(*default): """ decorator for exception handling Parameters ---------- *default: the default value returned when an exception occurs """ def outer(func): def inner(*args, **kwargs): try: return func(*args, **kwargs) except BaseException as e: print(e) value = deepcopy(default) if len(value) == 0: return None elif len(value) == 1: return value[0] else: return value return inner return outer
下面是傳送獲取歌曲資訊請求的程式碼,使用 exception_handler
裝飾了 getSongInfos
方法,這樣發生異常時會列印異常資訊並返回預設值:
# coding:utf-8 import json from urllib import parse from typing import List, Tuple import requests class KuWoMusicCrawler: """ Crawler of KuWo Music """ def __init__(self): super().__init__() self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36', 'Cookie': 'kw_token=C713RK6IJ8J', 'csrf': 'C713RK6IJ8J', 'Host': 'www.kuwo.cn', 'Referer': '' } @exceptionHandler([], 0) def getSongInfos(self, key_word: str, page_num=1, page_size=10) -> Tuple[List[SongInfo], int]: key_word = parse.quote(key_word) # configure request header headers = self.headers.copy() headers["Referer"] = 'http://www.kuwo.cn/search/list?key='+key_word # send request for song information url = f'http://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key={key_word}&pn={page_num}&rn={page_size}&reqId=c06e0e50-fe7c-11eb-9998-47e7e13a7206' response = requests.get(url, headers=headers) response.raise_for_status() # parse the response data song_infos = [] data = json.loads(response.text)['data'] for info in data['list']: song_info = SongInfo() song_info['rid'] = info['rid'] song_info.title = info['name'] song_info.singer = info['artist'] song_info.album = info['album'] song_info.year = info['releaseDate'].split('-')[0] song_info.track = info['track'] song_info.trackTotal = info['track'] song_info.duration = info["duration"] song_info.genre = 'Pop' song_info['coverPath'] = info.get('albumpic', '') song_infos.append(song_info) return song_infos, int(data['total'])
雖然我們實現了搜尋歌曲的功能,但是沒拿到每一首歌的播放地址,也就沒辦法把歌曲下載下來。我們先來播放一首不收費的歌曲試試。可以看到瀏覽器傳送了一個獲取播放連結的請求,路徑為 http://www.kuwo.cn/api/v1/www/music/playUrl
,有兩個需要關注的引數:
mid
:音樂 Id,此處的值為 941583
,和頁面 url 中的編號一致,由於我們是通過點選搜尋結果頁面中 二人
跳轉過來的,而 二人
這條結果也是動態載入出來的,超連結中的 Id 肯定也來自於上一節中響應結果的某個欄位。二人
是第四條記錄,通過對比可以發現 data.list[3].rid
就是 mid
;type
:音樂型別?此處的值為 music
,傳送請求的時候也設定為 music
即可在 Apifox 中新建一個獲取歌曲播放地址的請求,如下所示,發現可以成功拿到播放地址:
現在換一首歌,比如 aiko - 橫顏
,點選歌曲頁面上的播放按鈕時會彈出要求在使用者端中付費收聽的對話方塊。直接傳送請求,響應結果會是下面這個樣子,狀態碼為 403:
其實酷我在 2021 年 9 月份的時候換過獲取播放地址的介面,那時候的請求介面為 http://www.kuwo.cn/url
,支援以下幾個引數:
format
: 線上音樂的格式,可以是 mp3
type
: 和現在的介面中的 type
引數一樣,但是值為 convert_url3
rid
: 音樂 Id,和 mid
一樣br
: 線上音樂的位元率,越大則音質越高,可選的有 128kmp3
、 192kmp3
和 320kmp3
這個介面不管是付費音樂還是免費音樂都可以用。如果將現在這個介面的 type
引數的值換成 convert_url3
,請求結果如下所示,說明成功了:
下面是獲取線上音樂播放連結的程式碼,只需呼叫 downloadSong
函數並把爬取到的歌曲傳入就能完成歌曲的下載:
@exceptionHandler('') def getSongUrl(self, song_info: SongInfo) -> str: # configure request header headers = self.headers.copy() headers.pop('Referer') headers.pop('csrf') # send request for play url url = f"http://www.kuwo.cn/api/v1/www/music/playUrl?mid={song_info['rid']}&type=convert_url3" response = requests.get(url, headers=headers) response.raise_for_status() play_url = json.loads(response.text)['data']['url'] return play_url @exceptionHandler('') def downloadSong(self, song_info: SongInfo, save_dir: str) -> str: # get play url url = self.getSongUrl(song_info) if not url: return '' # send request for binary data of audio headers = self.headers.copy() headers.pop('Referer') headers.pop('csrf') headers.pop('Host') response = requests.get(url, headers=headers) response.raise_for_status() # save audio file song_path = os.path.join( save_dir, f"{song_info.singer} - {song_info.title}.mp3") with open(song_path, 'wb') as f: f.write(data) return song
除了獲取歌曲的詳細資訊和播放地址外,我們還能拿到歌詞、歌手資訊等,方法是類似的,在我的 Groove 中提供了線上歌曲的功能,一部分介面就是來自酷我,還有一些來自酷狗和網易雲,爬蟲的程式碼在 app/common/crawler
目錄下,喜歡的話可以給個 star 哦,以上~~
以上就是教你如何使Python爬取酷我線上音樂的詳細內容,更多關於Python爬取音樂的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45