1. 程式人生 > >【Python3爬蟲】網易雲音樂爬蟲

【Python3爬蟲】網易雲音樂爬蟲

此次的目標是爬取網易雲音樂上指定歌曲所有評論並生成詞雲

具體步驟:

一:實現JS加密

 找到這個ajax介面沒什麼難度,問題在於傳遞的資料,是通過js加密得到的,因此需要檢視js程式碼。

通過斷掉除錯可以找到資料是由core_8556f33641851a422ec534e33e6fa5a4.js?8556f33641851a422ec534e33e6fa5a4.js裡的window.asrsea函式加密的。

通過進一步的查詢,可以找到下面這個函式:

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實現上面四個函式。第一個生成隨機字串的函式沒難度,實現的程式碼如下:

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,然後應該就可以成功匯入了。

由於AES加密的明文長度必須是16的倍數,因此我們需要對明文進行必要的填充,以滿足它的長度是16的倍數,AES加密的模式是AES.MODE_CBC,初始化向量iv='0102030405060708′。

實現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字元填充。

實現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")