用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