使用Python爬一爬網易云音樂上那些評論火爆的歌曲

TristanMaco 7年前發布 | 30K 次閱讀 Python Python開發

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

  • 結果

對過程沒有興趣的童鞋直接看這里啦。

評論數大于五萬的歌曲排行榜

首先恭喜一下我最喜歡的歌手(之一)周杰倫的《晴天》成為網易云音樂第一首評論數過百萬的歌曲!

通過結果發現目前評論數過十萬的歌曲正好十首,通過這前十首發現:

  • 薛之謙現在真的很火啦~
  • 幾乎都是男歌手啊,男歌手貌似更受歡迎?(別打我),男歌手中周杰倫、薛之謙、許嵩(這三位我都比較喜歡)幾乎占了榜單半壁江山...
  • 《Fade》電音強勢來襲,很帶感哈(搭配炫邁寫代碼完全停不下來..)

根據結果做了網易云音樂歌單 :

提示: 評論數過五萬的歌曲 歌單中個別歌曲由于版權問題暫時下架,暫由其他優秀版本代替。

高能預警:TOP 29 《Lost Rivers》請慎重播放,如果你堅持播放請先看評論...

  • 過程

    1. 觀察網易云音樂官網頁面HTML結構
      首頁( 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,我們發現只要爬取到對應歌曲的ID就可以得到它的詳情頁URL,而歌曲的信息都在詳情頁。由此可知只要收集到所有歌曲的ID那么就可以得到所有歌曲的信息啦。而這些ID要從哪里爬呢?從歌單里爬,而歌單在哪爬呢?通過觀察歌單頁的URL我們發現歌單也有ID,而歌單ID可以從歌單分類頁中爬,好了就這樣爬最終就能收集到所有歌曲的ID了。
    3. 通過爬取評論數篩選出符合條件的歌曲

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

      響應結果很豐富呢,所有評論相關的數據都有,不過經過觀察發現這個API是經過加密處理的,不過沒關系...

    4. 爬取符合條件的歌曲的詳細信息(名字,歌手等)

      這一步就很簡單了,觀察下歌曲詳情頁的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()


友情提示 :隨著網易云音樂網站結構、接口、加密方式的更換本代碼可能并不能很好的工作,不過過程和原理都是一樣的,這里也只是給大家分享一下這一過程啦。

 

來自:http://www.jianshu.com/p/50d99bd7ed62

 

 本文由用戶 TristanMaco 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!