Python爬蟲——教你js逆向爬取網(wǎng)易云評論

大家好!我是霖hero

正所謂條條道路通羅馬,上次我們使用了Selenium自動化工具來爬取網(wǎng)易云的音樂評論,Selenium自動化工具可以驅(qū)動瀏覽器執(zhí)行特定的動作,獲得瀏覽器當前呈現(xiàn)的頁面的源代碼,做到可見即可爬,但需要等網(wǎng)頁完全加載完,也就是JavaScript完全渲染出來才可以獲取到當前的網(wǎng)頁源代碼,這樣的爬取效率太低了、爬取速度太慢了。

追求完美、追求高效率的我們,怎么會容忍效率低下呢?所以我們今天利用Scrapy框架加js逆向來爬取網(wǎng)易云評論,做效率最高的人?。。?/p>

在爬取前,我們首先要了解一下什么是js逆向。

js逆向

首先Javascript簡稱js,js是一種腳本語言,是不需要進行編譯的,也是瀏覽器中的一部分,經(jīng)常用在web客戶端腳本語言,主要是用來給html增加動態(tài)功能,也可以進行數(shù)據(jù)加密。

加密在前端開發(fā)和爬蟲中是很常見的,當我們掌握了加密算法且可以將加密的密文進行解密破解時,就可以從編程小白搖身變?yōu)榫幊檀笊瘢炀氄莆占用芩惴梢詭椭覀儗崿F(xiàn)高效的js逆向。由于加密算法的內(nèi)容有很多,今天我們主要是簡單了解一下加密算法有哪些,以后會專門寫一遍關(guān)于加密算法的文章,敬請期待?。?!

常見的加密算法

js中常見的加密算法有以下幾種:

  • 線性散列MD5算法:保證文件的正確性,防止一些人盜用程序,加些木馬或者篡改版權(quán),設(shè)計的一套驗證系統(tǒng),廣泛用于加密和解密技術(shù)上,如用戶的密碼;
  • 對稱加密DES算法:是一種使用密鑰加密的算法,其加密運算、解密運算需要使用的是同樣的密鑰,加密后密文長度是8的整數(shù)倍;
  • 對稱加密AES算法:是DES算法的加強版,采用分組密碼體制,加密后密文長度是16的整數(shù)倍,匯聚了強安全性、高性能、高效率、易用和靈活等優(yōu)點,比DES算法的加密強度更高,更安全;
  • 非對稱加密算法RSA:在公開密鑰加密和電子商業(yè)中被廣泛使用,需要公開密鑰和私有密鑰,只有對應(yīng)的私有密鑰才能解密;
  • base64偽加密:是一種用64個字符表示任意二進制數(shù)據(jù)的方法,只是一種編碼方式而不是加密算法;
  • https證書秘鑰加密:基于http和SSL/TLS實現(xiàn)的一個協(xié)議,保證在網(wǎng)絡(luò)上傳輸?shù)臄?shù)據(jù)都是加密的,從而保證數(shù)據(jù)安全。

js逆向作用

我們發(fā)送網(wǎng)絡(luò)請求的時候,往往需要攜帶請求參數(shù),如下圖所示:



有爬蟲基礎(chǔ)的人都知道,上圖發(fā)送的是POST網(wǎng)絡(luò)請求,在發(fā)送請求時,我們還要攜帶一些參數(shù),例如上圖中的limit和current,其中l(wèi)imit是每次獲取的數(shù)據(jù)個數(shù),current是頁碼數(shù)。要想獲取上面的URL鏈接所呈現(xiàn)中的數(shù)據(jù)時,必須要在發(fā)送網(wǎng)絡(luò)請求時攜帶limit和current這兩個參數(shù)。

有時候我們需要攜帶的請求參數(shù)是加密過的參數(shù),如下圖所示:



同樣是發(fā)送POST網(wǎng)絡(luò)請求,很明顯這次的參數(shù)是已經(jīng)加密過的參數(shù),該參數(shù)是一大串不知道表達什么意思的字符串,這時就需要采用js逆向來破解該參數(shù)。有人可能說,直接復(fù)制粘貼那參數(shù),也獲取到數(shù)據(jù)呀。可是這樣只能獲取到一小部分數(shù)據(jù)或者一頁的數(shù)據(jù),不能獲取到多頁。

通過上面的例子,我們可以知道,js逆向可以幫助我們破解加密過的參數(shù)。

當然除了幫我們破解加密過的參數(shù),還可以幫我們處理以下事情:

  • 模擬登錄中密碼加密和其他請求參數(shù)加密處理;
  • 動態(tài)加載且加密數(shù)據(jù)的捕獲和破解等等。

js逆向的實現(xiàn)

那么如何實現(xiàn)js逆向或者破解加密過的參數(shù)呢。

要破解加密過的參數(shù),大致可以分為四步:

  1. 尋找加密參數(shù)的方法位置找出來;
  2. 設(shè)置斷點找到未加密參數(shù)與方法;
  3. 把加密方法寫入js文件;
  4. 調(diào)試js文件。

下面我們以待會要爬取的網(wǎng)易云音樂評論為例,所創(chuàng)建的js文件名為wangyi.js,來演示一下如何實現(xiàn)js逆向。

尋找加密函數(shù)位置

首先打開開發(fā)者模式,找到你要獲取的數(shù)據(jù)的URL請求條目,再把加密參數(shù)的變量復(fù)制下來,點擊右上角三個小點,選擇Search。



在通過Search搜索把加密參數(shù)函數(shù)的存放位置找出來,如下圖所示:



經(jīng)過選擇我們發(fā)現(xiàn)加密函數(shù)放在core_b15...中,點擊4126這一行就會打開core_b15...,我們再在core_b15...中搜索有沒有其他params,鍵盤同時按下Ctrl F,如下圖所示:

由上圖可知,core_b15...中有34個params,這34個params中都有可能是加密參數(shù),這里我們來告訴大家一個小技巧,一般情況下,加密參數(shù)都是以下形式輸出的,

參數(shù):
參數(shù) =

所以我們可以在搜索框中稍稍加點東西,例如把搜索框中的params改為params:,結(jié)果如下圖所示:



這樣params就被我們精確到只有兩個,接下來我們開始設(shè)置斷點。

設(shè)置斷點找到未加密參數(shù)與函數(shù)

在上一步中,我們把params的范圍縮短到只有兩處,如下圖所示:




第一種圖的params只是一個類似字典的變量,而第二張圖的params:bYm0x.encText,表示在bYm0x中選取encText的值賦給params,而在13367行代碼中,表示encSecKey為bYm0x中encSecKey的值,所以我們可以通過變量bYm0x來獲取,而在params:bYm0x.encText上兩行代碼中,bYm0x變量中window調(diào)用了asrsea()方法,13364行代碼是我們加密參數(shù)的函數(shù)。我們把鼠標放在window.asrsea中間,如下圖所示:



由上圖可知,window.asrsea通過function d函數(shù)中調(diào)用的,其傳入?yún)?shù)為d,e,f,g,點擊f d(d,e,f,g),如下圖所示:

當我們不知道從哪里設(shè)置斷點時,我們可以嘗試在它調(diào)用函數(shù)的一行設(shè)置斷點或者你認為哪行代碼可疑就在哪行代碼設(shè)置斷點,刷新頁面,如下圖所示:

點擊上圖的1,一步步放開斷點,注意觀察上圖中的2,3處的變化,如下圖如下圖所示:



當左邊出現(xiàn)了評論區(qū),但沒出現(xiàn)評論內(nèi)容時,這時右邊的方框剛好出現(xiàn)了d,e,f,g這三個數(shù)據(jù),而且d中的數(shù)字剛好是歌曲的id。我們這四個參數(shù)復(fù)制下來,并去除\,觀察一下:
d: "{"rid":"R_SO_4_1874158536","threadId":"R_SO_4_1874158536","pageNo":"1","pageSize":"20","cursor":"-1","offset":"0","orderType":"1","csrf_token":""}"
e: "010001"
f: "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g: "0CoJUm6Qyw8W8jud"

通過上面的代碼,我們推測rid和threadId是單曲id,pageNo是評論區(qū)的頁數(shù),pageSize是評論數(shù)據(jù)的行數(shù),其他的不認識?。?!

為了證實推測,我們換個歌單來測試獲取d,e,f,g這四個參數(shù):

d: "{"rid":"A_PL_0_6892176976","threadId":"A_PL_0_6892176976",\"pageNo":"1","pageSize":"20","cursor":"-1","offset":"0","orderType":"1","csrf_token":""}"
e: "010001"
f: "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7"
g: "0CoJUm6Qyw8W8jud"

通過觀察可以發(fā)現(xiàn),我們的推測是正確的,而且e,f,g是固定不變的,那么我們可以確定參數(shù)d中的參數(shù)就是未加密的參數(shù),既然找到了未加密的參數(shù),那么我們先把未加密的參數(shù)寫入js文件中。

注意:rid中的A_PL_0代表的是歌單,而R_SO_4代表的是單曲。

把加密參數(shù)的方法寫入js文件

未加密的參數(shù)我們在上一步已經(jīng)獲取到了,也就知道了加密參數(shù)的函數(shù)為接下來開始把加密參數(shù)的方法并寫入js文件中。

該加密參數(shù)方法如下圖所示:



加密參數(shù)方法為window.asrsea(),所以我們直接復(fù)制粘貼第13364行代碼作為我們的加密參數(shù)方法,并寫在入口函數(shù)中,并返回變量bYm0x,具體代碼如下所示:

function start(){
    var bYm0x = window.asrsea(JSON.stringify(i8a), bqf4j(["流淚", "強"]), bqf4j(Sr6l.md), bqf4j(["愛心", "女孩", "驚恐", "大笑"]));
    return bYm0x
}

將鼠標放在window.asrsea中間,如下圖所示:



在圖中我們可以知道window.asrsea()調(diào)用了function d函數(shù),而傳入的參數(shù)對應(yīng)著未加密的參數(shù)d、e、f、g,而d屬于字典,e、f、g屬于常量,所以我們可以把上面的代碼改寫為:

function start(){
    var bYm0x=window.asrsea(JSON.stringify(d),e,f,g);
    return bYm0x
}

寫了入口函數(shù)后,我們開始觀察function d函數(shù),如下圖所示:



通過function d()函數(shù),我們發(fā)現(xiàn)function d()函數(shù)調(diào)用了a()函數(shù)、b()函數(shù)、c()函數(shù),所以我們要把這些函數(shù)都復(fù)制在剛才的js文件中。當我們不知道要復(fù)制哪些代碼時,就直接復(fù)制function d函數(shù)的外面一層花括號的所有代碼,也就是第13217行代碼為復(fù)制的開始點,第13257行代碼為復(fù)制的結(jié)束點。

為了我們的js文件可以在控制臺看到調(diào)試的結(jié)果,我們需要添加以下代碼:

console.log(start())

調(diào)試js文件

好了,我們已經(jīng)把代碼復(fù)制在js文件中了,在調(diào)試js文件前,我們先安裝node.js和node.js插件。

node.js

node.js安裝方式很簡單,進入node.js官網(wǎng),如下圖所示:


大家選擇對應(yīng)的系統(tǒng)來下載安裝,由于安裝實在太簡單了,都是無腦下一步就可以了,當然最好參照網(wǎng)上的教程來安裝,這里我們就不講解如何安裝node.js。

注意:一定要安裝node.js,否則會在調(diào)試js文件中報以下錯誤:

execjs._exceptions.ProgramError: TypeError: ‘JSON‘ 未定義

node.js插件

我們寫好js文件后,需要進行調(diào)試,而在pycharm中調(diào)試js文件需要安裝node.js插件。

首先進入pycharm中的setting配置,如下圖所示:



按照上圖中的步驟,即可安裝好插件。

好了,準備工作已經(jīng)做好了,現(xiàn)在開始調(diào)試js文件,運行剛才的js文件,會發(fā)現(xiàn)報了以下錯誤:

window.asrsea = d,
^

ReferenceError: window is not defined

該錯誤是說window沒定義,這時我們只需要在最前面添加以下代碼即可:

window={}

進行運行我們的js文件,發(fā)現(xiàn)又報錯了,錯誤如下所示:

        var c = CryptoJS.enc.Utf8.parse(b)
                ^

ReferenceError: CryptoJS is not defined

錯誤提示又是參數(shù)沒定義,但CryptoJS就不能簡單的設(shè)置一個空字典,這需要我們繼續(xù)在剛才的core_b15...中尋找CryptoJS了,如下圖所示:



由圖中可知,CryptoJS一共要13處那么多,那么我們該從何開始復(fù)制呢,又從何處結(jié)束復(fù)制呢,當我們不知道在哪里開始復(fù)制時,直接把所有的CrpytoJS都復(fù)制下來,請記住一個原則,寧愿復(fù)制多了也不復(fù)制少了,多了不會報錯,少了會報錯,而且還要找錯,重新復(fù)制。

好了,我們復(fù)制完后,繼續(xù)運行js文件。

運行結(jié)果如下:



好了,js文件已經(jīng)運行準確無誤了。接下來開始爬取數(shù)據(jù)

數(shù)據(jù)爬取

我們是通過Scrapy框架來爬取數(shù)據(jù),所以我們首先來創(chuàng)建Scrapy項目和spider爬蟲。

創(chuàng)建Scrapy項目、Spider爬蟲

創(chuàng)建Scrapy項目和Spider爬蟲很簡單,依次執(zhí)行以下代碼即可:

scrapy startproject <Scrapy項目名>
cd <Scrapy項目名>
scrapy genspider <爬蟲名字> <允許爬取的域名>

其中,我們的Scrapy項目名為NeteaseCould,爬蟲名字為:NC,允許爬取的域名為:music.163.com。

好了創(chuàng)建Scrapy項目后,接下來我們創(chuàng)建一個名為JS的文件夾來存放剛才編寫的js文件,項目目錄如下所示:



這里我們還創(chuàng)建了一個名為Read_js.py文件,該文件用來讀取js文件。

讀取js文件——Read_js.py

我們編寫好js文件后,當然要把它讀取出來,具體代碼如下所示:

def get_js():
    path = dirname(realpath(__file__)) + '/js/' + 'wangyi' + '.js'
    with open(path,'r',encoding='utf-8')as f:
        r_js=f.read()
    c_js=execjs.compile(r_js)
    u_js=c_js.call('start')
    data={
        "params":u_js['encText'],
        "encSecKey":u_js['encSecKey']
    }
    return data

我們把讀取到的js文件內(nèi)容存放在r_js變量中,然后通過execjs.compile()方法獲取代碼編譯完成后的對象,再通過call()方法來調(diào)用js文件中的入口函數(shù),也就是start()函數(shù)。然后將獲取到的數(shù)據(jù)存放在字典data中,最后返回字典data。

對了,為了使我們的代碼更靈活,我們可以把參數(shù)d放在Read_js.py文件中,具體代碼如下所示:

    url = 'https://music.163.com/#/song?id=17177324'
    id = url.split('=')[-1]
    d = {
        "rid": f"R_SO_4_{id}",
        "threadId": f"R_SO_4_{id}",
        "pageNo": "1",
        "pageSize": "5",
        "cursor": "-1",
        "offset": "0",
        "orderType": "1",
        "csrf_token": ""
    }
    u_js=c_js.call('start',d)

首先利用split()方法把歌曲的id獲取下來,然后放在參數(shù)d中,當我們需要獲取另一首歌的評論信息的時候,只需要修改上面的url即可。注意:參數(shù)d中R_SO_4代表的單曲,當我們要獲取其他的評論信息時,則需要更改R_SO_4,例如獲取歌單的時候則需要更改為A_PL_0。

items.py文件

在獲取數(shù)據(jù)前,我們先在items.py文件中,定義爬取數(shù)據(jù)的字典,具體代碼如下所示:

import scrapy

class NeteasecouldItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    content = scrapy.Field()

NC.py文件

在定義字段后,先看看評論數(shù)據(jù)的位置,如下圖所示:



現(xiàn)在我們開始獲取網(wǎng)易云音樂評論的數(shù)據(jù),具體代碼如下所示:

import scrapy
from NeteaseCould.Read_js import get_js
from NeteaseCould.items import NeteasecouldItem
class NcSpider(scrapy.Spider):
    name = 'NC'
    allowed_domains = ['music.163.com']
    start_urls = ['https://music.163.com/weapi/comment/resource/comments/get?csrf_token=']

    def start_requests(self):
        js=get_js()
        yield scrapy.FormRequest('https://music.163.com/weapi/comment/resource/comments/get?csrf_token=',formdata=js,callback=self.parse)

    def parse(self, response):
        json=response.json()
        p=json.get('data').get('comments')
        for i in p:
            item = NeteasecouldItem()
            item['content']=i.get('content')
            yield item

首先我們導(dǎo)入get_js和NeteasecouldItem,再將start_urls中的鏈接修改為https://music.163.com/weapi/comment/resource/comments/get?csrf_token=

由于我們發(fā)送的是POST請求,所以我們需要重寫start_requests()方法,在start_requests()方法中,我們先調(diào)用了get_js()方法,然后在通過ForMReuqest()方法發(fā)送網(wǎng)絡(luò)請求。

其中,formdata=相當于我們普通爬蟲的data=,callback=self.parse()表示將響應(yīng)返回給parse()方法。

最后通過parse()方法進行數(shù)據(jù)的獲取并通過yield生成器返回給引擎。

pipelines.py文件

當我們需要把數(shù)據(jù)放在數(shù)據(jù)庫或者存放在.txt文件中數(shù),則需要在pipelines.py文件編寫代碼,這里我們把數(shù)據(jù)存放在txt文件中,具體代碼如下所示:

from itemadapter import ItemAdapter

class NeteasecouldPipeline:
    def process_item(self, item, spider):
        with open('評論.txt','a',encoding='utf-8')as f:
            f.write(item['content'])
            f.write('\n')

獲取多條評論

對了,如何獲取多條評論呢,通常情況下,我們需要進行翻頁來獲取多條評論,但是這次不同,我們可以修改參數(shù)d中的數(shù)據(jù)就可以獲取多條評論,參數(shù)d如下所示:

d = {
    "rid": f"R_SO_4_{id}",
    "threadId": f"R_SO_4_{id}",
    "pageNo": "1",
    "pageSize": "5",
    "cursor": "-1",
    "offset": "0",
    "orderType": "1",
    "csrf_token": ""
}

我們可以修改pageSize的數(shù)據(jù),例如我現(xiàn)在的pageSize對應(yīng)的是5,所以只獲取五條評論。

settings.py文件

最后,我們需要在settings.py文件中做一些配置,具體代碼如下:

#屏蔽日志的輸出
LOG_LEVEL="WARNING"

#開啟引擎
ITEM_PIPELINES = {
   'NeteaseCould.pipelines.NeteasecouldPipeline': 300,
}

結(jié)果展示

所有的代碼已經(jīng)編寫完畢了,現(xiàn)在我們開始運行爬蟲,執(zhí)行如下代碼:

scrapy crawl NC

運行結(jié)果如下:


好了,js逆向爬取網(wǎng)易云評論就講到這里了,感謝觀看?。?!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容