python3爬蟲-下載網易雲音樂,評論

# -*- coding: utf-8 -*-
'''
16位隨機字符的字符串

參數一
獲取歌曲下載地址      "{"ids":"[1361348080]","level":"standard","encodeType":"aac","csrf_token":""}"
獲取歌曲評論信息      "{"rid":"R_SO_4_1361348080","offset":"0","total":"true","limit":"20","csrf_token":""}"

第二三四爲參數是固定的
"010001"
"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
"0CoJUm6Qyw8W8jud"

encSecKey 經過方法c,傳入參數  參數一爲16爲隨機字符串,參數2、三爲上面的參數二三
encText 進行了兩次加密  經過方法b    第一次是經過上面從參數一,參數四拿到返回值   第二次是返回值和16隨機字符串加密



# 關於我遇到的問題
一、首先就是參數一的獲取,
參數一很容易看得出來就是json格式的字典,經過json.dumps(dict)就能獲得,對,當時我也是這樣作的,由於分析網易雲進行js加密的代碼,
它也是將對象(也就是python的字典,相似)進行Json.stringify(obj)。因此我就跟着轉爲json格式的數據。
代碼書寫完畢以後,就進行測試階段了,啓動腳本,他給我返回的是json格式的字符串,400的錯誤,提示信息爲參數錯誤,最終我排除了請求頭是否
不合法等緣由,那就是我進行加密的時候,得出的加密結果不對。
爲了檢測是否我加密錯誤,我繼續在谷歌瀏覽器的開發者工具調試js代碼,由於進行加密的時候有一串16位的隨機字符串,我先在瀏覽器中獲取到這個隨機字符串
而後在我寫的python代碼中,將那個隨機的字符串固定爲瀏覽器獲取到的16位隨機字符串,而且執行,比較兩者參數哪裏對不上,encSecKey的值是能對上的,
encText(也就是formdata中的params)是對不上的,因此我獲取encText的加密內容出錯了。當時我徹底沒有考慮到是參數一的錯誤,由於我認爲參數一是對的,就是
json格式的數據呀,我認爲我加密的邏輯寫錯了。我把網易雲加密的那段js代碼,copy一份到本地的html文件中執行,參數也是一致,獲得的結果也是和瀏覽器加密後
的數據是一致的,我把加密後的數據直接用在python代碼中,執行,數據成功返回了,這是我更加確定是我加密代碼寫錯了,
通過一段的測試,我在本地的html文件中,把參數一的值寫成一個很簡單的字符串 "aaaa",我也把python代碼中的參數一也改成同樣。分別執行,臥槽,加密後參數
徹底同樣,找出了緣由,居然是我認爲不會出錯的參數一的緣由。找到緣由了,我就去看看參數一print出來究竟是啥,js中參數一console.log幾回,它的值是不變的
我再看python中,測試幾回,終於知道了緣由,我居然忽略了字典是無序了,
dic = {"name":"zhuyu","age":22}
print(json.dumps(dic))

# 多執行幾回,下面是輸出結果,你會發現:
{"name": "zhuyu", "age": 22}
{"age": 22, "name": "zhuyu"}

解決問題一:字典是無序的,執行json.dumps獲得的數據不是固定的,因此必需要弄一個固定的json格式數據,json數據它也是一個字符串,我本身弄一個和js中的
json數據同樣的字符串就行了
也就是這個>>>:  self.arg1 = '{"ids":"[%s]","level":"standard","encodeType":"aac","csrf_token":""}' % songId

二、對加密方法不清楚
對這一塊確實不擅長,你看到js中的加密代碼,殊不知道它傳遞的參數,到底有啥用,是用來作什麼的,你先要懂js代碼加密的邏輯,你猜能寫python代碼來實現同樣
的加密邏輯。

解決問題二:這個只能多百度,Google了,瞭解到加密方法,傳遞的參數是什麼形式,參數做用是啥,返回值又是什麼

三、瞭解網易雲js加密的流程
只有知道流程了,只能寫python加密的流程,這個須要你會chrome的開發者工具的使用,對js進行調試,知道重點的變量表明什麼

解決問題三:剛開始確實不會,須要查看一些博客,知道每一個按鈕對應的功能是什麼。還有就是要多看,多看,多看,指的是調試js代碼

四、知道js加密代碼的位置
個人方法:先看請求的所攜帶的參數,還有就是url,請求方法等等。。。而後就是ctrl+shift+f搜索你認爲的關鍵字,而後就是慢慢找吧
若是請求那裏找不到,也搜索不到加密的js位置,試試搜索 encrypt,還不行只能百度,Google了,看看別人的
'''

from Crypto.Cipher import AES
import base64
import random
import codecs
import requests
from fake_useragent import UserAgent
import time
from multiprocessing import Process


class DownLoadWYY:
    ua = UserAgent()

    def __init__(self):
        self.arg2 = "010001"
        self.arg3 = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
        self.arg4 = "0CoJUm6Qyw8W8jud"
        self.session = requests.Session()
        self.session.headers = {
            "Referer": "https://music.163.com/",
            "User-Agent": self.ua.random
        }
        self.__get_random_str()
        # self.__init_session()

    def __init_session(self):
        '''拿到後面須要的cookies'''
        resopnse = self.session.get("https://music.163.com/#/discover/playlist", headers=self.session.headers)
        print(resopnse.headers)

    def __AES_encrypt(self, text, key):
        '''
        獲取到加密後的數據
        :param text: 首先CBC加密方法,text必須位16位數據
        :param key: 加密的key
        :return: 加密後的字符串
        '''
        iv = "0102030405060708"
        pad = 16 - len(text) % 16
        if isinstance(text, str):
            text = text + pad * chr(pad)
        else:
            text = text.deocde("utf-8") + pad * chr(pad)
        aes = AES.new(key=bytes(key, encoding="utf-8"), mode=2, iv=bytes(iv, encoding="utf-8"))
        res = aes.encrypt(bytes(text, encoding="utf-8"))
        res = base64.b64encode(res).decode("utf-8")
        return res

    def __get_encText(self):

        encText = self.__AES_encrypt(self.arg1, self.arg4)
        encText = self.__AES_encrypt(encText, self.random_16_str)
        return encText

    def __get_encSecKey(self):
        '''經過查看js代碼,獲取encSecKey'''
        text = self.random_16_str[::-1]
        rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(self.arg2, 16) % int(self.arg3, 16)
        return format(rs, 'x').zfill(256)

    def __get_random_str(self):
        '''這是16位的隨機字符串'''
        str_set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        random_str = ""
        for i in range(16):
            index = random.randint(0, len(str_set) - 1)
            random_str += str_set[index]
        self.random_16_str = random_str

    def __getFormData(self):
        '''獲取到提交的數據'''
        data = {"params": self.__get_encText(), "encSecKey": self.__get_encSecKey()}
        return data

    def downloadSong(self, songId):
        '''獲取到歌曲的下載的地址就行了。若是想要下載能夠單獨再寫一個方法去下載音樂'''
        print("開始爬取歌曲mp3地址....")
        self.arg1 = '{"ids":"[%s]","level":"standard","encodeType":"aac","csrf_token":""}' % songId
        api = "https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token="
        headers = self.session.headers.copy()
        formdata = self.__getFormData()
        response = self.session.post(url=api, data=formdata, headers=headers)
        print("歌曲的下載地址爲>>:", response.json()["data"][0]["url"])

    def song_comment(self, songId):
        '''獲取到歌曲評論信息,我只是將結果print出來,若是保存的話,能夠單獨寫一個保存的方法'''
        print("開始爬取歌曲評論信息....")
        # self.arg1的格式爲:"{"rid":"R_SO_4_歌曲id","offset":"0","total":"true","limit":"20","csrf_token":""}"
        # 第一頁爲0,第二頁爲20,第三頁爲40  第四頁爲60  第五頁爲80
        offset = 0
        n = 1
        api = "https://music.163.com/weapi/v1/resource/comments/R_SO_4_{}?csrf_token=".format(songId)
        headers = self.session.headers.copy()
        while True:
            self.arg1 = '{"rid":"R_SO_4_%s","offset":"%s","total":"true","limit":"20","csrf_token":""}' % (
                songId, offset)
            formdata = self.__getFormData()
            response = self.session.post(url=api, headers=headers, data=formdata)
            # print("*"*100)
            # print("第{}頁評論".format(n))
            comment_list = response.json().get("comments")
            for dic in comment_list:
                try:
                    print("用戶: {}".format(dic["user"]["nickname"]))
                    print("評論內容: {} ".format(dic.get("content")))
                    print()
                except UnicodeEncodeError:
                    pass
            offset += 20
            n += 1
            time.sleep(1)


if __name__ == '__main__':
    song = DownLoadWYY()
    comment = DownLoadWYY()
    p1 = Process(target=song.downloadSong, args=(1361348080,))
    p2 = Process(target=comment.song_comment, args=(1361348080,))

    p1.start()
    p2.start()

    p1.join()
    p2.join()

    print("爬取完畢...")
相關文章
相關標籤/搜索