PyQuery 在這項基準測試中加入 jQuery 語法,且沒有帶來足以影響決策的選擇器額外負擔

最後更新於 August 18, 2026
PyQuery 在這項基準測試中加入 jQuery 語法,且沒有帶來足以影響決策的選擇器額外負擔
AI 摘要
PyQuery puts a jQuery-style API over lxml. Across five page sizes from 1 KB to 10 MB, all five displayed medians were lower than raw lxml's, including a 1.5% difference at the largest size. The benchmark does not establish that the wrapper is faster; it found no difference large enough to change this selector-and-read decision. It was also within a few percent of selectolax from 10 KB upward in the displayed medians. Without a predefined equivalence margin, that is a close result rather than a statistical tie.

PyQuery 在 lxml 之上包了一層 jQuery 風格的 API。從 1 KB 到 10 MB 這五種頁面大小來看,五組顯示出的中位數都比原生 lxml 更低;就算是在最大尺寸時,也只差 1.5%。不過,這份基準測試不能證明這個包裝層比較快;它只說明在這種選擇器加讀取的情境下,差異小到不太會影響選擇。

從 10 KB 起,在顯示的中位數裡,它也都和 selectolax 相差幾個百分點以內。若沒有先定義等效範圍,這最多只能算結果很接近,不能當成統計上的平手。

什麼是 PyQuery

PyQuery 是一個 Python 函式庫,讓你可以用 jQuery 式的選擇器和鏈式 API 來操作 lxml 文件樹。測試版本:2.1.0,BSD 授權,2,380 個 GitHub stars,59 個開放議題,最後一次推送時間為 2026-07-27。

官方參考文件: PyQuery documentation

System diagram: jQuery Syntax Over lxml

from pyquery import PyQuery as pq
d = pq(html)
titles = [e.text_content() for e in d("h3.title")]
hrefs = [e.get("href") for e in d("a")]

PyQuery 回傳的元素本質上就是 lxml 元素,所以你如果會 lxml 的用法,在這裡也一樣能用。這正是它的設計概念:PyQuery 是用來提升操作手感的外層,不是解析器本身。pip install pyquery 會裝入 3 個套件——lxml、cssselect 與 PyQuery 本身——總計 20.1 MiB,幾乎全部都是 lxml 的編譯擴充模組。

如果你用過 Node 裡的 cheerio,這在 Python 裡基本上就是同一種 API 思路。這裡測的兩個選擇器在兩邊都能正常運作;但這份測試不能證明 cssselect 和 cheerio 在選擇器語言上完全對等。

測量方式

這個研究基礎本來就有一套解析器基準測試,而它比多數 benchmark 多了一個特點:內容一致性門檻,會把擷取出的內容——排序後的標題與排序後的 href——和參考解析器做雜湊比對,所以不可能讓「少做事」的函式庫直接拿到好看的數字。測試規模為五種頁面大小、50 次迭代、3 組獨立執行。

把 PyQuery 加進來需要兩件事。

重新執行參考項。 selectolax 在同一個行程中再次執行。它的內容雜湊在 5/5 種尺寸都能重現,而且 p50 落在已發表數值的 0.989× 到 1.079× 之間——這代表是同一台機器、同一個測試環境。

也在同一個行程中執行 lxml。 已發表的 benchmark 記錄了機器和 Python 版本,但沒有記錄函式庫版本,因此其中的 lxml 列,可能來自和這裡 PyQuery 所包裝的不同版本。如果直接跨過這個差異比較,就會把兩個不同的 lxml 版本混在一起,然後誤以為是包裝層的成本。把 lxml 一起跑,這個問題就消失了——這個 venv 裡兩者都使用 lxml 6.1.1。

頁面大小selectolaxPyQuerylxmlPyQuery 相對 lxml
1 KB0.0286 ms0.0456 ms0.0508 ms0.90×
10 KB0.1725 ms0.1728 ms0.1802 ms0.96×
100 KB1.4855 ms1.4093 ms1.4145 ms1.00×
1 MB14.97 ms14.96 ms15.03 ms1.00×
10 MB158.10 ms162.86 ms165.25 ms0.99×

p50 毫秒,三次執行的中位數,全部都在同一個行程內完成。parser-bench.json。在每個尺寸上,三次內容雜湊都與參考值一致。

其實沒有的包裝層成本

Measured results chart: PyQuery and lxml on the same fixture

PyQuery 在所有顯示的中位數上都不高於原生 lxml。這不代表包裝層會讓解析變快。三次執行的中位數,以及沒有先定義的等效範圍,只能支持一個更窄的結論:在這個測例中,沒有出現足以影響決策的選擇器額外負擔。

在 10 MB 時,PyQuery 的三次結果是 162.86、163.17 和 161.13 ms;lxml 則是 169.46、165.25 和 164.18 ms。兩邊範圍相近,但沒有重疊。在 1 MB 時,兩者中位數只差 0.5%。這些小差距足以支持實務判斷,但還不足以主張統計上的等效。

System diagram: Wrapper and Parser Boundaries

它的運作方式其實很單純:pq(html) 先建立一次 lxml 樹,d("h3.title") 會透過 cssselect 把 CSS 選擇器編譯起來,就像 tree.cssselect() 一樣,而回傳的元素也是 lxml 元素。也就是說,在這條被測量的熱路徑上,PyQuery 幾乎沒有太多額外工作。遍歷、修改、重複查詢、匯入和記憶體成本,都不屬於這次關於選擇器時間的主張。

從 10 KB 起就很接近的結果

更有價值的發現,其實在第一欄。

從 10 KB 以上來看,在 selectolax、lxml 與 PyQuery 之間,最快到最慢的中位數差距在 10 KB 時為 4.5%,100 KB 時為 5.4%,1 MB 時為 0.5%,10 MB 時為 4.5%。這次不是等效性測試;實務上的判斷是,這些差距大致不會改變多數人在這種工作負載下對解析器的選擇。

selectolax 在 1 KB 時確實更快——0.0286 ms 對 0.0456 和 0.0508——但這一列其實不能解讀。三個解析器在這個大小下的差距高達 77.6%,而 selectolax 自己的三次結果也從 0.0267 到 0.0404 ms 不等。以 28 微秒來看,計時器和排程器的影響已經蓋過解析器本身。我不會在那裡做排名。

對這種選擇器加讀取的工作負載來說,這三者應該優先根據 API 和實際依賴條件來選,而不是預設哪個一定比較快。PyQuery 對 lxml 沒有顯示出足以影響決策的成本。selectolax 用的是不同的解析器堆疊,但這篇文章沒有在同一個基準下測它的安裝體積、wheel 覆蓋範圍或建置需求。

如果要做對照,已發表的 benchmark 在同一個 10 MB 測例上還放了另外兩個 Python 選項,而真正拉開差距的是它們:

解析器(10 MB)p50
selectolax (lexbor)159.93 ms
lxml172.93 ms
parsel231.85 ms
selectolax (modest)247.95 ms
BeautifulSoup + lxml2,261.56 ms
BeautifulSoup + html.parser2,788.75 ms

已發表數值來自 bench_parse.json

歷史 benchmark 的這些列顯示,BeautifulSoup 在這個測例上比速度較快的解析器中位數高出超過一個數量級。這些數值沒有在目前的 PyQuery/lxml 同行程測試中重新跑過,因此它們比較適合拿來當背景脈絡,而不是主結論的控制倍數。

Cheerio 的存檔結果是 2,927.89 msparser-bench.json 中為 2927.8857),而且擷取內容的雜湊也一致。這個跨執行環境的結果同樣受 Node、套件版本與歷史執行控制影響,不應被解讀成單純的函式庫速度倍率。

安裝與使用現實面

函式庫套件數硬碟空間授權Stars最後推送
PyQuery320.1 MiBBSD2,3802026-07-27
cheerio (Node)22 (npm)9.0 MiBMIT30,4492026-08-11

官方參考: PyQuery on PyPI

metadata-snapshot.json

3 個套件的依賴足跡算是相當乾淨,而且其中 2 個——lxml 與 cssselect——對很多 Python 網頁爬取專案來說本來就已經存在。若是這樣,PyQuery 的額外成本就只剩幾十 KB。

20.1 MiB 指的是 lxml 的編譯擴充模組,不是 PyQuery 本身。這和你直接使用 lxml 時要付出的 20 MiB 是同一筆成本。

這個套件採 BSD 授權。以當時的快照來看,它有 59 個開放議題,而測試前大約三週有一次推送;單看這些資訊,並不能證明維護品質或未來相容性。

記憶體,以及損壞 HTML 對它的影響

本批次每篇評測都列為未測的兩件事,現在都已量化。

更廣泛的壓力測試背景,請見 十個函式庫的記憶體與錯誤 HTML 比較

峰值常駐記憶體 透過 /usr/bin/time -l 測量,每個格子都用全新行程執行一次——匯入底線代表函式庫載入後、閒置時的成本,而峰值則包含文件本身。

函式庫執行環境匯入底線226 KB 峰值10 MB 峰值
html2textpython3.1418.719.971.2
pyquerypython3.1430.333.9172.5
resiliparsepython3.1420.525.1225.1
markdownifypython3.1423.928.9278.5
goose3python3.1444.152.4398.5
cheerionode2266.876.5398.5
justextpython3.1430.336.6431.2
newspaper4kpython3.1452.661.8668.5
trafilaturapython3.1452.564.8927.1
turndownnode2247.868.42947.1

memory-results.json。Python 和 Node 的基準不能直接互相比;兩邊都把直譯器成本算進去了。

PyQuery 在大型文件上的記憶體使用量比 resiliparse 更省——172.5 MiB 對 225.1 MiB——就算它的匯入底線比較高。lxml 的樹結構本來就很精簡,而 PyQuery 30.3 MiB 的底線裡,大多數其實是 lxml 被載入的成本,不是 PyQuery 額外做了什麼。

錯誤 HTML。 測了 12 份各自只壞一個地方的文件——未關閉標籤、巢狀錯誤的 inline 元素、帶空白卻沒加引號的屬性、亂入的結尾標籤、完全沒有 <html>、重複屬性、在標籤中途截斷的文件、錯誤 entity、未關閉的 <script>、說謊的 charset 宣告、包含標記的註解,以及 600 層巢狀結構——再加上 兩份同尺寸的正常對照組,因為「它什麼都沒回傳」只有在同尺寸乾淨文件上也同樣沉默時,才真的能說明它對壞格式有反應。

pyquery 在 14 份測試中有 0 份報錯1 份回傳空結果,並在這些破損測例中恢復了 10/22 個 sentinel(malformed-results.json)。其中有一份測例不計入這個統計:依 HTML5 規範,未關閉 <script> 後面的內容本來就都算 script 內容,所以在那裡少掉是正確行為,若把內容找回來反而才是偏差。若沒有把直接替代方案的 sentinel 結果放在一起看,10/22 只能算是韌性觀察,而不是解析器選擇排名。

優點與缺點

優點。 jQuery 語法,對寫過前端 JavaScript 或用過 cheerio 的人來說很熟悉。在這個測例裡,和原生 lxml 相比沒有出現足以影響決策的選擇器額外負擔。只有 3 個套件,而且其中 2 個你很可能早就有。它回傳的是 lxml 元素,所以 lxml 的操作技巧還是都能用。BSD 授權。在所有五種尺寸上,內容雜湊都和參考值一致。

缺點。 20.1 MiB,因為 lxml。2,380 stars 代表社群規模遠小於 cheerio 的 30,449——遇到奇怪問題時,可參考的實戰案例也比較少。它只是方便層,所以 lxml 做不到的事,它也做不到。如果你期待 jQuery API 帶來效能,那不會;它帶來的是操作手感,而真正做工的是底下那個解析器。

誰適合用,誰不適合

適合用 PyQuery:如果你或你的團隊偏好在 Python 裡用 jQuery 風格的選擇器。這次量到的建構、兩次選取和讀取流程,對 lxml 沒有顯示出足以影響決策的成本;其他 PyQuery 操作則沒有納入計時。

適合直接用 lxml:如果你偏好 XPath,或想少裝一個套件。這次測試沒有提供足以在兩者之間做效能取捨的理由。

可以評估 selectolax:如果它的解析器 API 和依賴堆疊更符合你的專案。1 KB 那一列明確不列名次,而本文也不支持「依賴最小」這種說法。

在 Node 中,cheerio 是相近的 API 形狀。這裡存檔的跨執行環境結果確實比較慢,但因為執行環境和歷史跑法不同,無法乾淨地得出單純的函式庫結論。

受管 API 的適用位置

PyQuery 是用來解析你已經拿到的 HTML。它不會抓取頁面、不會渲染 JavaScript,也不會處理反機器人層——這次比較裡沒有任何解析器能做這些,而在很多真實網站上,那一半其實才是更難的部分。

作者註記: Thunderbit 是我們提供的受管方案,負責 URL 取回、渲染與擷取。這裡沒有拿它和 PyQuery 直接比較。關鍵分界在於:你是已經握有 HTML、只想在本地跑選擇器,還是想把頁面取得和擷取交給服務來處理。

誠實地說,如果你手上已有 HTML,也知道自己要抓什麼,PyQuery 便宜又好用;如果選擇器老是壞,或者你正在大規模抓取,那就是另一個層級的採購問題了。

若你想看更廣的選項,我們的 web scraping API roundup 涵蓋了託管方案,而 open-source scraper pillar 則整理了自架工具。如果解析後的輸出要交給模型,請看 convertir HTML a Markdown in Python,那裡最容易丟失的是忠實度。

試用 Thunderbit 進行網頁資料擷取

那麼,你該用 PyQuery 嗎?

如果你想在 Python 裡用 jQuery 風格語法,而且這次測到的選擇和讀取路徑剛好符合你的工作負載,那答案是肯定的。

這份 benchmark 在保留內容雜湊一致性的前提下,沒有發現相對 lxml 有足以影響決策的選擇器成本。它也沒有證明整個函式庫是零成本。

在五種尺寸下,三個 Python 解析器的中位數都靠得很近,所以就這個任務來說,API 是否合用很可能比速度排名更重要。若要把這個判斷擴大成更一般的解析器排名,請先定義等效範圍,再把完全相同的替代方案重新跑一次。

試用 Thunderbit 進行網頁資料擷取 Get Started Free

常見問題

PyQuery 會拖慢 lxml 嗎? 這次沒有出現足以影響決策的選擇器額外負擔。五種頁面大小下,它的中位數都落在原生 lxml 之下或相同區間,而且兩者都在同一個行程中使用 lxml 6.1.1。10 MB 時,兩邊的範圍沒有重疊:PyQuery 為 161.13–163.17 ms,lxml 為 164.18–169.46 ms。pq(html) 會建立 lxml 樹,而測試的選擇器則透過 cssselect 編譯。

selectolax 比 PyQuery 快嗎? 它在 1 KB 時的中位數較低,但那一列不列名次,因為在微秒尺度上,變異比差距更大。從 10 KB 起,中位數差距約為 0.5%–5.4%。對這個工作負載來說這算很接近,而不是在所有情況下都能證明等效或範圍重疊。

為什麼要重新跑 lxml,而不是直接引用已發表的數字? 因為已發表的 benchmark 記錄了機器和 Python 版本,卻沒有記錄函式庫版本。它的 lxml 結果可能來自和今天 PyQuery 所包裝的不同 lxml;如果版本有差,就會被誤看成包裝層成本,而事實上不是。把兩者放在同一個行程裡、都使用 lxml 6.1.1,就能消除這種歧義。

它和 cheerio 相比如何? API 思路相同,但生態不同。這裡測到的兩個選擇器在兩邊都可運作,而且五個尺寸的內容雜湊都一致;但這不代表所有選擇器都完全相容。cheerio 的存檔時間較慢,不過跨執行環境和歷史跑法的差異,讓我們不能把它解讀成純函式庫倍率。

這裡沒有測什麼? 記憶體測了匯入-only、226 KB 文件和 10 MB 文件時的峰值 RSS。錯誤 HTML 則用 12 份破損文件加 2 份對照組測過了。仍未測的包括 PyQuery 的修改和遍歷效能、重複查詢快取、URL 抓取、並行處理,以及具代表性的真實網站工作負載。1 KB 的計時結果仍不列名次。

Ke
Ke
Thunderbit 技術長|資深資料科學家與機器學習專家 Ke Shen 在機器學習與資料科學領域擁有近十年經驗,畢業於哥倫比亞大學,曾任 Walmart Labs 資深資料科學家。他精通 Python、R、Java 與統計學,且具備深受同儕認可的深厚專業,分享如何將複雜的 AI 演算法從理論落實到可投入生產的架構的實戰見解。
目錄
Thunderbit · AI 網頁資料代理

1 次點擊 內擷取任何頁面的資料

深受 250,000+ 用戶信賴
提供免費方案
從網頁到試算表
描述你需要的內容——Thunderbit 的 AI 代理會幫你爬取,並匯出到 Excel、Google Sheets、Airtable 或 Notion。免費即可開始。
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week