送書 | 用啥selenium!JS逆向不香嗎?
大家好!我是啃書君
正所謂條條道路通羅馬,上次我們使用了Selenium自動化工具來爬取網(wǎng)易云的音樂評論,Selenium自動化工具可以驅(qū)動瀏覽器執(zhí)行特定的動作,獲得瀏覽器當(dāng)前呈現(xiàn)的頁面的源代碼,做到可見即可爬,但需要等網(wǎng)頁完全加載完,也就是JavaScript完全渲染出來才可以獲取到當(dāng)前的網(wǎng)頁源代碼,這樣的爬取效率太低了、爬取速度太慢了。
追求完美、追求高效率的我們,怎么會容忍效率低下呢?所以我們今天利用Scrapy框架加js逆向來爬取網(wǎng)易云評論并做詞云圖,做效率最高的人?。?!
在爬取前,我們首先要了解一下什么是js逆向。
js逆向
首先Javascript簡稱js,js是一種腳本語言,是不需要進(jìn)行編譯的,也是瀏覽器中的一部分,經(jīng)常用在web客戶端腳本語言,主要是用來給html增加動態(tài)功能,也可以進(jìn)行數(shù)據(jù)加密。
加密在前端開發(fā)和爬蟲中是很常見的,當(dāng)我們掌握了加密算法且可以將加密的密文進(jìn)行解密破解時,就可以從編程小白搖身變?yōu)榫幊檀笊?,熟練掌握加密算法可以幫助我們實現(xiàn)高效的js逆向。由于加密算法的內(nèi)容有很多,今天我們主要是簡單了解一下加密算法有哪些,之前有寫過加密算法,感興趣可以看看往期文章?。。?/p>
常見的加密算法
js中常見的加密算法有以下幾種:
線性散列MD5算法:保證文件的正確性,防止一些人盜用程序,加些木馬或者篡改版權(quán),設(shè)計的一套驗證系統(tǒng),廣泛用于加密和解密技術(shù)上,如用戶的密碼; 對稱加密DES算法:是一種使用密鑰加密的算法,其加密運(yùn)算、解密運(yùn)算需要使用的是同樣的密鑰,加密后密文長度是8的整數(shù)倍; 對稱加密AES算法:是DES算法的加強(qiáng)版,采用分組密碼體制,加密后密文長度是16的整數(shù)倍,匯聚了強(qiáng)安全性、高性能、高效率、易用和靈活等優(yōu)點,比DES算法的加密強(qiáng)度更高,更安全; 非對稱加密算法RSA:在公開密鑰加密和電子商業(yè)中被廣泛使用,需要公開密鑰和私有密鑰,只有對應(yīng)的私有密鑰才能解密; base64偽加密:是一種用64個字符表示任意二進(jìn)制數(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ù)是一大串不知道表達(dá)什么意思的字符串,這時就需要采用js逆向來破解該參數(shù)。有人可能說,直接復(fù)制粘貼那參數(shù),也獲取到數(shù)據(jù)呀。可是這樣只能獲取到一小部分?jǐn)?shù)據(jù)或者一頁的數(shù)據(jù),不能獲取到多頁。
通過上面的例子,我們可以知道,js逆向可以幫助我們破解加密過的參數(shù)。
當(dāng)然除了幫我們破解加密過的參數(shù),還可以幫我們處理以下事情:
模擬登錄中密碼加密和其他請求參數(shù)加密處理; 動態(tài)加載且加密數(shù)據(jù)的捕獲和破解;
js逆向的實現(xiàn)
那么如何實現(xiàn)js逆向或者破解加密過的參數(shù)呢。
要破解加密過的參數(shù),大致可以分為四步:
尋找加密參數(shù)的方法位置找出來; 設(shè)置斷點找到未加密參數(shù)與方法; 把加密方法寫入js文件; 調(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ù)。我們把鼠標(biāo)放在window.asrsea中間,如下圖所示:

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

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

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

當(dāng)左邊出現(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ù),其他的不認(rèn)識?。?!
為了證實推測,我們換個歌單來測試獲取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(["流淚", "強(qiáng)"]), bqf4j(Sr6l.md), bqf4j(["愛心", "女孩", "驚恐", "大笑"]));
return bYm0x
}
將鼠標(biāo)放在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文件中。當(dāng)我們不知道要復(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安裝方式很簡單,進(jìn)入node.js官網(wǎng),如下圖所示:

大家選擇對應(yīng)的系統(tǒng)來下載安裝,由于安裝實在太簡單了,都是無腦下一步就可以了,當(dāng)然最好參照網(wǎng)上的教程來安裝,這里我們就不講解如何安裝node.js。
注意:一定要安裝node.js,否則會在調(diào)試js文件中報以下錯誤:
execjs._exceptions.ProgramError: TypeError: ‘JSON‘ 未定義
node.js插件
我們寫好js文件后,需要進(jìn)行調(diào)試,而在pycharm中調(diào)試js文件需要安裝node.js插件。
首先進(jìn)入pycharm中的setting配置,如下圖所示:

按照上圖中的步驟,即可安裝好插件。
好了,準(zhǔn)備工作已經(jīng)做好了,現(xiàn)在開始調(diào)試js文件,運(yùn)行剛才的js文件,會發(fā)現(xiàn)報了以下錯誤:
window.asrsea = d,
^
ReferenceError: window is not defined
該錯誤是說window沒定義,這時我們只需要在最前面添加以下代碼即可:
window={}
進(jìn)行運(yùn)行我們的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ù)制呢,當(dāng)我們不知道在哪里開始復(fù)制時,直接把所有的CrpytoJS都復(fù)制下來,請記住一個原則,寧愿復(fù)制多了也不復(fù)制少了,多了不會報錯,少了會報錯,而且還要找錯,重新復(fù)制。
好了,我們復(fù)制完后,繼續(xù)運(yùn)行js文件。
運(yùn)行結(jié)果如下:

好了,js文件已經(jīng)運(yùn)行準(zhǔn)確無誤了。接下來開始爬取數(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文件后,當(dāng)然要把它讀取出來,具體代碼如下所示:
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中,當(dāng)我們需要獲取另一首歌的評論信息的時候,只需要修改上面的url即可。注意:參數(shù)d中R_SO_4代表的單曲,當(dāng)我們要獲取其他的評論信息時,則需要更改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=相當(dāng)于我們普通爬蟲的data=callback=self.parse()表示將響應(yīng)返回給parse()方法。
最后通過parse()方法進(jìn)行數(shù)據(jù)的獲取并通過yield生成器返回給引擎。
pipelines.py文件
當(dāng)我們需要把數(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')
獲取多條評論
對了,如何獲取多條評論呢,通常情況下,我們需要進(jì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)在我們開始運(yùn)行爬蟲,執(zhí)行如下代碼:
scrapy crawl NC
運(yùn)行結(jié)果如下:

制作詞云
制作詞云我們需要jieba庫,wordcloud庫、imageio庫,其安裝方式如下:
pip install jieba
pip install wordcloud
pip install imageio
在前面的步驟中,我們已經(jīng)成功獲取到評論并把評論數(shù)據(jù)保存在txt文本中,接下來我們將開始制作詞云,具體代碼如下:
import jieba
import wordcloud
import imageio
img_read=imageio.imread('小熊.jpg')
file_open=open('評論.txt', 'r', encoding='utf-8')
txt=file_open.read()
Cloud=wordcloud.WordCloud(width=1000,height=1000,background_color='white',mask=img_read,scale=8,font_path='C:\Windows\Fonts\msyhbd.ttc',stopwords={'的','了','是'})
txtlist=jieba.lcut(txt)
string=' '.join(txtlist)
Cloud.generate(string)
Cloud.to_file('小熊.png')
首先我們導(dǎo)入jieba、wordcloud、imageio庫,再調(diào)用imageio.imread()方法來讀取詞云的背景圖,然后再調(diào)用wordcloud.WordCloud()方法,把詞云圖設(shè)置寬高為1000,背景色為白色,詞云圖背景為剛才讀取的圖片。
注意:當(dāng)我們做的詞云有中文時,我們要把系統(tǒng)文字路徑傳入到wordcloud.WordCloud()方法中,這里我們還把“的,了,是”在詞云中屏蔽掉。
然后我們調(diào)用jieba.lcut()方法把text.txt文本中的文字進(jìn)行切割,由于我們分割出來的文字是以列表的形式保存的,所以調(diào)用join()方法把列表轉(zhuǎn)換為字符
最后調(diào)用generate()方法生成詞云,調(diào)用to_file()方法保存詞云圖。
結(jié)果展示

好了,js逆向爬取網(wǎng)易云音樂評論并做詞云就講到這里了,感謝觀看!?。?/p>
啃書君說:
本文僅用于學(xué)術(shù)交流!文章的每一個字都是我用心寫出來的,如果你看到了這里,希望可以得到你的【點贊】與【在看】,讓我知道你就是那個陪我一起努力的人。
我是啃書君,一個專注于學(xué)習(xí)的人,更多精彩內(nèi)容,我們下期再見!
送書
又到了每周三的送書時刻,今天給大家?guī)淼氖恰?span style="outline: 0px;font-family: Arial, "microsoft yahei";font-size: 16px;font-weight: 700;letter-spacing: 0.544px;">反爬蟲AST原理與還原混淆實戰(zhàn)》,AST是目前爬蟲領(lǐng)域的熱點。本書從AST這一個知識點出發(fā),由淺入深,帶領(lǐng)讀者掌握反爬蟲AST的原理,并幫助讀者培養(yǎng)解決實際問題的能力。本書共11章,分為四部分。第一部分(第1~4章)介紹開發(fā)環(huán)境的搭建方法、Web調(diào)試的必備技巧以及爬蟲與反爬蟲的基本知識;第二部分(第5~6章)講解混淆JavaScript代碼的手工逆向方法與JavaScript代碼安全防護(hù)的原理;第三部分(第7~8章)講解AST的原理與API的使用方法;第四部分(第9~11章)以AST為基礎(chǔ),講解自動化的JavaScript代碼防護(hù)與還原方案,并帶領(lǐng)讀者進(jìn)行實戰(zhàn)訓(xùn)練。本書適合作為計算機(jī)培訓(xùn)的教材,也可供安全開發(fā)人員、爬蟲初學(xué)者以及想要在爬蟲領(lǐng)域進(jìn)階的人員學(xué)習(xí)。

點擊下方回復(fù):送書 即可!
(點擊查看本書內(nèi)容)
