這次的目標是爬取網易雲音樂上指定歌曲全部評論並生成詞雲git
找到這個ajax接口沒什麼難度,問題在於傳遞的數據,是經過js加密獲得的,所以須要查看js代碼。github
經過斷掉調試能夠找到數據是由core_8556f33641851a422ec534e33e6fa5a4.js?8556f33641851a422ec534e33e6fa5a4.js裏的window.asrsea函數加密的。ajax
經過進一步的查找,能夠找到下面這個函數:json
function() {
// 生成長度爲16的隨機字符串
function a(a) {
var d, e, b = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c = "";
for (d = 0; a > d; d += 1)
e = Math.random() * b.length,
e = Math.floor(e),
c += b.charAt(e);
return c
}
// 實現AES加密
function b(a, b) {
var c = CryptoJS.enc.Utf8.parse(b)
, d = CryptoJS.enc.Utf8.parse("0102030405060708")
, e = CryptoJS.enc.Utf8.parse(a)
, f = CryptoJS.AES.encrypt(e, c, {
iv: d,
mode: CryptoJS.mode.CBC
});
return f.toString()
}
// 實現RSA加密
function c(a, b, c) {
var d, e;
return setMaxDigits(131),
d = new RSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
// 獲得加密後的結果
function d(d, e, f, g) {
var h = {}
, i = a(16);
return h.encText = b(d, g),
h.encText = b(h.encText, i),
h.encSecKey = c(i, e, f),
h
}
function e(a, b, d, e) {
var f = {};
return f.encText = c(a + e, b, d),
f
}
}()
所以咱們須要用Python實現上面四個函數。第一個生成隨機字符串的函數沒難度,實現的代碼以下:api
1 # 生成隨機字符串 2 def generate_random_string(length): 3 string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 4 # 初始化隨機字符串 5 random_string = "" 6 # 生成一個長度爲length的隨機字符串 7 for i in range(length): 8 random_string += string[int(floor(random() * len(string)))] 9 return random_string
第二個是實現AES加密的函數,而用AES加密須要使用Crypto這個庫。若是沒有安裝這個庫的話,須要先安裝pycrypto庫,而後再安裝Crypto庫。在成功安裝以後,若是import的時候沒有Crypto而只有crypto,先打開Python安裝目錄下的Lib\site-packages\crypto文件夾,若是裏面有Cipher文件夾,就返回到Lib\site-packages目錄下把crypto重命名爲Crypto,而後應該就能夠成功導入了。dom
因爲AES加密的明文長度必須是16的倍數,所以咱們須要對明文進行必要的填充,以知足它的長度是16的倍數,AES加密的模式是AES.MODE_CBC,初始化向量iv='0102030405060708′。ide
實現AES加密的代碼以下:函數
1 # AES加密 2 def aes_encrypt(msg, key): 3 # 若是不是16的倍數則進行填充 4 padding = 16 - len(msg) % 16 5 # 這裏使用padding對應的單字符進行填充 6 msg += padding * chr(padding) 7 # 用來加密或者解密的初始向量(必須是16位) 8 iv = '0102030405060708' 9 # AES加密 10 cipher = AES.new(key, AES.MODE_CBC, iv) 11 # 加密後獲得的是bytes類型的數據 12 encrypt_bytes = cipher.encrypt(msg) 13 # 使用Base64進行編碼,返回byte字符串 14 encode_string = base64.b64encode(encrypt_bytes) 15 # 對byte字符串按utf-8進行解碼 16 encrypt_text = encode_string.decode('utf-8') 17 # 返回結果 18 return encrypt_text
第三個是實現RSA加密的函數,在RSA加密中,明文和密文都是數字,RSA的密文是對錶明明文的數字的E次方求mod N 的結果,RSA加密後獲得的字符串長爲256,這裏不夠長咱們用x字符填充。post
實現RSA加密的代碼以下:學習
1 # RSA加密 2 def rsa_encrypt(random_string, key, f): 3 # 隨機字符串逆序排列 4 string = random_string[::-1] 5 # 將隨機字符串轉換成byte類型數據 6 text = bytes(string, 'utf-8') 7 # RSA加密 8 sec_key = int(codecs.encode(text, encoding='hex'), 16) ** int(key, 16) % int(f, 16) 9 # 返回結果 10 return format(sec_key, 'x').zfill(256)
第四個函數是獲得兩個加密參數的函數,傳入的四個參數,第一個參數JSON.stringify(i3x)是如下內容,其中offset和limit參數是必需要有的,offset的值是(頁數-1)*20,limit的值是20
'{"offset":'+str(offset)+',"total":"True","limit":"20","csrf_token":""}'
第二個參數,第三個參數和第四個參數的值都是根據Zj4n.emj獲得的:
encText的值是經過兩次AES加密獲得的,encSecKey是經過RSA加密獲得的,實現的具體代碼以下:
1 # 獲取參數 2 def get_params(page): 3 # 偏移量 4 offset = (page - 1) * 20 5 # offset和limit是必選參數,其餘參數是可選的 6 msg = '{"offset":' + str(offset) + ',"total":"True","limit":"20","csrf_token":""}' 7 key = '0CoJUm6Qyw8W8jud' 8 f = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a87' \ 9 '6aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9' \ 10 'd05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b' \ 11 '8e289dc6935b3ece0462db0a22b8e7' 12 e = '010001' 13 # 生成長度爲16的隨機字符串 14 i = generate_random_string(16) 15 # 第一次AES加密 16 enc_text = aes_encrypt(msg, key) 17 # 第二次AES加密以後獲得params的值 18 encText = aes_encrypt(enc_text, i) 19 # RSA加密以後獲得encSecKey的值 20 encSecKey = rsa_encrypt(i, e, f) 21 return encText, encSecKey
經過查看preview的信息能夠發現用戶名和評論內容都是保存在json格式的數據裏的
所以解析起來會很容易,直接提取nickname和content就行了。對於獲得的數據,都保存在以歌名爲文件名的txt文件中。實現的代碼以下:
1 # 爬取評論內容 2 def get_comments(data): 3 # data=[song_id,song_name,page_num] 4 url = 'https://music.163.com/weapi/v1/resource/comments/R_SO_4_' + str(data[0]) + '?csrf_token=' 5 # 獲得兩個加密參數 6 text, key = get_params(data[2]) 7 # 發送post請求 8 res = requests.post(url, headers=headers, data={"params": text, "encSecKey": key}) 9 if res.status_code == 200: 10 print("正在爬取第{}頁的評論".format(data[2])) 11 # 解析 12 comments = res.json()['comments'] 13 # 存儲 14 with open(data[1] + '.txt', 'a', encoding="utf-8") as f: 15 for i in comments: 16 f.write(i['content'] + "\n") 17 else: 18 print("爬取失敗!")
在進行這一步以前,須要先安裝好jieba和wordcloud兩個模塊,jieba模塊是一個用於中文分詞的模塊,wordcloud模塊是一個用於生成詞雲的模塊,能夠自行了解學習。
這部分就不贅述了,具體代碼以下:
1 # 生成詞雲 2 def make_cloud(txt_name): 3 with open(txt_name + ".txt", 'r', encoding="utf-8") as f: 4 txt = f.read() 5 # 結巴分詞 6 text = ''.join(jieba.cut(txt)) 7 # 定義一個詞雲 8 wc = WordCloud( 9 font_path="font.ttf", 10 width=1200, 11 height=800, 12 max_words=100, 13 max_font_size=200, 14 min_font_size=10 15 ) 16 # 生成詞雲 17 wc.generate(text) 18 # 保存爲圖片 19 wc.to_file(txt_name + ".png")
最後生成的詞雲以下:
完整代碼已經上傳到GitHub!