在一台機器上,Node 裡的 cheerio 解析並擷取一份 10 MB 的合成 HTML 文件中的標題與 href,耗時 2,927.89 ms。selectolax 透過 CPython 搭配 C 語言後端解析器執行,同樣的欄位擷取只花了 158 ms。排序後的標題文字與 href 雜湊結果一致。這是一個跨執行環境的端到端堆疊比較,不是單純在比解析器演算法誰對誰錯。
在 10 KB 頁面上,差距只有 2 倍,根本不會有人注意到。真正的問題是:你的頁面落在這條曲線的哪一段。
cheerio 是什麼
cheerio 是 Node 生態中使用 jQuery 語法風格的 HTML 解析器,之所以成為預設解法並非偶然:30,449 個 GitHub stars、採用 MIT 授權,而且我執行測試的前一天,該倉庫還有新的 push。測試版本:1.2.0。
官方說明可參考:Cheerio 官方介紹。
import * as cheerio from "cheerio";
const $ = cheerio.load(html);
const titles = $("h3.title").map((_, e) => $(e).text()).get();
const hrefs = $("a").map((_, e) => $(e).attr("href")).get();
如果你寫過 jQuery,上手這套 API 幾乎沒有學習門檻。也正因為這份熟悉感,它才會在自己的生態中勝出。
不過它底下不是單一解析器,而是一整層堆疊:htmlparser2 和 parse5 負責解析,domhandler 與 domutils 負責 DOM 樹,cheerio-select 負責選擇器,外加 undici、encoding-sniffer 等等——總共有 11 個直接依賴,解析後會展開成 22 個頂層套件,磁碟占用 9.0 MiB。這些套件提供了解析與編碼能力,同時也增加了依賴體積。本次檢視有測試錯誤輸入的輸出,但沒有測編碼正確性,也沒有把兩個解析後端分開評估。
這項測量是怎麼做的,為什麼可信
這份研究基礎原本就有一套解析器基準測試:從 1 KB 到 10 MB 的五種頁面大小、50 次迭代、三輪獨立執行,而且——最重要的一點——有一個一致性閘門,會把擷取內容做雜湊比對:排序後的標題與排序後的 href,拿去跟參考解析器對照。若某個解析器悄悄少做事,就不可能拿到漂亮的速度數字。
把 cheerio 加進來之前,得先確認兩件事。
參考結果是否仍然落在原本的位置? selectolax 在同一台機器、同一組 fixture 上重新跑了一次。它的內容雜湊在 5/5 的大小上都重現,p50 也落在已發布數值的 0.989× 到 1.079× 之間。換句話說,這就是產生原始表格的那台機器。
cheerio 是否產生相同的評分欄位? 它的內容雜湊——在 Node 中以相同規則計算,也就是對排序後的標題文字與排序後的 href 做 SHA-256——在 5/5 的大小上都與參考結果一致。這只能證明這些排序後欄位在這些 fixture 上一致,並不代表 DOM 形狀、文件順序、屬性、文字正規化,或錯誤修復行為也完全相同。
只有在這之後,這些時間數字才有意義。
| 頁面大小 | selectolax | lxml | PyQuery | cheerio(Node) | cheerio 相對 selectolax |
|---|---|---|---|---|---|
| 1 KB | 0.0286 ms | 0.0508 | 0.0456 | 0.1147 ms | 4.0× |
| 10 KB | 0.1725 ms | 0.1802 | 0.1728 | 0.3490 ms | 2.0× |
| 100 KB | 1.4855 ms | 1.4145 | 1.4093 | 3.8399 ms | 2.6× |
| 1 MB | 14.97 ms | 15.03 | 14.96 | 59.37 ms | 4.0× |
| 10 MB | 158.10 ms | 165.25 | 162.86 | 2,927.89 ms | 18.5× |
p50 毫秒,三輪執行的中位數。parser-bench.json。三個 Python 解析器是在同一個 process 中執行;cheerio 則是在 Node 22 中執行,這不只是函式庫差異,也是執行環境邊界的差異——如下所述。
如何誠實地解讀這張表

1 KB 這一列幾乎都是雜訊。 在三個 Python 解析器之間,這個大小的落差高達 77.6%,而且單次結果彼此重疊得很誇張——selectolax 三次執行分別落在 0.0267 到 0.0404 ms。以 28 微秒這種量級來看,計時解析度與排程延遲早就蓋過差異了。我不會拿 1 KB 去排名任何工具,cheerio 也一樣。
表格中段沒什麼特別。 在 10 KB 到 1 MB 的頁面之間,差距大約就是 2 倍到 4 倍。若爬蟲一次要跑幾百頁,那每頁從 15 毫秒變成 45 毫秒,實務上你根本感受不到。
10 MB 這一列不是雜訊。 cheerio 三次結果分別是 2,839、2,928、2,954 ms——數值很集中,而且明顯與其他列分開。10 MB 的端到端結果,和較小頁面大小的趨勢明顯不同。光靠五個大小點,還不足以推導漸近複雜度,也無法指出究竟是哪一層造成跳升:執行環境、解析器、選擇器、配置,還是垃圾回收。
它落在 BeautifulSoup 的區間裡。 先前的基準測試在同一個 10 MB fixture 上又測了四個解析器,把 cheerio 的 2,927.89 ms 拿去和它們並列,反而是本文最有參考價值的部分:
| 解析器(10 MB) | p50 |
|---|---|
| selectolax (lexbor) | 159.93 ms |
| lxml | 172.93 ms |
| parsel | 231.85 ms |
| selectolax (modest) | 247.95 ms |
| BeautifulSoup + lxml | 2,261.56 ms |
| BeautifulSoup + html.parser | 2,788.75 ms |
| cheerio | 2,927.89 ms |
上面四列 Python 數據來自 bench_parse.json 的已發布數值;cheerio 則來自這次測試。參考解析器兩次執行之間可重現於 0.989×–1.079×,所以差異小於約 8% 的結果都可視為落在不確定性範圍內——例如 cheerio 與 BeautifulSoup 的 html.parser 後端(相差 5%)就在這範圍內,但 cheerio 與 selectolax(18 倍)顯然不在。
BeautifulSoup 是很多人想要方便、也知道它速度較慢時會拿來用的工具——幾乎每一篇 Python 效能討論都會叫你換掉它。對 10 MB 文件來說,cheerio 也落在同一個慢速區間的底部,而不是它常被歸類的那些 C 語言後端解析器區間。
至於 Node 端的替代方案,本文仍然沒有定論。較新的 Node 替代工具沒有納入測試,所以這個結果不能說換工具不可能,也不能把它們說成不夠成熟。這裡只證明了,在這些 Python 堆疊對照下,cheerio 的實測路徑是什麼樣子。
這同時是執行環境比較,也是函式庫比較。 cheerio 的毫秒數包含 Node 的 JIT 與垃圾回收行為;其他工具則是 CPython 呼叫 C 後端解析器的結果。內容雜湊證明大家做的是同一件事,而這些數字也確實反映開發者在選擇技術堆疊時會面對的真實體驗——但絕不能把它解讀成「cheerio 的演算法比 selectolax 差 18 倍」。這只是它在這台機器上、各自原生執行環境中的實際表現。
安裝與部署現實
| 函式庫 | 套件數 | 磁碟占用 | 授權 | Stars | 最近一次 push |
|---|---|---|---|---|---|
| cheerio | 22(npm) | 9.0 MiB | MIT | 30,449 | 2026-08-11 |
| PyQuery | 3(pip) | 20.1 MiB | BSD | 2,380 | 2026-07-27 |
官方參考文件:Cheerio 配置說明。
metadata-snapshot.json,截稿當天抓取。
npm install cheerio 花不到兩秒,並拉下 9.0 MiB。冷啟動匯入時間在同一台機器的另一輪轉換測試中量得 0.056 秒。
11 個直接依賴對一個解析器來說不算少,若你有在審核依賴樹,這點值得留意:htmlparser2、parse5、parse5-htmlparser2-tree-adapter、parse5-parser-stream、domhandler、domutils、dom-serializer、cheerio-select、encoding-sniffer、undici 與 whatwg-mimetype。其中有兩套完整的解析器實作,因為 cheerio 會依照需求使用不同後端。
三萬多 stars,而且在測試前一天還有新的 push,這在這類工具裡已經算是非常健康的維護訊號了。
記憶體,以及損壞 HTML 會怎麼影響它
記憶體占用與錯誤 HTML 的處理方式都會影響部署與失敗處理,所以這兩項分開測量。
更完整的壓力測試背景可參考:十種函式庫的記憶體與錯誤 HTML 比較。
峰值常駐記憶體 使用 /usr/bin/time -l 測量,每一格都用全新的 process;import floor 是函式庫載入後、閒置時的成本,峰值則包含文件本身。
| 函式庫 | Runtime | 匯入基線 | 226 KB 峰值 | 10 MB 峰值 |
|---|---|---|---|---|
| html2text | python3.14 | 18.7 | 19.9 | 71.2 |
| pyquery | python3.14 | 30.3 | 33.9 | 172.5 |
| resiliparse | python3.14 | 20.5 | 25.1 | 225.1 |
| markdownify | python3.14 | 23.9 | 28.9 | 278.5 |
| goose3 | python3.14 | 44.1 | 52.4 | 398.5 |
| cheerio | node22 | 66.8 | 76.5 | 398.5 |
| justext | python3.14 | 30.3 | 36.6 | 431.2 |
| newspaper4k | python3.14 | 52.6 | 61.8 | 668.5 |
| trafilatura | python3.14 | 52.5 | 64.8 | 927.1 |
| turndown | node22 | 47.8 | 68.4 | 2947.1 |
memory-results.json。Python 與 Node 的基線彼此不能直接比較,因為兩者都把 interpreter 算在內。
在這張混合對照表裡,cheerio 的匯入基線最高,達到 66.8 MiB,包含 Node runtime 與相關依賴。它在 10 MB fixture 上的程序峰值來到 398.5 MiB。其他列包含解析器、轉換器與文章擷取器,它們的主要工作不同,因此應該把它們當作 process footprint 的背景資料,而不是同類性能排名。同一 runtime 的 turndown 峰值更高,但它做的是轉換,不是 cheerio 在此測量的標題與 href 擷取契約。
錯誤 HTML。 十二份文件各自只破壞一件事——未關閉標籤、inline 元素錯置巢狀、帶空格卻未加引號的屬性、多餘的閉合標籤、完全沒有 <html>、重複屬性、文件在標籤中間被截斷、錯誤實體、未關閉的 <script>、說謊的 charset 宣告、含有標記語法的註解,以及 600 層巢狀——再加上 兩份在相同大小下的正常控制樣本,因為「它什麼都沒回傳」只有在同大小的乾淨文件也一樣沒反應時,才能說明它對錯誤 HTML 的處理狀況。
cheerio 在 14/14 中沒有拋錯,也沒有回傳空結果,並在錯誤 fixture 上恢復了 11/22 個評分哨兵值(malformed-results.json)。對解析器來說,評分器會檢查 11 份可評分的錯誤文件中的標題與連結哨兵值;它不會計入段落哨兵,而且未閉合 <script> 的 fixture 會被排除。由於本節沒有同契約的基準對照,11/22 不能直接作為品質排名。能得出的結論只有:cheerio 在 14 份錯誤加控制樣本上都成功回傳非空輸出、沒有拋錯,並且找回了一半的評分標記。
優點與缺點
優點。 熟悉的 jQuery 語法。MIT 授權。30,449 stars 與測試前一天仍有 repository 活動,代表維護狀況良好。雖然後端與編碼相關套件都有存在,但本次沒有把後端容錯與編碼準確度拆開測。排序後的標題加 href 雜湊,在所有 fixture 大小上都與參考結果一致。
缺點。 在 10 MB 文件上比 selectolax 慢 18.5 倍,在 1 MB 上也慢 4 倍。11 個直接依賴,其中還包括兩套完整解析器。只支援 Node。而且它的文件裡,並沒有任何明確提示告訴你多大之後它就不再是顯而易見的選擇。
誰適合用,誰不適合用
適合使用 cheerio 的情境是:你在 Node 環境中工作、你重視類 jQuery 的 API,而且你的代表性頁面大小大致落在本次測試的 1 MB 以下。1 MB 是本次測試中最大的點,而 10 MB 的跳升則非常明顯;本文沒有證明兩者之間的臨界點,也沒有宣稱整個網路有多少內容低於這個範圍。
在真正採用前先做 benchmark,特別是面對大型 HTML 文件,例如自動產生報表、目錄匯出、或超長列表頁。XML sitemap 的行為沒有測試過。對 10 MB HTML fixture 而言,每份文件 2.9 秒是會累積成實際成本的。
如果你在 Python 環境中,這個比較反而傳達了另一件事:selectolax、lxml 與 PyQuery 在 10 KB 以上其實幾乎並列(差距介於 0.5% 到 5.4% 之間,而且執行區間重疊),所以你可以優先看 API 而不是速度。cheerio 跟這三者的差距才是最值得注意的數字,而不是它們彼此之間的差異。
代管 API 的角色在哪裡
cheerio 是解析你已經拿到的 HTML。它不會抓取頁面、不會渲染 JavaScript,也不會處理反機器人機制——而在很多真實目標上,後面這一半才是更難的部分。
像我們自己的 Thunderbit 這類代管的抓取/渲染/擷取服務,站在不同的責任邊界上。本次並未測試 Thunderbit。真正的差別在於:一邊是用 selector 去解析你提供的 HTML,另一邊則是把取得、渲染與擷取外包出去;本文沒有提供相同指標下的品質、延遲或成本比較。
比較公平的說法是:如果 HTML 已在手上,而且你也知道該用哪些 selector,那 cheerio 免費又好用。若你需要大規模抓頁,或你更想描述資料而不是 DOM,那就是不同層級的需求了。
如果你想看更完整的市場,我們的 web scraping API 總覽 會整理代管方案,而 開源爬蟲總整理 則涵蓋自架選項。如果擷取後的內容要送進模型,這篇 在 Python 中將 HTML 轉成 Markdown 會討論 fidelity 會在哪些地方流失。
你該不該用 cheerio?
如果你在 Node 中工作,且 API 風格很重要,而你的文件規模大致落在本次測試的小型到 1 MB 範圍內,那答案是:可以。
API 的熟悉度與目前的維護訊號,都是合理的選擇依據。這份 benchmark 並不能證明某個客服答案一定存在,也不能保證本次測試的頁面大小分布與你的 production 資料集完全相同。
真正該記住的是 10 MB 那一列。從 1 MB 到 10 MB 之間的某個位置開始,cheerio 的成本就不再跟其他工具同步,而是開始倍增——4 倍變成 18.5 倍。如果你的資料集中有這麼大的文件,正式採用前一定要先 benchmark,因為函式庫本身不會提醒你。
試用 Thunderbit 進行網頁資料擷取 Get Started Free
常見問題
拿 cheerio 跟 Python 解析器比較公平嗎?
這是堆疊比較,不是演算法比較。四個工具在 5/5 的頁面大小上,都依照雜湊規則產生了相同的排序標題文字與 href。這並不代表整個解析器行為完全等價。cheerio 的時間包含 Node runtime 的行為,其他工具則是 CPython 呼叫 C 後端解析器;這個比較描述的是這些端到端選擇。
為什麼 1 KB 那列不排名?
因為 28 微秒的量級幾乎完全被雜訊主導。三次執行下,Python 解析器的落差高達 77.6%,而且單次結果互相重疊。任何在這種大小下的排序都只是測量假象。從 10 KB 以上開始,結果才夠穩定可讀。
10 MB 時的跳升是什麼原因?
這次測試沒有辦法回答。它只能證明這個跳升是真實存在而不是雜訊:cheerio 三次結果落在 2,839、2,928 與 2,954 ms,明顯與其他結果分開,而 1 MB 時的差距只是 4 倍。若要找原因,就得把 cheerio 的 parser 後端分開做 profiling,這已超出本文範圍。
它到底有多少依賴?
11 個直接依賴,展開後有 22 個頂層套件,磁碟占用 9.0 MiB。其中有兩個是完整解析器實作——htmlparser2 與 parse5——因為 cheerio 可以依需求使用其中任一個。這就是它同時兼顧寬鬆解析與規格相容解析的代價;如果你會審核依賴樹,這很值得知道。
這裡沒有測什麼?
這份草稿確實測了 226 KB 與 10 MB 文件的 process 峰值記憶體,也測了 14 個輸入的錯誤 HTML+控制樣本集合,其中 cheerio 沒有拋錯、全部都有非空輸出,並回收了 11/22 個評分哨兵值。但它沒有測 parse5-parser-stream 的串流模式、編碼正確性、特定後端的修復能力、1 到 10 MB 間性能跳升的確切位置、XML 解析,或更新的 Node 替代方案。另外,原始相對路徑的 artifact 連結也要求發佈時仍維持相同的公開目錄結構;否則就需要可長期存取的公開 URL 或倉庫 commit 參照。


