寫這篇博客的初衷是加深自己對網(wǎng)絡(luò)請求發(fā)送和響應(yīng)的理解,僅供學(xué)習(xí)使用,請勿用于非法用途!文明爬蟲,從我做起。下面進(jìn)入正題。
在酷我的搜索框中輸入關(guān)鍵詞 aiko,回車之后可以看到所有和 aiko 相關(guān)的歌曲。打開開發(fā)者模式,在網(wǎng)絡(luò)面板下按下 ctrl + f,搜索 二人,可以找到響應(yīng)結(jié)果中包含 二人 的請求,這個請求就是用來獲取歌曲信息列表的。
請求的具體格式如下圖所示,可以看到請求路徑為 http://www.kuwo.cn/api/www/search/searchMusicBykeyWord,請求參數(shù)包括:
打開 Apifox(當(dāng)然 postman 也行),新建一個接口,把請求路徑和參數(shù)設(shè)置為下圖所示的樣子,為了讓響應(yīng)結(jié)果簡短點(diǎn),這里把每頁的條目數(shù)設(shè)置為 1 而非默認(rèn)的 30:
在沒有設(shè)置額外請求頭的情況下發(fā)個請求試試,發(fā)現(xiàn) 403 Forbidden 了,emmmmm,應(yīng)該是防盜鏈所致:
可以看到瀏覽器發(fā)出的請求的請求頭中有設(shè)置 Referer 字段,把它加上,應(yīng)該不會再報(bào)錯了吧:
這次狀態(tài)碼為 200,但是沒有收到任何數(shù)據(jù),success 為 false 說明請求失敗了,message 指明了失敗原因是缺少 CSRF token。問題不大,接著把瀏覽器發(fā)出的請求中的 csrf 加到 Apifox 請求頭中,再發(fā)請求,還是報(bào)錯 CSRF token Invalid!。算了,還是老老實(shí)實(shí)把 Cookie 也加上吧,但也不是全部加上,只加 kw_token=CCISYM2HV96 部分,因?yàn)?Cookie 里面只有這個字段和 token 有關(guān)系且它的值和 csrf 相同。
在源代碼面板按下 ctrl + shift + f,搜索一下 csrf,可以看到 csrf 本來就是來自 Object(h.b)("kw_token"),這個函數(shù)用來取出 document.cookie 中的 kw_token 字段值。至于 Cookie 中的 kw_token 怎么計(jì)算得到的,那就是服務(wù)器的事情了,咱們只管 CV 操作即可。
準(zhǔn)備好參數(shù)和請求頭,重新發(fā)送請求,可以得到想要的數(shù)據(jù)。如果去掉 reqId 參數(shù),也可以拿到數(shù)據(jù),但是會有略微的不同,這里就不貼出來了:
復(fù)制{
"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": ""
}
響應(yīng)結(jié)果的 data 字段中有很多東西,這里只提取需要的部分。在提取之前先來定義一下歌曲信息實(shí)體類,這樣在其他函數(shù)中要一首歌曲的信息時只要把實(shí)體類的實(shí)例傳入即可。
復(fù)制# 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
上述代碼顯示定義了實(shí)體類的基類,并且重寫了 __getitem__ 和 __setitem__ 魔法方法,這樣我們可以像訪問字典一樣來訪問實(shí)體類對象的屬性。接著讓歌曲信息實(shí)體類繼承了實(shí)體類基類,并且使用 @dataclass 裝飾器,這是 python 3.7 引入的新特性,使用它裝飾之后的實(shí)體類無需實(shí)現(xiàn)構(gòu)造函數(shù)、__str__等常用函數(shù),python 會幫我們自動生成。
在發(fā)送請求的過程中可能會遇到各種異常,如果在代碼里面寫 try except 語句會顯得很亂,這里同樣可以用裝飾器來解決這個問題。
復(fù)制# 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
下面是發(fā)送獲取歌曲信息請求的代碼,使用 exception_handler 裝飾了 getSongInfos 方法,這樣發(fā)生異常時會打印異常信息并返回默認(rèn)值:
復(fù)制# 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'])
雖然我們實(shí)現(xiàn)了搜索歌曲的功能,但是沒拿到每一首歌的播放地址,也就沒辦法把歌曲下載下來。我們先來播放一首不收費(fèi)的歌曲試試。可以看到瀏覽器發(fā)送了一個獲取播放鏈接的請求,路徑為 http://www.kuwo.cn/api/v1/www/music/playUrl,有兩個需要關(guān)注的參數(shù):
在 Apifox 中新建一個獲取歌曲播放地址的請求,如下所示,發(fā)現(xiàn)可以成功拿到播放地址:
現(xiàn)在換一首歌,比如 aiko - 橫顏,點(diǎn)擊歌曲頁面上的播放按鈕時會彈出要求在客戶端中付費(fèi)收聽的對話框。直接發(fā)送請求,響應(yīng)結(jié)果會是下面這個樣子,狀態(tài)碼為 403:
其實(shí)酷我在 2021 年 9 月份的時候換過獲取播放地址的接口,那時候的請求接口為 http://www.kuwo.cn/url,支持以下幾個參數(shù):
這個接口不管是付費(fèi)音樂還是免費(fèi)音樂都可以用。如果將現(xiàn)在這個接口的 type 參數(shù)的值換成 convert_url3,請求結(jié)果如下所示,說明成功了:
下面是獲取在線音樂播放鏈接的代碼,只需調(diào)用 downloadSong 函數(shù)并把爬取到的歌曲傳入就能完成歌曲的下載:
復(fù)制@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
除了獲取歌曲的詳細(xì)信息和播放地址外,我們還能拿到歌詞、歌手信息等,方法是類似的,在我的 Groove 中提供了在線歌曲的功能,一部分接口就是來自酷我,還有一些來自酷狗和網(wǎng)易云,爬蟲的代碼在 app/common/crawler 目錄下,喜歡的話可以給個 star 哦,以上~~
文章作者: 之一Yo
文章鏈接: https://www.cnblogs.com/zhiyiYo/p/16122664.html
天小猿圈給大家分享網(wǎng)易云音樂歌詞爬取方法。
本文的總體思路如下:
本文的目的是獲取網(wǎng)易云音樂的歌詞,并將歌詞存入到本地文件。整體的效果圖如下所示:
趙雷的歌曲
本文以民謠歌神趙雷為數(shù)據(jù)采集對象,專門采集他的歌曲歌詞,其他歌手的歌詞采集方式可以類推,下圖展示的是《成都》歌詞。
趙雷歌曲---《成都》
一般來說,網(wǎng)頁上顯示的URL就可以寫在程序中,運(yùn)行程序之后就可以采集到我們想要的網(wǎng)頁源碼。But在網(wǎng)易云音樂網(wǎng)站中,這條路行不通,因?yàn)榫W(wǎng)頁中的URL是個假URL,真實(shí)的URL中是沒有#號的。廢話不多說,直接上代碼。
獲取網(wǎng)頁源碼
本文利用requests、bs4、json和re模塊來采集網(wǎng)易云音樂歌詞,記得在程序中添加headers和反盜鏈referer以模擬瀏覽器,防止被網(wǎng)站拒絕訪問。這里的get_html方法專門用于獲取源碼,通常我們也要做異常處理,未雨綢繆。
獲取到網(wǎng)頁源碼之后,分析源碼,發(fā)現(xiàn)歌曲的名字和ID藏的很深,縱里尋她千百度,發(fā)現(xiàn)她在源碼的294行,藏在<ul class="f-hide">標(biāo)簽下,如下圖所示:
歌曲名和ID存在的位置
接下來我們利用美麗的湯來獲取目標(biāo)信息,直接上代碼,如下圖:
獲取歌曲名和ID
此處要注意獲取ID的時候需要對link進(jìn)行切片處理,得到的數(shù)字便是歌曲的ID;另外,歌曲名是通過get_text()方法獲取到的,最后利用zip函數(shù)將歌曲名和ID一一對應(yīng)并進(jìn)行返回。
得到ID之后便可以進(jìn)入到內(nèi)頁獲取歌詞了,但是URL還是不給力,如下圖:
歌詞的URL
雖然我們可以明白的看到網(wǎng)頁上的白紙黑字呈現(xiàn)的歌詞信息,但是我們在該URL下卻無法獲取到歌詞信息。小編通過抓包,找到了歌詞的URL,發(fā)現(xiàn)其是POST請求還有一大堆看不懂的data,總之這個URL是不能為我們效力。那該點(diǎn)解呢?
莫慌,小編找到了網(wǎng)易云音樂的API,只要把歌曲的ID放在API鏈接上便可以獲取到歌詞了,代碼如下:
調(diào)用網(wǎng)易云API并解析歌詞
在API中歌詞信息是以json格式加載的,所以需要利用json將其進(jìn)行序列化解析出來,并配合正則表達(dá)式進(jìn)行清洗歌詞,如果不用正則表達(dá)式進(jìn)行清洗的話,得到原始的數(shù)據(jù)如下所示(此處以趙雷的歌曲《成都》為例):
原始數(shù)據(jù)
很明顯歌詞前面有歌詞呈現(xiàn)的時間,對于我們來說其屬于雜質(zhì)信息,因此需要利用正則表達(dá)式進(jìn)行匹配。誠然,正則表達(dá)式并不是唯一的方法,小伙伴們也可以采取切片的方式或者其他方法進(jìn)行數(shù)據(jù)清洗,在此就不贅述了。
得到歌詞之后便將其寫入到文件中去,并存入到本地文件中,代碼如下:
寫入文件和程序主體部分
現(xiàn)在只要我們運(yùn)行程序,輸入歌手的ID之后,程序?qū)⒆詣影言摳枋值乃枨母柙~抓取下來,并存到本地中。如本例中趙雷的ID是6731,輸入數(shù)字6731之后,趙雷的歌詞將會被抓取到,如下圖所示:
程序運(yùn)行結(jié)果
之后我們就可以在腳本程序的同一目錄下找到生成的歌詞文本,歌詞就被順利的爬取下來了。
相信大家對網(wǎng)易云歌詞爬取已經(jīng)有了一定的認(rèn)識了,不過easier said than down,小編建議大家動手親自敲一下代碼,在實(shí)踐中你會學(xué)的更快,學(xué)的更多的。
這篇文章教會大家如何采集網(wǎng)易云歌詞,那網(wǎng)易云歌曲如何采集呢?且聽小編下回分解~~~
一、項(xiàng)目目標(biāo)】
通過Python爬取QQ音樂數(shù)據(jù)(一)我們實(shí)現(xiàn)了獲取 QQ 音樂指定歌手單曲排行指定頁數(shù)的歌曲的歌名、專輯名、播放鏈接。
此次我們在之前的基礎(chǔ)上獲取QQ音樂指定歌曲的歌詞及前15個精彩評論。
【二、需要的庫】
主要涉及的庫有:requests、json、html
【三、項(xiàng)目實(shí)現(xiàn)】
1.以歌曲“泡沫”為例,查看該界面的XHR
2.通過對XHR的Size進(jìn)行排序,逐個查看(參考英文含義),我們看到第一個紅框內(nèi)是歌曲評論,第二個框內(nèi)是歌詞!
3.分別查看這兩條數(shù)據(jù)Headers里面Parms參數(shù)。
4.發(fā)現(xiàn)這幾個參數(shù)可能會代表不同的歌曲,那到底是哪個呢,我們在代開另一首歌對比一下。
5.發(fā)現(xiàn)只有這個topid不同,其他都一樣,這就代表topid代表不同歌曲的id,同理我們看一下歌詞。
6、確定下來:musicid= topid = 歌曲的id,接下來我們的任務(wù)是找到這個id。
7.返回以下界面,也就是我們上一個項(xiàng)目的主戰(zhàn)場。
參考上一個項(xiàng)目,很容易找到“id”的值就是我們要尋找的id。
所以思路確定下來:先通過input()輸入歌名生成url_1找到該歌曲的“id”參數(shù),再生成url_2獲取歌詞和評論。
8.代碼實(shí)現(xiàn):獲取歌曲id,如下所示:
import requests,html,jsonurl_1 = 'https://c.y.qq.com/soso/fcgi-bin/client_search_cp'headers = {'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36', # 標(biāo)記了請求從什么設(shè)備,什么瀏覽器上發(fā)出 }i = input('請輸入需要查詢歌詞的歌曲名稱:')params = {'ct': '24', 'qqmusic_ver': '1298', 'new_json': '1', 'remoteplace': 'txt.yqq.song', 'searchid': '71600317520820180', 't': '0', 'aggr': '1', 'cr': '1', 'catZhida': '1', 'lossless': '0', 'flag_qc': '0', 'p': '1', 'n': '10', 'w': i, 'g_tk': '5381', 'loginUin': '0', 'hostUin': '0', 'format': 'json', 'inCharset': 'utf8', 'outCharset': 'utf-8', 'notice': '0', 'platform': 'yqq.json', 'needNewCode': '0'} res_music = requests.get(url_1,headers=headers,params=params)# 發(fā)起請求json_music = res_music.json()id = json_music['data']['song']['list'][0]['id']print(id)
9.代碼實(shí)現(xiàn):獲取歌詞
實(shí)現(xiàn)方法如下:
url_2 = 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_yqq.fcg'headers = {'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36', # 標(biāo)記了請求從什么設(shè)備,什么瀏覽器上發(fā)出 }params = { 'nobase64':'1', 'musicid':id, #用上面獲取到的id '-':'jsonp1', 'g_tk':'5381', 'loginUin':'0', 'hostUin':'0', 'format':'json', 'inCharset':'utf8', 'outCharset':'utf-8', 'notice':'0', 'platform':'yqq.json', 'needNewCode':'0', }res_music = requests.get(url_2,headers=headers,params=params)# 發(fā)起請求js = res_music.json()lyric = js['lyric']lyric_html = html.unescape(lyric) #用了轉(zhuǎn)義字符html.unescape方法# print(lyric_html)f1 = open(i+'歌詞.txt','a',encoding='utf-8')f1.writelines(lyric_html)f1.close() #存儲到txt中input('下載成功,按回車鍵退出!')
10. 代碼實(shí)現(xiàn):獲取評論。
url_3 = 'https://c.y.qq.com/base/fcgi-bin/fcg_global_comment_h5.fcg'headers = {'user-agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36', # 標(biāo)記了請求從什么設(shè)備,什么瀏覽器上發(fā)出 }params = {'g_tk_new_20200303': '5381', 'g_tk': '5381', 'loginUin': '0', 'hostUin': '0', 'format': 'json', 'inCharset': 'utf8', 'outCharset': 'GB2312', 'notice': '0', 'platform': 'yqq.json', 'needNewCode': '0', 'cid': '205360772', 'reqtype': '2', 'biztype': '1', 'topid': id, 'cmd': '8', 'needmusiccrit': '0', 'pagenum': '0', 'pagesize': '25', 'lasthotcommentid': '', 'domain': 'qq.com', 'ct': '24', 'cv': '10101010'}res_music = requests.get(url_3,headers=headers,params=params)# 發(fā)起請求js = res_music.json()comments = js['hot_comment']['commentlist']f2 = open(i+'評論.txt','a',encoding='utf-8') #存儲到txt中for i in comments: comment = i['rootcommentcontent'] + '\n——————————————————————————————————\n' f2.writelines(comment)# print(comment)f2.close()input('下載成功,按回車鍵退出!')
10. 封裝函數(shù)
11.結(jié)果展示
【四、總結(jié)】
1.項(xiàng)目二比項(xiàng)目一稍復(fù)雜一點(diǎn),多了一步獲取歌曲id的步驟;
2.通過XHR爬取數(shù)據(jù)一般要使用json,格式為:
res =requests.get(url)json =res.json()list = json[‘’][‘’]…
3.學(xué)習(xí)了轉(zhuǎn)義字符html.unescape方法;
4.保存到txt還可以用 with open() as的方法;
5.Python爬取QQ音樂數(shù)據(jù)(第三彈)將為大家?guī)砣绾闻廊「嘣u論,并生成詞云圖(wordcloud)。
6.需要本文源碼的話,請?jiān)诠娞柡笈_回復(fù)“QQ音樂”四個字進(jìn)行獲取。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。