1. 程式人生 > 其它 >用Python爬取網易雲音樂的使用者評論文字

用Python爬取網易雲音樂的使用者評論文字

本文利用Python2.7根據網易雲音樂歌曲ID爬取了該歌曲的所有使用者評論資料。以id是28875120的歌曲《小歲月太著急》為示例,通過Chrome的DevTools工具獲取已加密評論資料,然後基於AES對稱加密演算法對已加密資料進行解密實現,最後使用Python成功實現了對使用者評論資料的抓取與儲存。

利用DevTools工具獲取加密資料

進入 http://music.163.com/#/song?id=28875120 頁面,開啟Chrome的DevTools工具選擇Network並重載頁面,找到與評論資料相關的請求即name為R_SO_4_28875120?csrf_token=90e04572eb42b040167323ec2fcdd79f的POST請求,如下圖所示:

檢視該請求資訊,可知Request Headers引數如下:

其中的POST Request URL完整地址為 : http://music.163.com/weapi/v1/resource/comments/R_SO_4_28875120?csrf_token=90e04572eb42b040167323ec2fcdd79f

並且,該Form Data含有params和encSecKey兩個引數,顯然,這兩個引數是經過js加密後的。伺服器返回的和評論相關的資料為json格式的,裡面含有非常豐富的資訊(比如有關評論者的資訊,評論日期,點贊數,評論內容等等),同時,通過檢視第一張圖可知該請求的Initiator為core.js,因此需要通過檢視該js原始碼來分析兩個引數的值。

根據原始碼分析加密資料

AES加密演算法分析

AES(Advanced Encryption Standard)對稱加密演算法是一種高階資料加密標準,可有效抵制針對DES的攻擊演算法。特點:金鑰建立時間短、靈敏性好、記憶體需求低、安全性高。

將core.js檔案clone到本地並對其格式化,發現params和encSecKey兩個引數同時出現在以下程式碼中:

params的值為byw6q.encText,encSecKey的值為byw6q.encSecKey,而byw6q是由window.asrsea()這個函式得到,定位這個函式發現他其實是d(d, e, f, g)函式,該d函式程式碼如下圖所示:

由程式碼可知,d函式對json格式的明文資料params呼叫了兩次AES對稱加密(即b函式):第一次對params的值d加密,key是第四個引數g,第二次對第一次加密結果h.encText值進行加密並更新encText值,key是長度為16的隨機字串i(不妨設其值為16個F)。而且,在b加密函式中,金鑰偏移量iv值為”0102030405060708”,密碼工作模式model值為CBC,即密文連結分組密碼工作模式(明文加密前需要先和前面的密文進行異或運算,也就是相同的明文加密後產生不同的密文)。而d函式的第一個引數JSON.stringify(j5o)的值由j5o決定,在不同的請求下隨著j5o的變化會有不同的值,而後面的三個引數均為定值。

獲取 params值的程式碼如下:

# 第二個引數
second_param = "010001" 
# 第三個引數
third_param = "00e0b509f6259df8642dbc35662901477df22677ec152b
5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f561
35fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46
bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc
6935b3ece0462db0a22b8e7"
# 第四個引數
forth_param = "0CoJUm6Qyw8W8jud"

def get_params(page): # page為傳入頁數
    iv = "0102030405060708"
    first_key = forth_param
    second_key = 16 * 'F'
    if(page == 1): 
        # offset的取值為:(評論頁數-1)*20,total第一頁為true,其餘頁為false
        first_param = '{rid:"", offset:"0", total:"true", limit:"20", csrf_token:""}'
        h_encText = AES_encrypt(first_param, first_key, iv)
    else:
        offset = str((page-1)*20)
        first_param = '{rid:"", offset:"%s", total:"%s", limit:"20", csrf_token:""}'
 %(offset,'false')
        h_encText = AES_encrypt(first_param, first_key, iv)
    h_encText = AES_encrypt(h_encText, second_key, iv)
    return h_encText

encSecKey的值由c(i, e, f)函式,其引數均為常數,因此無論歌曲id或評論頁數如何變化,該值均不變。獲取encSecKey值的程式碼如下:

def get_encSecKey():
    encSecKey = "257348aecb5e556c066de214e531faadd1c55d814f9be95fd06d6bff9f4c7a41f831f6394d5a3fd2e3881736d94a02ca919d952872e7d0a50ebfa1769a7a62d512f5f1ca21aec60bc3819a9c3ffca5eca9a0dba6d6f7249b06f5965ecfff3695b54e1c28f3f624750ed39e7de08fc8493242e26dbc4484a01c76f739e135637c"
    return encSecKey

AES解密演算法實現

這裡用到的AES演算法為AES-128-CBC,金鑰偏移量iv值為”0102030405060708”,分組密碼填充方式為PKCS5Padding(即需對待加解密塊按需補位),輸出格式為base64。對稱演算法中加密與解密實現一致,程式碼如下:

def AES_encrypt(text, key, iv):
    pad = 16 - len(text) % 16
    text = text + pad * chr(pad)
    encryptor = AES.new(key, AES.MODE_CBC, iv)
    encrypt_text = encryptor.encrypt(text)
    encrypt_text = base64.b64encode(encrypt_text)    return encrypt_text

使用Python抓取評論資料

抓取評論資料

獲取json格式的評論資料的程式碼如下:

# 設定代理伺服器proxies= {            'http:':'http://121.232.146.184',             'https:':'https://144.255.48.197'
        }def get_json(url, params, encSecKey):
    data = {               "params": params,               "encSecKey": encSecKey
    }
    response = requests.post(url,        headers=headers, data=data,proxies = proxies)            return response.content

抓取熱門評論的程式碼如下:

def get_hot_comments(url):
    hot_comments_list = []
    hot_comments_list.append(u"使用者ID 使用者暱稱 使用者頭像地址 評論時間 點贊總數 評論內容n")
    params = get_params(1) # 第一頁
    encSecKey = get_encSecKey()
    json_text = get_json(url,params,encSecKey)
    json_dict = json.loads(json_text)
    hot_comments = json_dict['hotComments'] # 熱門評論
    print("共有%d條熱門評論!" % len(hot_comments))    for item in hot_comments:
            comment = item['content'] # 評論內容
            likedCount = item['likedCount'] # 點贊總數
            comment_time = item['time'] # 評論時間(時間戳)
            userID = item['user']['userID'] # 評論者id
            nickname = item['user']['nickname'] # 暱稱
            avatarUrl = item['user']['avatarUrl'] # 頭像地址
            comment_info = userID + " " + nickname + " " + avatarUrl + " " + comment_time + " " + likedCount + " " + comment + u"n"
            hot_comments_list.append(comment_info)    return hot_comments_list
def get_all_comments(url):
    all_comments_list = [] # 存放所有評論
    all_comments_list.append(u"使用者ID 使用者暱稱 使用者頭像地址 評論時間 點贊總數 評論內容n") # 頭部資訊
    params = get_params(1)
    encSecKey = get_encSecKey()
    json_text = get_json(url,params,encSecKey)
    json_dict = json.loads(json_text)
    comments_num = int(json_dict['total'])    if(comments_num % 20 == 0):
        page = comments_num / 20
    else:
        page = int(comments_num / 20) + 1
    print("共有%d頁評論!" % page)    for i in range(page):  # 逐頁抓取
        params = get_params(i+1)
        encSecKey = get_encSecKey()
        json_text = get_json(url,params,encSecKey)
        json_dict = json.loads(json_text)        if i == 0:
            print("共有%d條評論!" % comments_num) # 全部評論總數
        for item in json_dict['comments']:
            comment = item['content'] # 評論內容
            likedCount = item['likedCount'] # 點贊總數
            comment_time = item['time'] # 評論時間(時間戳)
            userID = item['user']['userId'] # 評論者id
            nickname = item['user']['nickname'] # 暱稱
            avatarUrl = item['user']['avatarUrl'] # 頭像地址
            comment_info = unicode(userID) + u" " + nickname + u" " + avatarUrl + u" " + unicode(comment_time) + u" " + unicode(likedCount) + u" " + comment + u"n"
            all_comments_list.append(comment_info)
        print("第%d頁抓取完畢!" % (i+1))    return all_comments_list

寫入文字檔案

將已獲得的評論資料寫入文字檔案,程式碼如下:

def save_to_file(list,filename):
        with codecs.open(filename,'a',encoding='utf-8') as f:
            f.writelines(list)
        print("寫入檔案成功!")

利用Python獲得資料結果

獲取Headers資料

獲取headers程式碼如下:

headers = {    'Accept':"*/*",    'Accept-Encoding':"gzip, deflate",    'Accept-Language':"zh-CN,zh;q=0.8",    'Connection':"keep-alive",      'Content-Length':"416",    'Content-Type':"application/x-www-form-urlencoded",    'Cookie':"_ntes_nnid=61528ed156a887c721f86bb28fb76864,1498012702495; ...; __csrf=880488a01f19e0b9f25a81842477c87b",    'Host':"music.163.com",    'Origin':"http://music.163.com",    'Referer':"http://music.163.com/",    'User-Agent':"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
    }

資料抓取實現

安裝pycrypto

#pip uninstall Crypto#pip uninstall pycryptopip install pycrypto

抓取資料

#!/usr/bin/env python2.7# 、-*- coding: utf-8 -*-from Crypto.Cipher import AESimport base64import requestsimport jsonimport codecsimport timeif __name__ == "__main__":
    start_time = time.time() # 開始時間
    url = "http://music.163.com/weapi/v1/resource/comments/R_SO_4_28875120?csrf_token=90e04572eb42b040167323ec2fcdd79f"
    filename = u"小歲月太著急.txt"
    all_comments_list = get_all_comments(url)
    save_to_file(all_comments_list,filename)
    end_time = time.time() #結束時間
    print("程式耗時%f秒." % (end_time - start_time))

資料結果輸出

程式碼實現

ZerodeMBP:~ zero$ cd /Users/zero/Documents/163mc_spider
ZerodeMBP:163mc_spider zero$ python 163mc_spider.py
共有22頁評論!
共有434條評論!
第1頁抓取完畢!
第2頁抓取完畢!
...
第22頁抓取完畢!
寫入檔案成功!
程式耗時3.193853秒.

評論資料檢視

http://blog.csdn.net/weixin_37325825/article/details/73556908