最近,博主喜歡上了聽歌,但是又苦於找不到好音樂,於是就打算到網易雲的歌單中逛逛
本着 “用技術改變生活” 的想法,於是便想着寫一個爬蟲爬取網易雲的歌單,並按播放量自動進行排序
這篇文章,我們就來講講怎樣爬取網易雲歌單,並將歌單按播放量進行排序,下面先上效果圖

1、用 requests 爬取網易雲歌單
打開 網易雲音樂 歌單首頁,不難發現這是一個靜態網頁,而且格式很有規律,爬取起來應該十分簡單
按照以前的套路,很快就可以寫完代碼,無非就是分為下面幾個部分:
(1)獲取網頁源代碼
這里我們使用 requests 發送和接收請求,核心代碼如下:
import requests
def get_page(url):
# 構造請求頭部
headers = {
'USER-AGENT':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
# 發送請求,獲得響應
response = requests.get(url=url,headers=headers)
# 獲取響應內容
html = response.text
return html
(2)解析網頁源代碼
解析數據部分我們使用 xpath(對於 xpath 的語法不太熟悉的朋友,可以看看博主之前的文章)
文章傳送門:


核心代碼如下:
from lxml import etree
# 解析網頁源代碼,獲取數據
def parse4data(self,html):
html_elem = etree.HTML(html)
# 播放量
play_num = html_elem.xpath('//ul[@id="m-pl-container"]/li/div/div/span[@class="nb"]/text()')
# 歌單名稱
song_title = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[1]/a/@title')
# 歌單鏈接
song_href = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[1]/a/@href')
song_link = ['https://music.163.com/#'+item for item in song_href]
# 用戶名稱
user_title = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[2]/a/@title')
# 用戶鏈接
user_href = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[2]/a/@href')
user_link = ['https://music.163.com/#'+item for item in user_href]
# 將數據打包成列表,其中列表中的每一個元素是一個字典,每一個字典對應一份歌單信息
data = list(map(lambda a,b,c,d,e:{'播放量':a,'歌單名稱':b,'歌單鏈接':c,'用戶名稱':d,'用戶鏈接':e},play_num,song_title,song_link,user_title,user_link))
# 返回數據
return data
# 解析網頁源代碼,獲取下一頁鏈接
def parse4link(self,html):
html_elem = etree.HTML(html)
# 下一頁鏈接
href = html_elem.xpath('//div[@id="m-pl-pager"]/div[@class="u-page"]/a[@class="zbtn znxt"]/@href')
# 如果為空,則返回 None;如果不為空,則返回鏈接地址
if not href:
return None
else:
return 'https://music.163.com/#' + href[0]
(3)完整代碼
import requests
from lxml import etree
import json
import time
import random
class Netease_spider:
# 初始化數據
def __init__(self):
self.originURL = 'https://music.163.com/#/discover/playlist'
self.data = list()
# 獲取網頁源代碼
def get_page(self,url):
headers = {
'USER-AGENT':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
response = requests.get(url=url,headers=headers)
html = response.text
return html
# 解析網頁源代碼,獲取數據
def parse4data(self,html):
html_elem = etree.HTML(html)
play_num = html_elem.xpath('//ul[@id="m-pl-container"]/li/div/div/span[@class="nb"]/text()')
song_title = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[1]/a/@title')
song_href = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[1]/a/@href')
song_link = ['https://music.163.com/#'+item for item in song_href]
user_title = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[2]/a/@title')
user_href = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[2]/a/@href')
user_link = ['https://music.163.com/#'+item for item in user_href]
data = list(map(lambda a,b,c,d,e:{'播放量':a,'歌單名稱':b,'歌單鏈接':c,'用戶名稱':d,'用戶鏈接':e},play_num,song_title,song_link,user_title,user_link))
return data
# 解析網頁源代碼,獲取下一頁鏈接
def parse4link(self,html):
html_elem = etree.HTML(html)
href = html_elem.xpath('//div[@id="m-pl-pager"]/div[@class="u-page"]/a[@class="zbtn znxt"]/@href')
if not href:
return None
else:
return 'https://music.163.com/#' + href[0]
# 開始爬取網頁
def crawl(self):
# 爬取數據
print('爬取數據')
html = self.get_page(self.originURL)
data = self.parse4data(html)
self.data.extend(data)
link = self.parse4link(html)
while(link):
html = self.get_page(link)
data = self.parse4data(html)
self.data.extend(data)
link = self.parse4link(html)
time.sleep(random.random())
# 處理數據,按播放量進行排序
print('處理數據')
data_after_sort = sorted(self.data,key=lambda item:int(item['播放量'].replace('萬','0000')),reverse=True)
# 寫入文件
print('寫入文件')
with open('netease.json','w',encoding='utf-8') as f:
for item in data_after_sort:
json.dump(item,f,ensure_ascii=False)
if __name__ == '__main__':
spider = Netease_spider()
spider.crawl()
print('Finished')
2、用 selenium 爬取網易雲歌單
然而,事情真的有這么簡單嗎?
當我們運行上面的代碼的時候,會發現解析網頁源代碼部分,返回的竟然是空列表!
這是為什么呢?敲重點,敲重點,敲重點,這絕對是一個坑啊!
我們重新打開瀏覽器,認真觀察網頁的源代碼

原來,我們所提取的元素被包含在 <iframe> 標簽內部,這樣我們是無法直接進行定位的
因為 iframe 會在原有頁面中加載另外一個頁面,當我們需要獲取內嵌頁面的元素時,需要先切換到 iframe 中
明白了原理之后,重新修改一下上面的代碼
思路是利用 selenium 獲取原有網頁,然后使用 switch_to.frame() 方法切換到 iframe 中,最后返回內嵌網頁
對 selenium 的使用以及環境配置不清楚的朋友,可以參考博主之前的文章
需要修改的地方是獲取網頁源代碼的函數,另外也需要在初始化數據的函數中實例化 webdriver,完整代碼如下:
from selenium import webdriver
from lxml import etree
import json
import time
import random
class Netease_spider:
# 初始化數據(需要修改)
def __init__(self):
# 無頭啟動 selenium
opt = webdriver.chrome.options.Options()
opt.set_headless()
self.browser = webdriver.Chrome(chrome_options=opt)
self.originURL = 'https://music.163.com/#/discover/playlist'
self.data = list()
# 獲取網頁源代碼(需要修改)
def get_page(self,url):
self.browser.get(url)
self.browser.switch_to.frame('g_iframe')
html = self.browser.page_source
return html
# 解析網頁源代碼,獲取數據
def parse4data(self,html):
html_elem = etree.HTML(html)
play_num = html_elem.xpath('//ul[@id="m-pl-container"]/li/div/div/span[@class="nb"]/text()')
song_title = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[1]/a/@title')
song_href = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[1]/a/@href')
song_link = ['https://music.163.com/#'+item for item in song_href]
user_title = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[2]/a/@title')
user_href = html_elem.xpath('//ul[@id="m-pl-container"]/li/p[2]/a/@href')
user_link = ['https://music.163.com/#'+item for item in user_href]
data = list(map(lambda a,b,c,d,e:{'播放量':a,'歌單名稱':b,'歌單鏈接':c,'用戶名稱':d,'用戶鏈接':e},play_num,song_title,song_link,user_title,user_link))
return data
# 解析網頁源代碼,獲取下一頁鏈接
def parse4link(self,html):
html_elem = etree.HTML(html)
href = html_elem.xpath('//div[@id="m-pl-pager"]/div[@class="u-page"]/a[@class="zbtn znxt"]/@href')
if not href:
return None
else:
return 'https://music.163.com/#' + href[0]
# 開始爬取網頁
def crawl(self):
# 爬取數據
print('爬取數據')
html = self.get_page(self.originURL)
data = self.parse4data(html)
self.data.extend(data)
link = self.parse4link(html)
while(link):
html = self.get_page(link)
data = self.parse4data(html)
self.data.extend(data)
link = self.parse4link(html)
time.sleep(random.random())
# 處理數據,按播放量進行排序
print('處理數據')
data_after_sort = sorted(self.data,key=lambda item:int(item['播放量'].replace('萬','0000')),reverse=True)
# 寫入文件
print('寫入文件')
with open('netease.json','w',encoding='utf-8') as f:
for item in data_after_sort:
json.dump(item,f,ensure_ascii=False)
if __name__ == '__main__':
spider = Netease_spider()
spider.crawl()
print('Finished')
這樣,得到目前網易雲音樂中播放量排名前十的歌單如下(哈哈,又可以愉快的聽歌啦):
- 2018年度最熱新歌TOP100
- 聽說你也在找好聽的華語歌
- 精選 | 網絡熱歌分享
- 隔壁老樊的孤單
- 溫柔暴擊 | 沉溺於男友音的甜蜜鄉
- 誰說翻唱不好聽
- 若是心懷舊夢 就別再無疾而終
- KTV必點:有沒有一首歌,唱着唱着就淚奔
- 化妝拍照BGM.
- 會講故事的男聲 歌詞唱的太像自己
注意:本項目代碼僅作學習交流使用!!!
