前言
網(wǎng)易云音樂這款音樂APP本人比較喜歡,用戶量也比較大,而網(wǎng)易云音樂之所以用戶眾多和它的歌曲評論功能密不可分,很多歌曲的評論非常有意思,其中也不乏很多感人的評論。但是,網(wǎng)易云音樂并沒有提供熱評排行榜和按評論排序的功能,沒關(guān)系,本文就使用爬蟲給大家爬一爬網(wǎng)易云音樂上那些熱評的歌曲。
結(jié)果

對過程沒有興趣的童鞋直接看這里啦。
評論數(shù)大于五萬的歌曲排行榜
首先恭喜一下我最喜歡的歌手(之一)周杰倫的《晴天》成為網(wǎng)易云音樂第一首評論數(shù)過百萬的歌曲!
通過結(jié)果發(fā)現(xiàn)目前評論數(shù)過十萬的歌曲正好十首,通過這前十首發(fā)現(xiàn):
根據(jù)結(jié)果做了網(wǎng)易云音樂歌單 :
提示: 評論數(shù)過五萬的歌曲 歌單中個別歌曲由于版權(quán)問題暫時下架,暫由其他優(yōu)秀版本代替。
高能預(yù)警:TOP 29 《Lost Rivers》請慎重播放,如果你堅(jiān)持播放請先看評論...
過程
1、觀察網(wǎng)易云音樂官網(wǎng)頁面HTML結(jié)構(gòu)
首頁(http://music.163.com/)
歌單分類頁(http://music.163.com/discover/playlist)。
歌單頁(http://music.163.com/playlist?id=499518394)
歌曲詳情頁(http://music.163.com/song?id=109998)
2、爬取歌曲的ID
通過觀察歌曲詳情頁的URL,我們發(fā)現(xiàn)只要爬取到對應(yīng)歌曲的ID就可以得到它的詳情頁URL,而歌曲的信息都在詳情頁。由此可知只要收集到所有歌曲的ID那么就可以得到所有歌曲的信息啦。而這些ID要從哪里爬呢?從歌單里爬,而歌單在哪爬呢?通過觀察歌單頁的URL我們發(fā)現(xiàn)歌單也有ID,而歌單ID可以從歌單分類頁中爬,好了就這樣爬最終就能收集到所有歌曲的ID了。
3、通過爬取評論數(shù)篩選出符合條件的歌曲

很遺憾的是評論數(shù)雖然也在詳情頁內(nèi),但是網(wǎng)易云音樂做了防爬處理,采用AJAX調(diào)用評論數(shù)API的方式填充評論相關(guān)數(shù)據(jù),由于異步的特性導(dǎo)致我們爬到的頁面中評論數(shù)是空,那么我們就找一找這個API吧,通關(guān)觀察XHR請求發(fā)現(xiàn)是下面這個家伙..


響應(yīng)結(jié)果很豐富呢,所有評論相關(guān)的數(shù)據(jù)都有,不過經(jīng)過觀察發(fā)現(xiàn)這個API是經(jīng)過加密處理的,不過沒關(guān)系...
4、爬取符合條件的歌曲的詳細(xì)信息(名字,歌手等)
這一步就很簡單了,觀察下歌曲詳情頁的HTML很容易就能爬到我們要的名字和歌手信息。
源碼
# encoding=utf8import requestsfrom bs4 import BeautifulSoupimport os, jsonimport base64from Crypto.Cipher import AESfrom prettytable import PrettyTableimport warningswarnings.filterwarnings("ignore")BASE_URL = 'http://music.163.com/'_session = requests.session()# 要匹配大于多少評論數(shù)的歌曲COMMENT_COUNT_LET = 100000class Song(object): def __lt__(self, other): return self.commentCount > other.commentCount# 由于網(wǎng)易云音樂歌曲評論采取AJAX填充的方式所以在HTML上爬不到,需要調(diào)用評論API,而API進(jìn)行了加密處理,下面是相關(guān)解決的方法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 ciphertextdef 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]# 通過第三方渠道獲取網(wǎng)云音樂的所有歌曲ID# 這里偷了個懶直接從http://grri94kmi4.app.tianmaying.com/songs爬了,這哥們已經(jīng)把官網(wǎng)的歌曲都爬過來了,省事不少# 也可以使用getSongIdList()從官方網(wǎng)站爬,相對比較耗時,但更準(zhǔn)確def getSongIdListBy3Party(): pageMax = 1 # 要爬的頁數(shù),可以根據(jù)需求選擇性設(shè)置頁數(shù) 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# 從官網(wǎng)的 發(fā)現(xiàn)-> 歌單 頁面爬取網(wǎng)云音樂的所有歌曲IDdef getSongIdList(): pageMax = 1 # 要爬的頁數(shù),目前一共42頁,爬完42頁需要很久很久,可以根據(jù)需求選擇性設(shè)置頁數(shù) 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) # 歌單里難免有重復(fù)的歌曲,去一下重復(fù)的歌曲ID songIdList = list(set(songIdList)) return songIdList# 匹配歌曲的評論數(shù)是否符合要求# let 評論數(shù)大于值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# 設(shè)置歌曲的信息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') # 去除歌曲名稱后面()內(nèi)的字,如果不想去除可以注掉下面三行代碼 index = name.find('(') if index > 0: name = name[0:index] song.name = name# 獲取符合條件的歌曲列表def getSongList(): print ' ##正在爬取歌曲編號... ##' # songIdList = getSongIdList() songIdList = getSongIdListBy3Party() print ' ##爬取歌曲編號完成,共計(jì)爬取到' + str(len(songIdList)) + '首##' songList = [] print ' ##正在爬取符合評論數(shù)大于' + 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, ',評論數(shù):', song.commentCount, '}' print ' ##爬取完成,符合條件的的共計(jì)' + str(len(songList)) + '首##' return songListdef main(): songList = getSongList() # 按評論數(shù)從高往低排序 songList.sort() # 打印結(jié)果 table = PrettyTable([u'排名', u'評論數(shù)', 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()友情提示:隨著網(wǎng)易云音樂網(wǎng)站結(jié)構(gòu)、接口、加密方式的更換本代碼可能并不能很好的工作,不過過程和原理都是一樣的,這里也只是給大家分享一下這一過程啦。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。
新聞熱點(diǎn)
疑難解答
圖片精選