JS 逆向的時候 Python 如何呼叫 JavaScript 程式碼?「建議收藏哦!」

2020-08-12 14:03:46

你好,我是悅創。關注公衆號:AI悅創,搶先閱讀優質文章。

公衆號原文:https://mp.weixin.qq.com/s/PYEiSMgP2LT0DmcHX08PCw

部落格原文:https://www.aiyc.top/798.html

本篇目標

  • 瞭解爲什麼我們需要直接呼叫 JavaScript
  • 瞭解常見的 Python 呼叫 JavaScript 的庫
  • 瞭解一種效能更高的操作方式
  • 知道什麼場景下應該使用什麼方式進行呼叫

通過本文的學習,在你寫爬蟲時,你應該會對呼叫 JavaScript 有一個更清晰的瞭解,並且你還要瞭解到一些你平時可能見不到的騷操作。

大家如果接觸過 JavaScript 逆向的話,應該都知道,通常來說碰到 JS 逆向網站時會有這兩種情況:

  • 簡單 JS 破解:通過 Python 程式碼輕鬆實現
  • 複雜的 JS 破解:程式碼不容易重寫,使用程式直接呼叫 JS 執行獲取結果。

對於簡單的 JS 來說,我們可以通過 Python 程式碼,直接重寫,輕輕鬆鬆的就能搞定。

而對於復的 JS 程式碼而言呢,由於程式碼過於複雜,重寫太費時費力,且碰到對方更新這就比較麻煩了。所以,我們一般直接使用程式去呼叫 JS,在 Python 層面就只是獲取一個執行結果,這樣做相比於重寫而言就方便多了。

那麼,接下來我帶大家看一下兩種比較簡單的 JS 程式碼重寫。

本文涉及的所有演示程式碼請到公衆號後臺回覆 回復:PJS 。來獲取即可!

1. Base64

首先,我們先來看一下 Base64 ,Base64 是我們再寫爬蟲過程中經常看到的一種編碼方式。這邊我們來寫兩個例子。

// 原字元
NightTeam
// 編碼之後的:
TmlnaHRUZWFt

第一個例子如上,是 NightTeam 經過編碼是如上面的結果(TmlnaHRUZWFt),如果我們只是通過這個結果來分析的話,它的特徵不是很明顯。如果是見的不多或者是新手小白的同學,並不會把它往 Base64 方向去想。

然後,我們來看一下第二個例子:

// 原字元
aiyuechuang
// 編碼之後的:
YWl5dWVjaHVhbmc=

// 原字元
Python3
// 編碼之後
UHl0aG9uMw==

第二個例子是 aiyuechuang 編碼之後的結果,它的末尾有一個等號,Python3 編碼之後末尾有兩個等號,這個特徵相對第一個就比較明顯了。一般我們看到尾號有兩個等號時應該大概可以猜到這個就是 Base64 了。

然後,直接解碼看一看,如果沒有什麼特別的話,就可以使用 Python 進行重寫了。

同學可以使用以下鏈接 Base64 編碼解碼的測試學習:http://tool.alixixi.com/base64/

不過 Base64 也會有一些騷操作,碰到那種情況的時候,我們如果用 Python 重寫可能有點麻煩。具體的內容我會在後面的的課程中,單獨的跟大家詳細的講解。

2. MD5

第二個的話就是 MD5 ,MD5 在 Javascript 中並沒有標準的庫,一般我們都是使用開源庫去操作。

注意:md5 的話是 雜湊 並不是加密。

下面 下麪我來看一個 js 實現 md5 的一個例子:

md5.js

上面的程式碼時被混淆過的,但是它的主要一個特徵還是比較明顯的,有一個入口函數:console.log(hex_md5("aiyuechuang")) 我們可以使用命令列執行一下結果,命令如下:

node md5.js

上面的程式碼自行復制儲存爲 md5.js 然後執行。

執行結果:

$ node md5.js
e55babec7f5d5cf7bac7872f0481bec1

我們數一下輸出的結果的話,會發現這正好 是 32位元,通常我們看到 32 位的一個英文數位混合的字串,應該馬上就能想到時 md5 了,這兩個操作的話,因爲在 Python 中都有對應的庫,分別是:Base64 和 hashlib ,大家應該都知道這個我就不多說了。

例程:Base64 和 hashlib

import base64  
str1 = b'aiyuechuang'
str2 = base64.b64encode(str1)
print(str2)
str3 = base64.b64decode('YWl5dWVjaHVhbmc=')
print(str3)

輸出

b'YWl5dWVjaHVhbmc='
b'aiyuechuang'
[Finished in 0.2s]
import hashlib

data = "aiyuechuang"
result = hashlib.md5(data.encode(encoding = "UTF-8")).hexdigest()
print(result)

輸出

e55babec7f5d5cf7bac7872f0481bec1
[Finished in 0.1s]

像我們前面看到的那些程式碼,都是比較簡單的,他們的演算法部分也沒有經過修改,所以我們可以使用其他語言和對應的庫進行重寫。

但是如果對方把演算法部分做了一些改變呢?

如果程式碼量比較大也被混淆到看不出特徵了,連操作後產生的字串都看不出,我們就無法直接使用一個現成的庫來複寫操作了。

而且這種情況下的程式碼量太大了,直接對着程式碼重寫成 Python 版本也不太現實,對方一更新你就得再重新看一遍,這樣顯然時非常麻煩的,也非常耗時。

那麼有沒有一種更高效的方法呢?

顯然是有的,接下來我們來講如何通過程式來直接呼叫 JavaScript 程式碼,也就是碰到複雜的 JS 時候的處理。

  1. 使用 Python 呼叫 JS
  2. 一種效能更高的呼叫方式
  3. 到底選擇哪種方案比較好

首先,我會分享一些使用 Python 呼叫 JavaScript 的方式,然後會介紹一種效能更高的呼叫。以及具體使用哪種呼叫方式以及怎麼選擇性的使用,最後我會總結一下這些方案存在的小問題。並且會告訴你如何踩坑。

3. 使用 Python 呼叫 JS

我們接下來首先講一下 Python 中呼叫 JavaScript。

  • PyV8
  • Js2Py
  • PyExecJS
  • PyminiRacer
  • Selenium
  • Pyppeteer

Python 呼叫 JS 庫的話,光是我瞭解的話,目前就有這麼一堆,接下來我們就來依次來介紹這些庫。

3.1 PyV8

  • V8 是谷歌開源的 JavaScript 引擎,被使用在了 Chrome 中
  • PyV8 是 V8 引擎的一個 Python 層的包裝,可以用來呼叫 V8 引擎執行 JS 程式碼
  • 網上有很多使用它來執行 JS 程式碼的文章
  • 年久失修,最新版本是 2010年的(https://pypi.org/project/PyV8/)
  • 存在記憶體漏失問題,所以不建議使用

首先來看一下什麼是 PyV8,V8 是谷歌開源的 JavaScript 引擎,被使用在了 Chrome 瀏覽器中,後來因爲有人想在 Python 上呼叫它(V8),於是就有了 PyV8。

那 PyV8 實際上是 V8 引擎的一個 Python 層的包裝,可以用來呼叫 V8 引擎執行 JS 程式碼,但是這個我不推薦使用它,那我既然不推薦大家使用,我爲什麼又要講它呢?

其實,是這樣的:

雖然目前網上有很多文章使用它執行 JS 程式碼,但是這個 PyV8 實際上已經年久失修了,而且它最新的一個正式版本還是 2010年的,可見是有多久遠了,鏈接在上方可以執行存取檢視。而且,如果你實際使用過的話,你應該會發現它存在一些記憶體漏失的問題。

所以,這邊我拿出來說一下,避免有人踩坑。接下來我們來說一下第二個 JS2Py。

3.2 Js2Py

  • Js2Py 是一個純 Python 實現的 JavaScript 直譯器和翻譯器
  • 雖然 2019年依然有更新,但那也是 6月份的事情了,而且它的 issues 裏面有很多的 bug 沒有修復(https://github.com/PiotrDabkowski/Js2Py/issues)。

Js2Py 是一個純 Python 實現的 JavaScript 直譯器和翻譯器,它和 PyV8 一樣,也是有挺多文章提到這個庫,然後來呼叫 JS 程式碼。

但是,Js2Py 雖然在2019年仍然更新,但那也是 6月份的事情了,而且它的 issues 裏面有很多的 bug 沒有修復(https://github.com/PiotrDabkowski/Js2Py/issues)。另外,Js2Py 本身也存在一些問題,就直譯器部分來說:

直譯器部分:

  • 效能不高
  • 存在一些 BUG

那不僅僅就直譯器部分,還有翻譯器部分:

  • 對於高度混淆的大型 JS 會轉換失敗
  • 而且轉換出來的程式碼可讀性差、效能不高

總之來講,它在各個方面來說都不太適合我們的工作場景,所以也是不建議大家使用的。

3.3 PyMinRacer

  • 同樣是 V8 引擎的包裝,和 PyV8 的效果一樣
  • 一個繼任 PyExecJS 和 PyramidV8 的庫
  • 一個比較新的庫

這個庫也是一個 PyV8 引擎包裝,它的效果和 PyV8 的效果一樣的。

而且作者號稱這是一個繼任 PyExecJS 和 PyramidV8 的庫,乍眼一看挺唬人的,不過由於它是一個比較新的庫,我這邊就沒有過多的嘗試了,也沒有再實際生產環境中使用過,所以不太清楚會有什麼坑,感興趣的朋友,大家可以自己去嘗試一下。

3.4 PyExecJS

  • 一個最開始誕生於 Ruby 中的庫,後來被移植到了 Python 上
  • 較新的文章一般都會說用它來執行 JS 程式碼
  • 有多個引擎可選,但一般我們會選擇使用 NodeJS 作爲引擎來執行程式碼

接下來我要說的是 PyExecJS ,這個庫一個最開始誕生於 Ruby 中的庫,後來人被移植到了 Python 上,目前看到一些比較新的文章都是用它來執行 JS 程式碼的,然後它是有多個引擎可以選擇的,我們一般選擇 NodeJS 作爲它的一個引擎執行程式碼,畢竟 NodeJS 的速度是比較快的而且設定起來比較簡單,那我帶大家來看一下 PyExecjs 的使用。

3.4.5 PyExecJS 的使用

  1. 安裝 JS 執行環境

    這裏推薦安裝 Node.js,安裝方便,執行效率也高。

首先我們就是要安裝引擎了,這個引擎指的就是 JS 的一個執行環境,這邊推薦使用 Node.js。

注意:雖然 Windows 上有個系統自帶的 JScript,可以用來作爲 PyExecjs 的引擎,但是這個 JScript 很容易與其他的引擎有一個不一樣的地方,容易踩到一些奇奇怪怪的坑。所以請大家務必要安裝一個其他的引擎。比如說我們這裏安裝 Node.js 。

那上面裝完 Nodejs 之後呢,我們就需要執行安裝 PyExecjs 了:

  1. 安裝 PyExecJS
pip install pyexecjs

這邊我們使用上面的 pip 就可以進行安裝了。

那麼我們現在環境就準備好了,可以開始執行了。

  1. 程式碼範例(檢測執行環境)

首先,我們開啓 IPython 終端,執行一下一下兩行程式碼,以下也給出了執行結果:

In [1]: import execjs

In [2]: execjs.get().name # 檢視呼叫環境
Out[2]: 'Node.js (V8)'

execjs.get() # 檢視呼叫的環境用此來看看我們的庫能不能檢測到 nodejs,如果不能的話那就需要手動設定一下,不過一般像我上面一樣正常輸出 node.js 就可以了。

如果,你檢測出來的引擎不是 node.js 的話,那你就需要手動設定一下了,這裏有兩種設定形式,我在下方給你寫出來了:

選擇不同引擎進行解析

# 長期使用
os.environ["EXECJS_RUNTIME"]="Node"
	
# 臨時使用
import execjs.runtime_names
node=execjs.get(execjs.runtime_names.Node)

由上邊可知,我們有兩種形式:一種是長期使用的,通過環境變數的形式,通過把環境變數改成大寫的 EXECJS_RUNTIME 然後將其值賦值爲 Node。

另一種的話,將它改成臨時使用的一種方式,這種是直接使用 get,這種做法的話,你在使用的時候就需要使用 node 變數了,不能直接匯入 PyExecjs 來直接開始使用,相對麻煩一些。

接下來,就讓我們正式使用 PyExecJS 這個包吧。

In [8]: import execjs
	
In [9]: e = execjs.eval('a = new Array(1, 2, 3)') # 可以直接執行 JS 程式碼
	
In [10]: print(e)
[1, 2, 3]

PyExecjs 最簡單的用法就是匯入包,然後通過 eval 這個方法並傳入簡單的 JS 程式碼來執行。但是我們正常情況下肯定不會這麼使用,因爲我們的 JS 程式碼是比較複雜的而且 JS 程式碼內容也是比較多的。

# -*- coding: utf-8 -*-
# @Author: clela
# @Date:   2020-03-24 13:54:27
# @Last Modified by:   aiyuechuang
# @Last Modified time: 2020-04-03 08:44:15
# @公衆號:AI悅創
	
In [12]: import execjs

In [13]: jstext = """
    ...: function hello(str){return str;}
    ...: """

In [14]: ctx = execjs.compile(jstext) # 編譯 JS 程式碼

In [15]: a = ctx.call("hello", "hello aiyc")

In [16]: print(a)
hello aiyc

這樣的話,我們一般通過使用第二種方式,第二種方式是通過使用 compile 對 JS 字串進行編譯,這個編譯操作其實就是把參數(jstext)裏面的那段 JS 程式碼給放到一個叫 Context 的上下文中,它並不是我們平時編譯程式所說的編譯。然後我們 呼叫 call 方法進行執行。

第一個參數是我們呼叫 JS 中的的函數名,也就是 hello。然後後面跟着的 hello aiyc 就是參數,也就是我們 JS 中需要傳入到 str 的參數。如果 JS 中存在多個參數,我們就直接在後面打個逗號,然後接着寫下一個參數就好了。

接下來我們來看一個具體的程式碼:

aes_demo.js

這邊我準備了一個 CryptoJS 的一個 JS 檔案,CryptoJS 它是一個包含各種加密雜湊編碼演算法的一個開源庫,很多網站都會用它提供的函數來生成參數,那麼這邊我是寫了如上面這樣的程式碼,用來呼叫它裏面的 AES 加密參數,來加密一下我提供的字串。

注意:JS 程式碼不要放在和 Python 程式碼同一個檔案中,儘量放在單獨的 js 檔案中,因爲我們的 JS 檔案內容比較多。然後通過讀取檔案的方式,

run_aes.py

# Python 檔案:run_aes.py
# -*- coding: utf-8 -*-
# @時間 : 2020-04-06 00:00
# @作者 : AI悅創
# @檔名 : run_aes.py
# @公衆號: AI悅創
from pprint import pprint

import execjs
import pathlib
import os

js_path = pathlib.Path(os.path.abspath(os.path.dirname(__file__)))
js_path = js_path / "crypto.js"
with js_path.open('r', encoding="utf-8") as f:
	script = f.read()

c = "1234"

# 傳入python中的變數
add = ('''
aesEncrypt = function() {
	result={}
	var t = CryptoJS.MD5("login.xxx.com"),
		i = CryptoJS.enc.Utf8.parse(t),
		r = CryptoJS.enc.Utf8.parse("1234567812345678"),
		u = CryptoJS.AES.encrypt(''' + "'{}'".format(c) + ''',i, {
		iv: r
	});
		result.t=t.toString()
		result.i =i.toString()
		result.r =r.toString()
		result.u =u.toString()
		return result
	};
	''')
script = script + add
print("script",script)

x = execjs.compile(script)
result = x.call("aesEncrypt")
print(result)

這裏我通過讀取檔案的方式,將 js 檔案讀取進來,把程式碼讀取到我們的字串裏面,這樣一方面方便我們管理,另一方面也可以直接通過程式碼檢測自動補全功能,使用起來會比較方便。

然後,這裏我們有一個小技巧,我們可以通過 format 字串拼接的形式,將 Python 中的變數,也就是上面的變數 c 然後將這個變數寫入到 Js 程式碼中,從而變相的實現了通過呼叫 JS 函數,在沒有參數的情況下修改 JS 程式碼中的特定變數的值。最後我們拼接好了我我們的 JS 程式碼(add 和 script)。

拼完 JS 程式碼之後,我們這邊再常規的進行一個操作,呼叫 Call 方法執行 aesEncrypt 這樣一個函數,需要注意的是,這個程式碼裏面 return 出來的 JS,它是一個 object,JS 中的 object 也就是 Python 中的字典

我們實際使用時,如果需要在 Python 中拿到 object 的話,建議把它轉換成一個 json 字串,而不是直接的把結果 return 出來。

因爲,有些時候 PyExecjs 對 object 的轉換會出現問題,所以我們可能會拿到一些類似於將字典直接用 str 函數包裹後轉爲字串的一個東西,這樣的話它是無法通過正常的方式去解析的。

或者說你也可能會遇到其情況的報錯,總之大家最好先轉一下 json 字串,然後再 return 避免踩坑。這是我們的一個程式碼。

接下來我們來說一下,PyExecJS 存在的一些問題主要有以下兩點:

  • 執行大型 JS 時會有點慢(這個是因爲,每次執行 JS 程式碼的時候,都是從命令列去呼叫到的 JS,所以 JS 程式碼越複雜的話,nodejs 的初始化時間就越長,這個基本上是無解的)
  • 特殊編碼的輸入或輸出參數會出現報錯的情況(因爲,是從命令列呼叫的,所以在碰到一些特殊字元輸入或輸出參數或者 JS 程式碼本身就有一些特殊字元的情況下,就會直接執行不了,給你拋出一個異常。不過這個跟系統的命令列預設編碼有一定關係,具體的話這裏就不深究了,直接就說解決方案吧。)
  • 可以把輸入或輸出的參數使用 Base64 編碼一下(如果看報錯是 JS 程式碼部分導致的,那就去看看能不能刪除程式碼中的那部分字元或者你自己 new 一個上下文物件,將那個名叫 tempfile 的參數開啓,這樣在呼叫的時候,它就直接去執行那個檔案了,不過大量呼叫的情況下,可能會對磁碟造成一定壓力。

而如果參數不充分導致的話,有個很簡單的方法:就是把參數使用 Base64 編碼一下,因爲編碼之後出來的字串,我們知道 Base64 編碼之後是生成英文和數位組成的。這樣就沒有特殊符號了。所以就不會出現問題了。)

關於 PyExecejs 的相關東西就介紹到這裏了,我們來看一些其他的內容。

3.5 其他使用 Python 呼叫 JS 的騷操作

前面說的都是非瀏覽器環境下直接呼叫 JS 的操作,但是還有一些市面上根本沒人提到的騷操作,其實也挺好用的,接下來我給大家介紹一下:

  1. Selenium
  • 一個 web 自動化測試框架,可以驅動各種瀏覽器進行模擬人工操作
  • 用於渲染頁面以方便提取數據或過驗證碼
  • 也可以直接驅動瀏覽器執行 JS

這個大家是比較熟悉的,它是一個外部自動化的測試框架,可以驅動各種瀏覽器進行模擬人工操作,很多文章或者培訓班的課程,都會提到它在爬蟲方面的一個使用,比如用它採集一些動態頁面,或者用來過一些滑動驗證碼之類的。

不過我們這裏不用它來做這些事,我們要做的是用它來執行 JS 程式碼,因爲這樣的話是直接在瀏覽器環境下執行的 ,所以的話它是省了很多事,那麼 Selenium 執行 JS 的核心程式碼,實際上就下面 下麪一行:

js = "一大段 JS"
result = browser.execute_script(js)

我們來看一下實際的例子:

SeleniumDemo

進入專案根目錄,輸入:python server.py

$ python server.py
 * Serving Flask app "server" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 262-966-819
 * Running on http://0.0.0.0:5002/ (Press CTRL+C to quit)

存取 localhost:5002 我們進入網頁之後,有這樣的一句話:


每次重新整理都會顯示不同的內容,檢視原始碼的話,會發現這個頁面中的原始碼裏面沒有對應頁面顯示的那句話,而是隻有一個 input 標籤。

我還能觀察到,input 標籤裏面有兩個屬性,一個是 id、一個是 data,這兩個是比較關鍵的屬性,然後我們還發現這裏面參照了一個 js 檔案,所以這個網頁最終結果實際上是通過 JS 檔案,然後一系列的操作生成的,那接下來我就來看看 JS 檔案,做了什麼工作。

js

我們可以看見,這個 JS 檔案最後一句,有一個 window.onload =doit 的這樣一程式碼,這個我們知道,當頁面載入完成之後,立即執行這個 JS 方法。

function doit() {
    let browser_type=BrowserType();
    console.log(browser_type)
    let supporter =browser_type.supporter
    if(supporter==="chrome"){
        Base64.run('base64', 'data',supporter)
    }

}

然後這個方法裏面做了一個這樣一個操作:let browser_type=BrowserType(); 首先去判斷 supporter 是否等於 Chrome 這個 supporter 實際上有一個 browser_type 這個 browser_type 實際上就是檢測瀏覽器等一系列參數,然後我們獲取它裏面的 supporter 屬性,當 supportersupporter =browser_type.supporter )等於 Chrome 的時候,我們再去執行這個 run 函數。

run: function (id, attr,supporter) {
        let all_str = $(id).getAttribute(attr)
        let end_index=supporter.length+58
        Base64._keyStr = all_str.substring(0, end_index)
        let charset = all_str.substring(64, all_str.length)
        let encoded = Base64.decode(charset,supporter);
        $(id).value = encoded;
    }

也就是 run 函數裏面做了一系列操作,然後我傳入的 id 可以通過看一下上面的函數 doit 可知傳入的是 Base64

也就是說,實際上對 input 這個標籤做了一個取值的操作,然後到這邊我們就這整體一個過程將會用 JS 去模擬,所以這邊我就不細說了。

最終會把這樣的一個結果去通過 input.value 屬性把值複製到 input 中,也就是我們最終看到的那樣一個結果,到目前我就把這個 js 大概做了一件什麼樣的事情就已經講的差不多了。接下來我們去看一下 Selenium 這邊。

程式碼如下:

# -*- coding: utf-8 -*-
# @Time : 2020-04-01 20:56
# @Author : aiyuehcuang
# @File : demo.py
# @Software: PyCharm
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
import time


def get_text(id,attr):
    ###  拼接字串注意{}要寫出{{}}
    script=("""
       let bt=BrowserType();
       let id='{id}';
       let attr='{attr}';
       let supporter =bt.supporter;
    const run=function(){{
    let all_str = $(id).getAttribute(attr)
    let end_index=supporter.length+58
    Base64._keyStr = all_str.substring(0, end_index)
    let charset = all_str.substring(64, all_str.length)
    let encoded = Base64.decode(charset,supporter);
    return encoded
}}
    return run()
    """).format(id=id,attr=attr)
    return script



chrome_option = Options()
chrome_option.add_argument("--headless")
chrome_option.add_argument("--disable-gpu")
chrome_option.add_argument('--ignore-certificate-errors')  # SSL儲存
browser = webdriver.Chrome(options=chrome_option)
wait = WebDriverWait(browser, 10)
# 啓動瀏覽器,獲取網頁原始碼
mainUrl = "http://127.0.0.1:5002/"
browser.get(mainUrl)
result=browser.execute_script(get_text("base64","data"))
print(result)
time.sleep(10)
browser.quit()

這邊關鍵的一行程式碼是:通過 execute_script(get_text("base64","data")) 這樣的一句話去執行這個函數,這個函數實際上就是返回一段 JS 程式碼,這邊實際上就是去模擬構造 run 所需要的一些參數,然後把最終的結果返回回去。

這裏有兩點需要注意:

  1. 如果裏面存在拼接字串的時候,注意花括號實際上要寫兩個
  2. 如果需要在後面需要獲取 JS 返回的值,所以我們上面的程式碼需要加上 return 來返回 run 函數的結果

我們可以執行一下程式碼,輸出結果如下:

$ python demo.py

DevTools listening on ws://127.0.0.1:59507/devtools/browser/edbe51d8-744d-447d-9304-e9551a2a6421
[0407/184920.601:INFO:CONSOLE(286)] "[object Object]", source: http://127.0.0.1:5002/static/js/base64.js (286)
生活不是等待暴風雨過去,而是要學會在雨中跳舞。

我們可以看到,我們程夠獲取到了結果。

這個例子因爲它用到了檢測瀏覽器的屬性,而且它檢測完屬性之後會把屬性值一直往下傳,我們可以從上面的程式碼中看到它有很多地方使用。

所以,如果我們用 PyExecjs 來寫的話,就需要修改很多參數,這樣就很不方便了。因爲我們需要去模擬這些瀏覽器參數,我這邊寫的例子比較簡單,像那種更加複雜的。像獲取更多的瀏覽器的一個屬性的話,用 PyExecjs 再去寫的時候,可能沒有瀏覽器這樣的一個環境,所以 PyExecjs 沒有 Selenium 有優勢。

當然,除了 Selenium 以爲,還有一個叫做 Pyppeteer 的庫,也是比較常見。

爲了控制文章篇幅,咱們下次再續咯,記得關注公衆號:AI悅創!