網易雲音樂這款音樂APP本人比較喜歡,使用者量也比較大,而網易雲音樂之是以使用者衆多和它的歌曲評論功能密不可分,很多歌曲的評論非常有意思,其中也不乏很多感人的評論。但是,網易雲音樂并沒有提供熱評排行榜和按評論排序的功能,沒關系,本文就使用爬蟲給大家爬一爬網易雲音樂上那些熱評的歌曲。
結果
對過程沒有興趣的童鞋直接看這裡啦。

評論數大于五萬的歌曲排行榜
首先恭喜一下我最喜歡的歌手(之一)周傑倫的《晴天》成為網易雲音樂第一首評論數過百萬的歌曲!
通過結果發現目前評論數過十萬的歌曲正好十首,通過這前十首發現:
薛之謙現在真的很火啦~
幾乎都是男歌手啊,男歌手貌似更受歡迎?(别打我),男歌手中周傑倫、薛之謙、許嵩(這三位我都比較喜歡)幾乎占了榜單半壁江山...
《Fade》電音強勢來襲,很帶感哈(搭配炫邁寫代碼完全停不下來..)
根據結果做了網易雲音樂歌單 :
提示: 評論數過五萬的歌曲 歌單中個别歌曲由于版權問題暫時下架,暫由其他優秀版本代替。
高能預警:TOP 29 《Lost Rivers》請慎重播放,如果你堅持播放請先看評論...
過程
爬取歌曲的ID
通過觀察歌曲詳情頁的URL,我們發現隻要爬取到對應歌曲的ID就可以得到它的詳情頁URL,而歌曲的資訊都在詳情頁。由此可知隻要收集到所有歌曲的ID那麼就可以得到所有歌曲的資訊啦。而這些ID要從哪裡爬呢?從歌單裡爬,而歌單在哪爬呢?通過觀察歌單頁的URL我們發現歌單也有ID,而歌單ID可以從歌單分類頁中爬,好了就這樣爬最終就能收集到所有歌曲的ID了。
通過爬取評論數篩選出符合條件的歌曲

很遺憾的是評論數雖然也在詳情頁内,但是網易雲音樂做了防爬處理,采用AJAX調用評論數API的方式填充評論相關資料,由于異步的特性導緻我們爬到的頁面中評論數是空,那麼我們就找一找這個API吧,通關觀察XHR請求發現是下面這個家夥..


響應結果很豐富呢,所有評論相關的資料都有,不過經過觀察發現這個API是經過加密處理的,不過沒關系...
爬取符合條件的歌曲的詳細資訊(名字,歌手等)
這一步就很簡單了,觀察下歌曲詳情頁的HTML很容易就能爬到我們要的名字和歌手資訊。
源碼
# encoding=utf8
import requests
from bs4 import BeautifulSoup
import os, json
import base64
from Crypto.Cipher import AES
from prettytable import PrettyTable
import warnings
warnings.filterwarnings("ignore")
BASE_URL = 'http://music.163.com/'
_session = requests.session()
# 要比對大于多少評論數的歌曲
COMMENT_COUNT_LET = 100000
class Song(object):
def __lt__(self, other):
return self.commentCount > other.commentCount
# 由于網易雲音樂歌曲評論采取AJAX填充的方式是以在HTML上爬不到,需要調用評論API,而API進行了加密處理,下面是相關解決的方法
def aesEncrypt(text, secKey):
pad = 16 - len(text) % 16
text = text + pad * chr(pad)
encryptor = AES.new(secKey, 2, '0102030405060708')
ciphertext = encryptor.encrypt(text)
ciphertext = base64.b64encode(ciphertext)
return ciphertext
def rsaEncrypt(text, pubKey, modulus):
text = text[::-1]
rs = int(text.encode('hex'), 16) ** int(pubKey, 16) % int(modulus, 16)
return format(rs, 'x').zfill(256)
def createSecretKey(size):
return (''.join(map(lambda xx: (hex(ord(xx))[2:]), os.urandom(size))))[0:16]
# 通過第三方管道擷取網雲音樂的所有歌曲ID
# 這裡偷了個懶直接從http://grri94kmi4.app.tianmaying.com/songs爬了,這哥們已經把官網的歌曲都爬過來了,省事不少
# 也可以使用getSongIdList()從官方網站爬,相對比較耗時,但更準确
def getSongIdListBy3Party():
pageMax = 1 # 要爬的頁數,可以根據需求選擇性設定頁數
songIdList = []
for page in range(pageMax):
url = 'http://grri94kmi4.app.tianmaying.com/songs?page=' + str(page)
# print url
url.decode('utf-8')
soup = BeautifulSoup(_session.get(url).content)
# print soup
aList = soup.findAll('a', attrs={'target': '_blank'})
for a in aList:
songId = a['href'].split('=')[1]
songIdList.append(songId)
return songIdList
# 從官網的 發現-> 歌單 頁面爬取網雲音樂的所有歌曲ID
def getSongIdList():
pageMax = 1 # 要爬的頁數,目前一共42頁,爬完42頁需要很久很久,可以根據需求選擇性設定頁數
songIdList = []
for i in range(1, pageMax + 1):
url = 'http://music.163.com/discover/playlist/?order=hot&cat=全部&limit=35&offset=' + str(i * 35)
url.decode('utf-8')
soup = BeautifulSoup(_session.get(url).content)
aList = soup.findAll('a', attrs={'class': 'tit f-thide s-fc0'})
for a in aList:
uri = a['href']
playListUrl = BASE_URL + uri[1:]
soup = BeautifulSoup(_session.get(playListUrl).content)
ul = soup.find('ul', attrs={'class': 'f-hide'})
for li in ul.findAll('li'):
songId = (li.find('a'))['href'].split('=')[1]
print '爬取歌曲ID成功 -> ' + songId
songIdList.append(songId)
# 歌單裡難免有重複的歌曲,去一下重複的歌曲ID
songIdList = list(set(songIdList))
return songIdList
# 比對歌曲的評論數是否符合要求
# let 評論數大于值
def matchSong(songId, let):
url = BASE_URL + 'weapi/v1/resource/comments/R_SO_4_' + str(songId) + '/?csrf_token='
headers = {'Cookie': 'appver=1.5.0.75771;', 'Referer': 'http://music.163.com/'}
text = {'username': '', 'password': '', 'rememberLogin': 'true'}
modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7'
nonce = '0CoJUm6Qyw8W8jud'
pubKey = '010001'
text = json.dumps(text)
secKey = createSecretKey(16)
encText = aesEncrypt(aesEncrypt(text, nonce), secKey)
encSecKey = rsaEncrypt(secKey, pubKey, modulus)
data = {'params': encText, 'encSecKey': encSecKey}
req = requests.post(url, headers=headers, data=data)
total = req.json()['total']
if int(total) > let:
song = Song()
song.id = songId
song.commentCount = total
return song
# 設定歌曲的資訊
def setSongInfo(song):
url = BASE_URL + 'song?id=' + str(song.id)
url.decode('utf-8')
soup = BeautifulSoup(_session.get(url).content)
strArr = soup.title.string.split(' - ')
song.singer = strArr[1]
name = strArr[0].encode('utf-8')
# 去除歌曲名稱後面()内的字,如果不想去除可以注掉下面三行代碼
index = name.find('(')
if index > 0:
name = name[0:index]
song.name = name
# 擷取符合條件的歌曲清單
def getSongList():
print ' ##正在爬取歌曲編号... ##'
# songIdList = getSongIdList()
songIdList = getSongIdListBy3Party()
print ' ##爬取歌曲編号完成,共計爬取到' + str(len(songIdList)) + '首##'
songList = []
print ' ##正在爬取符合評論數大于' + str(COMMENT_COUNT_LET) + '的歌曲... ##'
for id in songIdList:
song = matchSong(id, COMMENT_COUNT_LET)
if None != song:
setSongInfo(song)
songList.append(song)
print '成功比對一首{名稱:', song.name, '-', song.singer, ',評論數:', song.commentCount, '}'
print ' ##爬取完成,符合條件的的共計' + str(len(songList)) + '首##'
return songList
def main():
songList = getSongList()
# 按評論數從高往低排序
songList.sort()
# 列印結果
table = PrettyTable([u'排名', u'評論數', u'歌曲名稱', u'歌手'])
for index, song in enumerate(songList):
table.add_row([index + 1, song.commentCount, song.name, song.singer])
print table
print 'End'
if __name__ == '__main__':
main()
友情提示:随着網易雲音樂網站結構、接口、加密方式的更換本代碼可能并不能很好的工作,不過過程和原理都是一樣的,這裡也隻是給大家分享一下這一過程啦。這裡有個Java語言的實作教程不過源碼不全,有興趣的可以看看,本文代碼中的getSongIdListBy3Party()就是從這哥們的結果中爬的。