lxml 評測:至今仍能碾壓多數 Python 解析器的 XPath 引擎

最後更新於 July 17, 2026
lxml 評測:至今仍能碾壓多數 Python 解析器的 XPath 引擎
AI 摘要
This lxml review frames the library as the long-running Python binding around libxml2 and libxslt, with one major advantage that newer parsers still rarely match: a real XPath engine. The article tests XPath coverage, parser strictness modes, streaming memory behavior, CSS-versus-XPath expressiveness, and libxml2 depth limits. It shows lxml as fast, memory-efficient, and unusually capable for XML and HTML workloads that need axes, predicates, functions, streaming, or robust recovery modes. The review also explains the security-minded tree-depth defaults and when huge_tree changes that boundary.

每隔幾個月,就會冒出一個更快的 HTML 解析器,跑分也會跟著洗版,然後總有人宣告舊派工具已經過時。可等你真的要挑出每一個「包含」特定關鍵字的段落,或是抓出某個匹配節點的父層時,就會想起為什麼 lxml 依然開著在你另一個分頁裡。

lxml 是一個已有 20 年歷史的 libxml2 綁定。它談不上新潮,也不算話題焦點。但在某一類工作上——任何真正需要 XPath 的場景——主流 Python 生態裡幾乎沒有別的工具能正面對打。這篇實測會帶你看它到底做得到什麼、在哪些地方默默勝出,以及有哪幾個預設值如果你沒先知道,遲早會踩雷。

一段話看懂 lxml:它到底是什麼

lxml 是 Python 對 C 函式庫 libxml2 和 libxslt 的綁定。它是解析器與序列化器,不是爬蟲,也不是瀏覽器——它能把標記語言轉成你可以查詢和編輯的樹狀結構,再把這棵樹轉回位元組。它提供與 ElementTree 相容的 API、完整的 XPath 1.0 引擎、XSLT 1.0,以及結構驗證功能,由 Stefan Behnel 維護,標語是 "Python 語言中最功能豐富、也最容易使用的 XML 與 HTML 處理函式庫"

以下是依據 2026-07-14 拉取的 GitHub 與 PyPI 快照所整理的現況:

欄位數值
Repolxml/lxml
Stars3,043
Forks620
Open issues16
授權BSD-3-Clause
建立日期2011-02-11
最後推送2026-07-02
PyPI 穩定版6.1.1(2026-05-18)
內建引擎libxml2 2.14.6 + libxslt 1.1.43

在進入正文前,先說清楚一件事,免得有人以為我在吹捧:這篇評測沒有什麼隱藏彩蛋。lxml 夠老了,本文提到的每一種行為都能在 lxml 文件、libxml2 的更新記錄,或某個討論串裡找到依據。我沒有挖到什麼不公開的秘技,也不打算硬掰一個。這篇文章的價值在於把資訊系統化、量化,並且圍繞 lxml 本身來整理,而不是因為它講的是什麼全新發現。

測試設定(以及為什麼時間數字是沿用的)

這篇評測用到兩類資料,而且來源不同,所以我先直接講清楚。

能力測試——像是 XPath 行為、兩種 parser API、命名空間、編碼、節點生命週期——我是在一台機器上重新跑的:macOS arm64、Python 3.14.2、lxml 6.1.1、libxml2 2.14.6。那些 artifacts/raw/*.json 檔裡的數字都是程式跑完算出來的,不是手打上去的。能力測試是可重現的布林值與列舉值,所以單次結果就很穩定——機器負載不會影響 //a/@href 到底回傳不回傳屬性字串。

時間與記憶體數字不是這次新跑的。 這些直接沿用前一份 selectolax 基準測試包的結果——同一台機器、同一個虛擬環境、同一套 lxml 和 libxml2 建置、基準時間以 2026-07-13 為準——我沒有在這裡重新執行。這是刻意的。若在做一批能力測試時同時重跑效能基準,CPU 競爭很容易污染沿用的數據;而且那也屬於重複工作:在上一份測試包裡,lxml 本來就已經是完整量測過的對照組。沿用同一份 benchmark,才能保持一致比較,而不是引入第二種略有差異的測量方式。所以當你在下方看到毫秒數字,請把它理解成「同一套測試環境、截至 2026-07-13」,而不是「我今天重新計時過」。

每個結論都附有信心標籤:single-observation 代表可重現的能力測試,triple-run 代表沿用的時間分佈,hypothesis 則是我提出但沒有單獨驗證的機制推測。

XPath:selectolax 和 BeautifulSoup 就是沒有這一招

這就是本篇主軸,所以先從這裡開始。

lxml XPath coverage moat with axes predicates and functions

我把 lxml 的 xpath() 丟進一個事先註冊好的 37 項矩陣測試:每一個案例的預期結果都先寫進原始碼,測試開始前就定好,避免事後調整標準。測了十種軸、九種 predicate 寫法、十個內建函式、三種純量回傳型別,以及五個故意設下的陷阱案例,使用的是 XPath 2.0 才有的語法,而 lxml 的 1.0 引擎理應要拒絕。

類別覆蓋範圍結果
軸(axes)child / descendant / parent / ancestor / following-sibling / preceding-sibling / self / attribute / following / preceding10/10 通過
Predicate[1] / last() / position()<n / 屬性相等 / 屬性存在 / and / or / 巢狀 [.//a] / not()9/9 通過
函式text() / contains() / starts-with() / count() / string-length() / normalize-space() / concat() / substring() / name() / string()10/10 通過
回傳型別布林值 / 數值純量3/3 通過
陷阱案例matches() / sequences / if-then-else / except / 語法錯誤5/5 正確拒絕

總分是 37/37,而真正重要的是「陷阱案例」那一欄。matches()、sequence 表達式、if/then/elseexcept 都是 XPath 2.0 語法,libxml2 的 1.0 引擎不是半套支援,而是直接丟出 XPathEvalError 拒絕執行,不會悄悄回傳錯誤的節點集合。這代表它是在被刻意刁難之後仍拿滿分,而不是靠簡單題湊出來的分數。這裡每一項行為都完全符合 lxml XPath 文件 的描述,這正是重點。

有一件事我得老實承認,因為這才是你真的能信的 37/37。//div[.//a[@href]] 這個案例,我一開始預期會有兩個命中,結果跑出來只有一個。我當下還以為是 lxml 錯了大概三十秒,後來回頭看 fixture,才發現第二個元素其實是 <footer>,不是 <div>——是我的預期錯了,不是引擎錯了。我把預期集合修正後,還在原始碼註解裡保留了這個失誤。責任歸屬應該這樣排:先懷疑自己的測試,再懷疑一個 20 年歷史的 C 函式庫。

XPath 對上 CSS:有些事情 CSS 根本做不到

「XPath 比較強大」這句話如果只停在抽象層,說服力不夠,所以我把差距量化了。lxml 同時提供 .xpath().cssselect()(後者底層其實是把 CSS 轉成 XPath)。我挑了十個選取目標,檢查 CSS 到底能不能表達。

XPath expresses seven of ten tasks CSS cannot express

目標XPathCSS(cssselect)
依文字內容過濾(contains(text(),"bargain")可以沒有文字 predicate
從子節點找父節點(//b/parent::p可以沒有 parent selector
回傳屬性值(//a/@href可以只能選元素
回傳文字節點(//p/text()可以沒有 text node
ancestor 軸(//td/ancestor::div可以不能往上走
依子元素數量過濾父節點(//ul[count(li)=4]可以沒有 count predicate
依文字長度過濾(string-length(text())>5可以沒有長度 predicate
nth-child / last-child / 相鄰兄弟可以可以(3 個基礎情境)

十個目標裡,有七個 CSS 完全沒有對應寫法。依文字內容篩選、往上找父層或祖先、把屬性值或純文字節點直接當結果、用數量做過濾——CSS 都做不到。只有三個(nth-childlast-child、相鄰兄弟)兩邊都能寫。這就是「我到底能從 lxml 拿到什麼」的量化答案。selectolax 只支援 CSS,而且根本沒有 xpath() 方法,所以那七類查詢在那裡不是變成多步驟 Python 迴圈,就是乾脆無法實作。如果你的爬取邏輯需要這些能力,那選擇其實已經很明確了。

(是的,這個 harness 又抓到我一次:我原本預測 string-length(text())>5 會是空集合,但其實有兩個六個字元的字串符合。修正的是預期,不是工具。)

三段嚴格度:etree、recover 與 lxml.html

XPath 是你會選 lxml 的原因;三段式嚴格度控制,則是你會留下它的原因。

lxml strictness gears: etree, recover, and lxml.html

大多數解析器遇到壞資料時,只會給你一種行為。lxml 給你三種,而且相當可預測——我把六類格式不良的標記丟進三種模式,並且預先寫好了每條路徑應該怎麼反應。

格式不良輸入lxml.etree(嚴格)etree + recover=Truelxml.html(寬鬆)
未關閉標籤 <root><a>x</root>拋出例外自動修復接受
巢狀錯誤 <b><i></b></i>拋出例外自動修復接受
未定義實體 &nbsp;拋出例外自動修復接受
裸露的 &Tom & Jerry拋出例外自動修復接受
多個根節點 <a>1</a><b>2</b>拋出例外自動修復接受
結構完整的 XML接受接受(0 errors)接受
布林屬性 <input disabled>拋出例外自動修復接受

七項裡有七項都符合預先註冊的預期。lxml.etree 會在這六類錯誤格式上全部丟出 XMLSyntaxError。在同一個 parser 裡加上 recover=True,它就會吞掉錯誤並重建出可用的樹——而且最被低估的地方是,parser.error_log 會把它吞掉的每一個錯誤都列出來。lxml.html 則是全部直接接受,不會多說一句。

決定「丟例外 / 自動修復 / 直接接受」的分類器,本身是根據 runtime 的 error_log 長度來判斷,而不是寫死的,因此一份格式完整的文件在 recover=True 下會被正確標記成「接受」(空的 log),而不是「自動修復」。我第一版的分類器只要看到 recover=True 就一律標記成「自動修復」,因此把乾淨輸入誤標了;改成讀實際的 error_log 之後就正常了。

實務上的意義很直接:如果資料流壞了就該明確失敗,選 lxml.etree;如果是骯髒的真實世界 HTML,只求先解析過去,選 lxml.html;而大多數工具做不到的中間方案——「可以寬鬆,但請告訴我到底壞在哪裡,好讓我記錄」——則用 recover=True 再讀 error log。selectolax 只有寬鬆模式,沒有嚴格模式,也沒有 error log。

iterparse:selectolax 完全沒有的串流模式

這裡講的是能力,不是速度旋鈕。selectolax 只能一次吃進整段字串,沒有漸進式介面。lxml 的 iterparse 會在元素關閉時逐步吐出元素,搭配經典的 fast_iter 模式(一路 elem.clear(),並刪除前面的兄弟節點),不管文件有多大,記憶體都能維持平坦。

lxml iterparse streams 300K records with about 1-2 MB RSS

我直接量了記憶體特性——以 ru_maxrss 看峰值 RSS,每個 subject 都在全新的 process 裡執行,測的是 300,000 個 <record> 元素、總大小約 15 MB 的資料。

模式峰值 RSS 增量備註
iterparse + clear(fast_iter)約 1-2 MB邊解析邊釋放;與數量無關,幾乎維持平坦
iterparse 不 clear約 386 MB持有參照;和完整載入一樣重
etree.parse(完整載入,對照組)約 386 MB已知很吃記憶體;證明量測儀表有抓到規模差

有上限的模式把峰值 RSS 增量壓在大約 1-2 MB,而完整載入約是 386 MB——差距大約 0.3-0.4% 的量級;而且第一個 record 事件甚至在檔案還沒讀完前就先觸發了,所以這真的是漸進式串流,不是假串流。中間那一列最有教學意義。只要把同樣的 iterparse 迴圈跑一遍,但省略 clear(),記憶體就會一路回升到約 386 MB,因為你把每個東西都握在手裡。真正的勝利點在 clear(),不在 iterparse 本身。完整載入那個對照組也明確證明 RSS 量測儀真的有看見量級差距,而不是亂讀。(這項記憶體測試是我在這份 pack 裡新跑的——它是 footprint 測量,和沿用的時間數字不同。)

換成真實世界的語境:一份數 GB 的 XML 匯出,只要塞不進 RAM,selectolax 就完全沒路可走。你要嘛用 lxml 的串流 parser,要嘛就得換別的語言。

命名空間:RSS、SVG,以及預設命名空間陷阱

我測了十二個命名空間案例,涵蓋跨三個命名空間的 RSS、帶有預設命名空間和 xlink 的 SVG,以及預設命名空間 XML。十二個全部通過。

lxml 能從 RSS feed 中精準取出 //dc:creator/text(),結果就是 ["Alice", "Bob"];它也能在同一份文件裡跨三個不同命名空間解析 //atom:link/@href//content:encoded;在 SVG 的第二個命名空間中處理 //s:rect//s:use/@xlink:href;還能用 QName 拆解 Clark notation 的 {uri}local 名稱,並透過 nsmap 做內省。這些都是有維護、也有文件記載的行為,而且是 selectolax 完全碰不到的一個維度,因為 selectolax 只支援 HTML5,不處理任意 XML 命名空間。

但有一個文件上明寫、也最值得記住的陷阱:XPath 對預設命名空間沒有概念。把 //book 丟到一個宣告 xmlns="urn:..." 的文件裡,結果會是 0 筆命中——XPath 裡空白前綴是未定義的,這點 lxml 文件 說得很清楚。你必須綁一個人工前綴(例如 //c:book 搭配 namespaces={"c": "urn:..."},這樣就能找到全部三筆),或者退回 //*[local-name()='book'](也會找到三筆)。這不是 bug,而是 XPath 規格被忠實實作。只是每個人通常都會在這裡踩一次雷。

真實骯髒頁面:11 個實際抓取頁的還原度

合成測試很乾淨,真正的網路不是。這裡我沿用 selectolax pack 裡的 11 個實際截取頁面 fixture(以 2026-07-10 為準、唯讀),把 lxml.html 放上去實測。

Fixture大小連結數libxml2 修復錯誤數Strict XML
news_bbc.html398 KB2554ok
docs_mdn_array.html243 KB5080raised
wiki_scraping.html227 KB4600raised
gov_whitehouse.html289 KB1540raised
oldstyle_craigslist.html561 KB3510raised
forum_reddit.html129 KB3180raised
docs_python.html80 KB3412raised
ecommerce_books.html51 KB940raised
news_hackernews.html35 KB2290raised
ecommerce_webscraper_allinone.html16 KB350raised
spa_quotes_js.html6 KB50raised

這 11 份都能被 lxml.html 正常解析,而且連結、標題、圖片的數量都和 selectolax pack 裡沿用的 lxml 結果完全一致——交叉檢查為 true。這種一致性,才是真正告訴我「沿用同一來源是合理的 apples-to-apples 比較」,而不是兩組不同測量硬貼同一個標籤。

順帶一提,嚴格 XML parser 在這 11 份裡有 10 份直接丟例外。真實世界的網頁幾乎都不是格式完整的 XML,這正是 libxml2 的 HTML 修復模式存在的原因——就是為了吞掉這些壞資料。唯一的例外是 BBC News,因為它是用 Next.js 呈現,結構夠完整,所以連嚴格 XML 都能過。不是所有標成「HTML」的東西都一定需要修復模式。

還有一個很容易誤解的計數差異。在 docs_python.html 裡,//a[@href](屬性存在)算出 343,而 selectolax pack 裡的 if n.get("href")(值為真)只算出 341。多出來的兩個都是空的 href="" 連結。這是計數規則不同——屬性存在 vs. 屬性值非空——不是 lxml 行為不同;只要 predicate 對齊,數字就會一致。做爬取時這件事很重要:空 href 算不算,是你過濾條件的選擇,不是 parser 替你決定的。

看起來像 bug 的深度限制,其實不是

selectolax pack 曾記錄 lxml 在 1,000 層與 5,000 層巢狀 <div> 標記下會丟失最深層內容,並把它描述成「lxml 靜默遺失最深內容」。我想知道機制,所以拿預設 parser 跟 huge_tree=True 做了比較。

lxml default depth guard around 253 levels and huge_tree to 2045

要求深度預設 parser 到達huge_tree=True 到達
300253(後面都掉了)299(已修復)
1000253(後面都掉了)999(已修復)
5000253(後面都掉了)2045(仍然掉)

預設 parser 大約在 253 層就截斷,超過的內容會靜默丟失。這不是 bug,而是 libxml2 的 DoS 防護:一個約 256 層的巢狀深度上限,用來避免惡意文件把堆疊撐爆,這點在 lxml launchpad 討論串 關於 XML_PARSE_HUGE 的說明裡有寫。把 huge_tree=True 打開後,300 層和 1,000 層都能完整恢復。不過 5,000 層即使開了 huge_tree 也只到 2,045 層就卡住了——libxml2 其實還有第二道更硬的遞迴上限,而 huge_tree 解不了那一層。

所以實際建議很明確:如果你要解析來自可信來源、但層數很深的標記,請用 lxml.html.HTMLParser(huge_tree=True)。這份測試除了沿用前面的觀察之外,補上的就是機制說明(安全上限,不是資料損壞)、修正方式(huge_tree),以及還有一個它碰不到的第二道天花板。

DOM 讀寫、序列化與編碼

lxml 是完整可讀寫的樹,不是只能讀的擷取器;我也逐項驗證了編輯面。八項 DOM 操作全數通過:SubElementinsertremovereplacestrip_tags(移除標籤但保留文字)、strip_elements(連標籤與文字一起移除)、drop_treelxml.html 的獨有功能),以及常讓新手搞混的 text / tail 雙槽模型——在 <p>head<b>bold</b>tail</p> 裡,p.text"head"b.text"bold"b.tail 則是 "tail"

序列化則是五項全過:tostring 的 XML 與 HTML 模式(HTML 會正確保留 void elements,不會自己收尾)、pretty_print、C14N 標準化(method="c14n",也是 lxml 的獨有功能),以及可乾淨往返的 round-trip。

編碼處理是 lxml 默默把自己和別人拉開差距的地方。把非 UTF-8 位元組餵給它——例如把 "<p>café éè</p>".encode("latin-1") 丟進 lxml.html.fromstring——它仍能完整還原出 café éè,不會冒出 U+FFFD 替代字元,也不會丟掉位元組。這直接重現了它在 selectolax pack 裡被當成「乾淨參考值」的角色;同樣的輸入在另外兩個引擎裡會靜默損壞(Lexbor 產生替代字元,Modest 直接丟掉位元組)。在這一點上,lxml 背後的 libxml2 字元集偵測就是比較穩。

反過來說,它對你怎麼宣告編碼很嚴格。XML 宣告裡如果寫 encoding="latin-1",就會拋出 XMLSyntaxError: Unsupported encoding: latin-1;但如果寫 IANA 標準名稱 encoding="ISO-8859-1",就能正常解析並回傳 café。libxml2 只接受標準化編碼名稱,不接受別名——這個細節早就在 launchpad #613302 被記錄了。你不知道時會很煩;知道後就只是個小差異。

最後是節點生命週期。我在獨立 subprocess 中跑了三種 stale-handle 情境(如果有硬崩潰,會顯示為非零退出):樹被垃圾回收後還持有節點、執行 drop_tree() 之後再讀取 handle、以及執行 remove() 後再使用節點。三種情境都沒有 segfault——lxml 會保住節點對樹的參照,避免 use-after-free。這項測試上,selectolax 也拿到同樣乾淨的結果。

速度與記憶體(沿用,但誠實說明)

這一段全部都是沿用 selectolax pack 的數字,基準時間以 2026-07-13 為準。這份 pack 本身沒有產生任何 timing 數據,我寧可把這件事講兩次,也不希望你以為我有重跑。

面向lxml 數值解讀
純解析 p50(10 MB)77.9 ms比 selectolax-Lexbor 快約 33-34%
完整解析 + 擷取 p50(1 MB / 10 MB)14.18 ms / 172.9 ms小型資料時與 Lexbor 大致相當
10 萬節點 CSS 吞吐量3,002,646 nodes/s六個 C 引擎裡的第一梯隊
10 MB RSS 增量128.9 MB六種 parser 裡最省記憶體,比 BeautifulSoup 約省 1.7 倍
冷啟動 import14.1 ms比 parsel 風格的 import 快約 2.3 倍

純解析與吞吐數字都很漂亮,而 lxml 也是這六個 parser 裡記憶體最省的一個。不過執行緒那一欄要加註解。沿用資料顯示,4 執行緒的整體加速只有 1.21x,標記為 inconclusive——但那是共享預設 parser 的路徑。lxml FAQ 已明確說過:只有每個執行緒各自使用自己的 parser(或複製過的預設 parser)時,解析過程才會釋放 GIL;共享 parser 只會把存取序列化。我有從 API 層面驗證正確用法是否存在(XMLParser.copy()get/set_default_parser 都存在,XPathEvaluator 也有內部鎖),但我沒有量測「每執行緒各自 parser」的實際加速,因為那會產生新的 timing 測量,而這份 pack 不做那件事。所以請把「1.21x」理解成「在天真的共享路徑下」的結果,而不是 lxml 的執行緒上限。

另外,全段數據都還有一個星號:這些都是單平台數字,macOS arm64。至於「lxml 的純解析比 Lexbor 還快」這點,跟一般認知中 Lexbor 背後的 parser 通常最快的說法有衝突,所以在任何人把它當定論之前,都值得在 Linux x86_64 上再確認一次。

授權:最無聊、卻最實用的勝利

lxml 採用 BSD-3-Clause 授權,而它內建的 C 函式庫——libxml2 與 libxslt——都是 MIT 授權。這是一條完全寬鬆、沒有任何 copyleft 的鏈條,尤其當你要重新散佈時,差異就很大。對照來看,selectolax 的 wheel 內含 LGPL-2.1 的 Modest 與 Apache-2.0 的 Lexbor,所以如果你要放進閉源產品裡,lxml 的授權故事更單純。

實際安裝也有優勢:lxml 提供預編譯 wheel,而且會靜態連結 libxml2 與 libxslt,所以一般來說 pip install lxml 不需要你機器上另外裝 system libxml2,也不需要編譯器——這跟從原始碼自行編譯是完全不同的體驗。

lxml 的定位——以及 AI 擷取層什麼時候會接手

這裡需要把邊界講清楚,因為這很容易搞混。lxml 是解析函式庫。它給你一棵樹和很強的查詢引擎,而這棵樹周圍的事情仍然要你自己處理:抓網頁、渲染 JavaScript、繞過反爬、寫與維護 XPath、以及把結果結構化。這是一個不同層級,跟代管式擷取服務不是競爭關係,比較像鄰居。

對於那些不想自己扛 fetch-render-select-maintain 整套流程的開發者,上層工具就像 Thunderbit 這樣的服務——而對這類使用者來說,重點不是瀏覽器擴充套件,而是 API、MCP server 與 CLI。Thunderbit Open API 提供 POST /distill,可把頁面轉成乾淨的 Markdown;也提供 POST /extract,依 JSON Schema 擷取結構化資料,還能切換 renderMode,並支援批次任務。相同引擎也能以 MCP server 形式提供(thunderbit_suggest_fieldsthunderbit_distillthunderbit_extract),給 agent 和 coding assistant 使用;也可以用 CLI 從終端機直接執行,透過 npx @thunderbit/thunderbit-cli 即可。它內建 JS 渲染、反爬與 CAPTCHA 處理,並直接回傳符合 schema 的 JSON——這是解析之上的那一層,不是替代品。

使用 Thunderbit 進行網頁資料擷取

概念其實很簡單:如果你自己掌握整條 pipeline,而且希望對樹狀結構有精準的 XPath 控制,就用 lxml;如果你不想維護選擇器與渲染流程,就交給 AI 擷取 API。很多真實系統兩者都會用:結構清楚、可控的 feed 用 lxml;雜亂、長尾的頁面交給擷取服務。

這篇評測沒有測的東西

這是一篇暫定評測,不是最終總表,所以先說明它沒涵蓋什麼。

所有時間與記憶體數字都沿用,且都是單一平台(macOS arm64、Python 3.14),也承接那份 pack 的限制——「lxml 純解析比較快」這個結果,跟主流共識相反,所以需要在 Linux x86_64 再驗證。每執行緒各自 parser 的加速我沒測(那需要新的 timing)。我量了 30 萬筆 record 的 iterparse 記憶體,但沒有量 GB 級真實 XML,也沒有量 HTML 與 XML 之間的 iterparse 差異,更沒有做長時間 soak 測試。lxml 的 XSLT 1.0、RelaxNG / XMLSchema / DTD 驗證,以及 EXSLT 擴充,我在這裡完全沒測——這些都是大面積能力,但已超出本篇聚焦的解析與選取核心。我觀察到第二道深度上限在 2,045,但沒有釐清 libxml2 的精確遞迴常數。只測了穩定版 6.1.1,沒有測 7.0.0 alpha。Windows、原始碼編譯,以及 free-threaded 3.14t 版本也都沒測。而在 XPath 本身,我只涵蓋內建函式,沒有測 XPath 變數、自訂 Python extension functions,或預編譯 etree.XPath 物件重複使用的情況。

結論

lxml 不是最新、也不是最潮的工具,而這正是它值得推薦的原因。它是一個有二十年歷史的 libxml2 綁定,擁有主流 Python 替代品無法匹敵的完整 XPath 1.0 引擎、三段式可預期的解析嚴格度與中間的 error log、可處理大型文件的真正串流 parser、對多命名空間與編碼的正確支援,以及完全寬鬆的授權。少數幾個尖銳邊角——約 253 層的深度上限,以及共享 parser 的執行緒數字——都已文件化、可設定,而且現在也已說明清楚。

如果你掌握自己的爬取流程,而且很依賴 XPath,那 lxml 仍然是你會拿起來的 parser。若你不想維護選擇器與渲染,那就是 Thunderbit API、MCP 與 CLI 發揮作用的地方——分工清楚,不是互相取代。無論你選哪條路,這些數字都先當作暫定值,真正在設計文件裡引用之前,最好先在你自己的平台上重跑一次。

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

常見問題

lxml 是網頁爬蟲嗎? 不是。lxml 是解析器與序列化器——它是 libxml2/libxslt 的 Python 綁定,能把標記轉成可編輯、可查詢的樹狀結構。它不負責抓取頁面、不會渲染 JavaScript,也不處理反爬機制;你要自己提供請求層(例如 requestshttpx、無頭瀏覽器,或爬取服務),再把 bytes 交給 lxml。

什麼時候該用 lxml,而不是 BeautifulSoup 或 selectolax? 當你需要 XPath 時,就該選 lxml。BeautifulSoup 雖然可以用 lxml 當底層 parser,但本身沒有原生 XPath;selectolax 只支援 CSS,而且只在自己的狹窄場景裡更快。如果你的選取邏輯需要文字內容過濾、父層或祖先節點導覽、屬性或文字節點擷取,或是依數量做過濾,lxml 的 XPath 引擎是主流 Python 生態裡唯一能直接表達這些需求的選擇。

為什麼 lxml 會靜默丟掉太深的巢狀內容? 它的預設 parser 會把巢狀深度限制在約 253 層——這是 libxml2 為了防止惡意文件的 DoS 防禦,不是 bug。把 huge_tree=True 打開(例如 lxml.html.HTMLParser(huge_tree=True))後,就能完整恢復 300 層與 1,000 層的深度。不過要注意,還有第二道更硬的遞迴上限,大約在 2,045 層,huge_tree 無法解除。

lxml 在多執行緒解析時會釋放 GIL 嗎? 只有在條件正確時才會。lxml FAQ 說明,當每個執行緒使用自己的 parser,或使用複製過的預設 parser 時,解析過程會釋放 GIL;如果共用同一個 parser,則會把存取序列化。這份沿用資料中的 4 執行緒加速 1.21x,反映的是天真的共享 parser 路徑,不是每執行緒 parser 的上限;後者並沒有在這裡量測。

到了 2026 年,lxml 還有人維護嗎? 有。穩定版 6.1.1 已在 2026-05-18 發布,倉庫最後一次推送是 2026-07-02,而且 7.0.0 alpha 也在開發中。GitHub 約有 3,000 顆星,下層又有持續維護的 libxml2,所以它仍然是當代、且受到良好支援的函式庫,而不是老古董。

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

一鍵 中從任何頁面提取數據

全球超過 25 萬用戶信賴
提供免費方案
使用 AI 提取數據
輕鬆將數據傳輸到 Google Sheets、Airtable 或 Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week