在同一台機器上,cheerio 在 Node 中解析並擷取一份 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 建構樹狀結構,cheerio-select 負責選擇器,外加 undici、encoding-sniffer 等等——一共 11 個直接依賴,解決後會展開成 22 個頂層套件,磁碟占用 9.0 MiB。這些套件提供了解析與編碼相關能力,也一併增加了依賴負擔。本次評測有測試錯誤 HTML 的輸出,但沒有測試編碼正確性,也沒有把兩個解析後端分開獨立比較。
這次量測怎麼做,為什麼可信
這套研究基底本來就有解析器基準測試:頁面大小從 1 KB 到 10 MB 共五個級距、每個點 50 次迭代、三輪獨立執行,而且最重要的是——有一個一致性門檻,會把擷取出的內容雜湊比對基準解析器:排序後的標題加上排序後的 href。任何悄悄少做事的解析器,都不能靠偷快取得好成績。
要把 cheerio 加進來,先得做兩個檢查,數字才有意義。
基準結果的位置有沒有變? selectolax 在同一個 session、同一組 fixtures 上重新執行。它的內容雜湊在 5/5 種大小上都能重現,而 p50 落在公開數字的 0.989× 到 1.079× 之間。也就是說,這台機器確實產生了原始表格。
cheerio 有沒有產生相同的評分欄位? 它的內容雜湊——在 Node 中用相同規則計算,以排序後標題文字與排序後 href 做 SHA-256——在 5/5 種大小上都與基準一致。這證明它在這些 fixtures 上,對這些排序後欄位是等價的;但不代表 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 中執行,這既是 runtime 邊界,也是 library 邊界——下面會再說。
如何解讀這張表,才算誠實

1 KB 那一列幾乎是雜訊。 三個 Python 解析器在該尺寸下的差距達 77.6%,單次執行結果也大幅重疊——selectolax 在三輪中的範圍是 0.0267 到 0.0404 ms。28 微秒這種等級,計時器解析度與排程影響都太大了。1 KB 我不會拿來排名,cheerio 也一樣。
表格中段沒有太多戲劇性。 10 KB 到 1 MB 之間大約是 2 倍到 4 倍。對一個要抓幾百頁的爬蟲來說,這只是每頁 45 毫秒而不是 15 毫秒,實務上你根本感覺不到。
10 MB 那一列不是雜訊。 cheerio 的三輪結果是 2,839、2,928 與 2,954 ms——很集中,而且和其他大小的數字明顯分開。10 MB 的端到端結果,明顯偏離較小尺寸時的模式。五個尺寸點不足以建立漸近複雜度,也不足以判定到底是 runtime、parser、selector、配置,還是 garbage collection 哪一層造成這個跳升。
它落在 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 替代工具沒有被測,所以這個結果不能代表「換工具不可能」,也不能說它們比較不成熟。它只證明了這次量測下的 cheerio 與列出的 Python 堆疊相比如何。
這不只是 library 比較,也是 runtime 比較。 cheerio 的毫秒數來自 Node 的 JIT 與 garbage collector;其他方案則是 CPython 呼叫 C 背景解析器。內容雜湊證明做的是同樣的工作,而這兩個數字也正是開發者在選擇技術堆疊時真正會感受到的差異——但沒有人應該把它解讀成「cheerio 的演算法比 selectolax 糟 18 倍」。這只是這台機器上、各自原生 runtime 裡,實際發生的結果。
安裝與環境現實面
| 函式庫 | 套件數 | 磁碟占用 | 授權 | 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。冷啟動 import 在同一台機器的另一輪轉換測試中量得 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,再加上測試前一天還有專案活動,對這類工具而言,這已經算是很健康的維護訊號了。
記憶體,以及損壞 HTML 會怎麼影響它
記憶體占用與錯誤 HTML 的處理行為,都會影響部署與失敗處理,因此這裡分開量測。
更完整的壓力測試背景可參考十個函式庫的記憶體與錯誤 HTML 比較。
峰值常駐記憶體以 /usr/bin/time -l 量測,每個欄位都啟動一個全新 process——import floor 代表函式庫載入且閒置時的成本,峰值則包含文件本身。
| 函式庫 | Runtime | Import 下限 | 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 在這個混合情境表中具有最高的 import 下限,66.8 MiB,這已經包含 Node runtime 與依賴。它在 10 MB fixture 上的 process 峰值達到 398.5 MiB。其他列包含解析器、轉換器與文章擷取器,主要工作各不相同,因此請把它們視為 process 占用背景,而不是同類性能排名。同 runtime 的 turndown 峰值更高,但它做的是轉換,而不是 cheerio 這裡測到的標題與 href 選取契約。
損壞 HTML。 這組資料包含 12 份文件,每份都只故意壞掉一件事——未關閉標籤、內嵌元素錯位、帶空白且未加引號的屬性、孤立的閉合標籤、完全沒有 <html>、重複屬性、在標籤中途截斷的文件、錯誤 entity、未關閉的 <script>、故意誤導的 charset 宣告、包含 markup 的註解,以及 600 層巢狀結構——另外再加上兩份同大小的正常控制組,因為「它什麼都沒回傳」只有在同尺寸的正常文件也同樣沉默時,才能說明它真的對 malformed 很脆弱。
cheerio 在 14 份測試中 0 份拋錯、0 份回傳空值,在 malformed fixtures 中成功恢復了 11/22 個評分哨兵(malformed-results.json)。對解析器來說,評分器會檢查 11 份可評分的 malformed 文件中的標題與連結哨兵;它不計算 paragraph 哨兵,而未關閉 <script> 的 fixture 也被排除。在本節沒有相同契約的基準可對照時,11/22 不能算是品質排名。能支持的結論只有:cheerio 對全部 14 份 malformed+control 輸入都回傳了非空結果且沒有拋錯,並且成功恢復了一半的評分標記。
優缺點
優點。 熟悉的 jQuery 語法。MIT 授權。30,449 顆 stars 與測試前一天還有 repo 活動,代表維護狀態健康。雖然此處沒有把 backend recovery 與編碼準確性拆開測試,但它確實內建兩種解析後端與編碼相關套件。排序後的標題+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,也不會處理 anti-bot 層——而在許多真實目標上,後者才是更難的一半。
像我們自己的 Thunderbit 這類代管的抓取/渲染/擷取服務,屬於不同的責任邊界。這裡沒有對 Thunderbit 做 benchmark。關鍵差異在於:是用 selector 去解析你提供的 HTML,還是把取得、渲染與擷取整套外包;本篇文章沒有提供同一指標下的品質、延遲或成本比較。
比較公平的說法是:如果 HTML 已經在你手上,而且你也清楚自己的 selectors,那 cheerio 既免費又好用。若你要大規模抓頁,或者你想描述的是資料而不是 DOM,那就是另一種採購決策。
如果你想看更完整的市場,我們的網頁抓取 API 總覽整理了代管選項,而開源爬蟲總整理則涵蓋自架方案。如果解析後的結果要送進模型,想知道 fidelity 會在哪裡流失,可以參考在 Python 中將 HTML 轉成 Markdown。
你該用 cheerio 嗎?
如果你在 Node 裡,而且 API 的相容性很重要,且你的代表性文件大小大致接近這次測試的小型到 1 MB 區間,那答案是:可以。
API 熟悉度與目前的維護訊號,都是合理的選型依據。不過這份 benchmark 不能證明某種支援一定存在,也不能證明你實際生產資料的頁面大小分布與這次測試相同。
你真正該記住的,是 10 MB 那個數字。在 1 MB 與 10 MB 之間的某個地方,cheerio 的成本開始不再跟其他工具平行,而是倍數放大——4× 變成 18.5×。如果你的資料集中可能出現這麼大的文件,在正式採用前先跑 benchmark,因為函式庫本身不會提醒你。
試用 Thunderbit 進行網頁資料擷取 Get Started Free
常見問題
拿 cheerio 跟 Python 解析器比,公平嗎?
這是堆疊比較,不是演算法比較。四個方案在 5/5 種頁面大小上,都依照相同的雜湊規則產生了完全一致的排序後標題文字與 href。這不代表所有 parser 都完全等價。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 個輸入的 malformed+control 集合;其中 cheerio 沒有拋錯、全部都回傳非空結果,並恢復了 11/22 個評分哨兵。沒有測試 parse5-parser-stream 的串流模式、編碼正確性、後端特定修復能力、1 MB 與 10 MB 之間性能跳升的位置、XML 解析,或更新的 Node 替代方案。


