爬取網易雲音樂的評論後,竟有這種發現!
作者 | 志穎
責編 | 胡巍巍
用過網易雲音樂聽歌的朋友都知道,網易雲音樂每首歌曲後面都有很多評論,熱門歌曲的評論更是接近百萬或者是超過百萬條。
現在筆者就來分享一下如何爬取網易雲音樂歌曲的全部評論,由於網易雲音樂的評論都做了混淆加密處理,因此我們需要深入了解它的加密過程之後,才能爬取到網易雲音樂歌曲的全部評論。
首先分析數據的請求方式
網易雲音樂歌曲頁面的URL形式為https://music。163。com/#/song?id=歌曲ID號,這裡我用Delacey的Dream it possible 為例進行講解,它的URL為https://music。163。com/#/song?id=38592976。接下來開始分析數據的請求方式。
由於網易雲音樂的評論是通過Ajax傳輸,我們打開瀏覽器的開發者工具(檢查元素),選中控制面板中的Network,再點擊XHR(捕獲AJAX數據),然後點擊左上角的重新載入,會看到下面圖片中的數據請求列表。
現我們所需要的數據就在這JSON格式的數據中,其中Comments中是第一頁的全部評論,一共20條,Hot Comments是精彩評論一共有15條,每首歌曲只有第一頁評論才有精彩評論。接著看一下它的請求頭,點擊Headers。
我們發現的它是個Post請求,向下滑你會發現這個Post請求還帶有數據。
這些數據都是經過加密處理的,因此我們需要分析它的加密過程來生成相應的參數,然後把加密後的參數加到Post請求中才能獲取到我們需要的評論數據。
分析加密過程
通過斷點調試發現params和encSecKey是由JS腳本中的Window.asrsea()函數生成的。
我們發現Window.asrsea()函數有4個參數,在瀏覽器的JS控制台分別對這四個參數進行調試:
後面三個參數是定值,只有第一個參數是控制評論頁面偏移量的參數,它是一個變數。筆者經過分析發現第一個參數的形式是:
{"rid":"R_SO_4_38592976","offset":"0","total":"True","limit":"20","csrf_token":""}
下面我來詳細講解這個變數的發現過程:
首先找到core_dfe56728795d119e4d476fd09ea2dc51。JS這個JS腳本,然後將斷點打在第12973行,點擊第一頁評論,頁面載入到斷點處便停止了。
然後按下電腦的Esc鍵打開JS控制台,輸入i1x,查看第一個變數:
這是第一頁的i1x的值,接下來看第二頁的(需要點擊第2頁,然後輸入i1x的值):
再看第3頁:
再看第4頁:
通過這幾頁的分析,我們可以得到i1x值的變化規律,且可以得到它的一般形式:
{"rid":"R_SO_4_38592976","offset":"0","total":"True","limit":"20","csrf_token":""}
offset和limit是必選參數,其他參數是可選的,其他參數不影響data數據的生成,offset (頁面偏移量) = (頁數-1) * 20, 注意limit最大值為100,當設為100時,獲取第二頁時,默認前一頁是20個評論,也就是說第二頁最新評論有80個,有20個是第一頁顯示的。因此我們可以構造第一個參數為:
接下來,我們來看一下window.asrsea()函數的整個加密過程:
!function(){
// 函數a生成長度為16的隨機字元串
functiona(a){
vard, e, b ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", c ="";
for(d =; a > d; d +=1)
e =Math.random() * b.length,
e =Math.floor(e),
c += b.charAt(e);
returnc
}
// 函數b實現AES加密
functionb(a, b){
varc = 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
});
returnf.toString()
}
// 函數c實現RSA加密
functionc(a, b, c){
vard, e;
returnsetMaxDigits(131),
d =newRSAKeyPair(b,"",c),
e = encryptedString(d, a)
}
functiond(d, e, f, g){
varh = {}
, i = a(16);
returnh.encText = b(d, g),
h.encText = b(h.encText, i),
h.encSecKey = c(i, e, f),
h
}
functione(a, b, d, e){
varf = {};
returnf.encText = c(a + e, b, d),
f
}
window.asrsea = d,
window.ecnonasr = e
}();
window.asrsea()函數就是上面的d函數,現在我們來看函數d:
functiond(d, e, f, g){
varh = {}
, i = a(16);
returnh.encText = b(d, g),// 第一次AES加密
h.encText = b(h.encText, i),// 第二次AES加密
h.encSecKey = c(i, e, f),// RSA加密
h
}
參數h。encText是經過兩次AES加密得到的,h。encSecKey是經過一次RSA加密得到的,其中i是隨機生成的長度為16的隨機字元串。
生成加密參數
首先我們需要生成長度為16的隨機字元串,這裡我們仿照上面的javascript的實現,用Python生成16位長的隨機字元串:
# 生成隨機字元串
defgenerate_random_strs(length):
string="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
# 控制次數參數i
i =
# 初始化隨機字元串
random_strs =""
whilei
e = random.random() * len(string)
# 向下取整
e = math.floor(e)
random_strs = random_strs + list(string)[e]
i = i +1
returnrandom_strs
接著用Python實現AES加密,這裡要用到PyCrypto庫,先安裝好這個庫:
pipinstall pycrypto
然後導入加密模塊:
fromCrypto.CipherimportAES
由於AES加密的明文長度必須是16的倍數,因此我們需要對明文進行必要的填充,以滿足它的長度是16的倍數:
# msg是需要加密的明文,如果不是16的倍數則進行填充(paddiing)
padding=16- len(msg) %16
# 這裡使用padding對應的單字元進行填充
msg= msg + padding * chr(padding)
# AES加密
defAESencrypt(msg, key):
# 如果不是16的倍數則進行填充(paddiing)
padding =16- len(msg) %16
# 這裡使用padding對應的單字元進行填充
msg = msg + padding * chr(padding)
# 用來加密或者解密的初始向量(必須是16位)
iv ="0102030405060708"
cipher = AES.new(key, AES.MODE_CBC, iv)
# 加密後得到的是bytes類型的數據
encryptedbytes = cipher.encrypt(msg)
# 使用Base64進行編碼,返回byte字元串
encodestrs = base64.b64encode(encryptedbytes)
# 對byte字元串按utf-8進行解碼
enctext = encodestrs.decode("utf-8")
returnenctext
然後是RSA加密。首先我簡單介紹一下RSA的加密過程。在RSA中,明文,密鑰和密文都是數字。RSA的加密過程可以用下列的公式來表達,這個公式非常的重要,你只有理解了這個公式,才能用Python實現RSA加密。
密文 = 明文EmodN (RSA加密)
RSA的密文是對代表明文的數字的E次方求mod N的結果, 通俗地講就是將明文和自己做E次乘法,然後將其結果除以N求餘數,這個餘數就是密文。
下面來看具體的RSA加密代碼實現:
# RSA加密
def RSAencrypt(randomstrs,key, f):
# 隨機字元串逆序排列
string= randomstrs[::-1]
# 將隨機字元串轉換成byte類型數據
text= bytes(string,"utf-8")
seckey = int(codecs.encode(text, encoding="hex"), 16)**int(key, 16) % int(f, 16)
# 返回整數的小寫十六進位形式
returnformat(seckey,"x").zfill(256)
RSA加密後得到的字元串長為256,如果不夠長則進行填充(不足部分在左側添0)。
最後就是獲取那兩個加密參數:
# 獲取參數
defget_params(page):
# msg也可以寫成msg= {"offset":"頁面偏移量=(頁數-1) * 20","limit":"20"},offset和limit這兩個參數必須有(js)
# limit最大值為100,當設為100時,獲取第二頁時,默認前一頁是20個評論,也就是說第二頁最新評論有80個,有20個是第一頁顯示的
# 偏移量
offset = (page-1) *20
# offset和limit是必選參數,其他參數是可選的,其他參數不影響data數據的生成,最好還是保留
msg ="{"offset":"+ str(offset) +","total":"True","limit":"20","csrf_token":""}"
key ="0CoJUm6Qyw8W8jud"
f ="00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
e ="010001"
enctext = AESencrypt(msg, key)
# 生成長度為16的隨機字元串
i = generate_random_strs(16)
# 兩次AES加密之後得到params的值
encText = AESencrypt(enctext, i)
# RSA加密之後得到encSecKey的值
encSecKey = RSAencrypt(i, e, f)
returnencText, encSecKey
獲取全部評論
上面我們獲取到了兩個參數encText和encSecKey,利用這兩個參數來構造post表單數據(Form Data),即data的值:
params, encSecKey = get_params(page)
data = {"params":params,"encSecKey": encSecKey}
歌曲評論的URL為:
url="https://music.163.com/weapi/v1/resource/comments/R_SO_4_"+ str(songid) +"?csrf_token="
然後把data加到post的參數中去就能獲取到json格式的評論數據。
html= requests.post(url, headers=headers, data=data)
至此,獲取網易雲音樂全部評論的Python爬蟲實現原理分析全部完成!
作者:志穎,一名狂熱的python爬蟲愛好者。
本文系作者投稿,不代表CSDN立場。
微信改版了,
想快速看到CSDN的熱乎文章,
趕快把CSDN公眾號設為星標吧,
打開公眾號,點擊「設為星標」就可以啦!
2018 AI開發者大會
AI工程師必備大會
2018 AI開發者大會是一場由中美人工智慧技術高手聯袂打造的AI技術與產業的年度盛會!我們只講技術,拒絕空談!
這裡有10場技術專題論壇:計算機視覺、數據分析、機器學習、知識圖譜、智慧金融、智能駕駛、語音技術、智慧醫療、機器學習工具、自然語言處理。
還有15+矽谷實力講師團、80+AI領軍企業技術核心人物、100+技術&大眾實力媒體、1500+AI專業開發者
點擊下方「海報」,快速獲取大會更多信息,並獲得最低折扣票!
※放棄 802.11 命名方式,Wi-Fi 6 標準公布,速度快 37%
※優秀程序員寫代碼一定會用的 11 條經驗!
TAG:CSDN |