2026 年的 BeautifulSoup:最親切的 HTML 解析器,也是最慢的那個(慢 12–17 倍)

最後更新於 July 17, 2026
2026 年的 BeautifulSoup:最親切的 HTML 解析器,也是最慢的那個(慢 12–17 倍)
AI 摘要
這篇評測以精準數據衡量 BeautifulSoup 作為 Python 中最容易上手的 HTML 解析器,並量化它的友善代價。文章從速度、壞 HTML 容錯、CSS 選擇器覆蓋率、物件保留與編碼還原等面向,將 bs4 與 C 語言後端的解析器做比較。結果顯示 BeautifulSoup 通常慢上 12 到 17 倍,但也解釋了開發者為何仍然選它:API 易讀、容錯寬鬆、soupsieve 選擇器支援完整,而且在處理雜亂的一次性抓取工作時很順手。這是一篇實用指南,幫助你判斷何時可以接受速度稅,何時該改用更快的解析器。

BeautifulSoup 幾乎是每個人在 Python 裡第一次抓網頁時都會先想到的函式庫,而它也確實是嚴肅 HTML 解析器中最慢的一個。這兩件事都是真的,而且都不是缺點。真正有意思的是,這個「慢」其實不是一種感覺,而是一個可以精準量化、也可以被拿來權衡成本的數字。

我把 bs4(也就是 beautifulsoup4,版本 4.15.0,於 2026 年 6 月釋出,採 MIT 授權)放進一套新的能力測試,加上同一組基準環境裡既有的計時資料,結果非常一致:你用大約一個數量級的速度代價,換來的是業界最友善的 API,以及最強的容錯能力。這筆交易值不值得,完全取決於你的工作負載,所以這篇評測會把兩邊都攤開來看。

BeautifulSoup 到底是什麼,又不是什麼

大多數教學都會跳過最重要的一點:BeautifulSoup 並不負責解析 HTML。它其實是一層包裝器。底層它會把文件交給三個真正的解析器之一——Python 內建的 html.parserlxml,或 html5lib`——然後把它們產生的樹結構包成一套單一、非常好用的導覽與搜尋 API。bs4 的工作不是解析,而是讓你更舒服地在結果裡走動、查找、抽取。

它的作者自己稱它為「螢幕抓取函式庫(screen-scraping library)」,訴求一直都很一致:你只要把它丟到那些連瀏覽器都會皺眉的破 HTML 上,它還是能把你要的資料抓出來。這個名聲是名副其實的,只是有一個小但重要的例外,等等會講。

先把幾個基本事實釘牢:

欄位
套件beautifulsoup4(匯入為 bs4
測試版本4.15.0(2026-06-07 上傳)
Python 要求>=3.7.0
授權MIT
官方首頁crummy.com/software/BeautifulSoup
原始碼 + Bug 追蹤Launchpad不是 GitHub
維護狀態持續活躍(2026 年 6 月的 4.15.0,過去一年有六次釋出)

「不是 GitHub」這一點比表面看起來更重要。bs4 是一個有 20 年歷史、運作在 crummy.com 和 Launchpad 上的函式庫,所以你不能用一般 GitHub 星數來判斷它的健康程度。比較合理的做法是看釋出頻率;照這個標準,它運作得很好。

再補一個授權上的細節,方便需要向法務或合規團隊交代的人:包裝器本身是 MIT,但你實際安裝 bs4 後,依賴樹裡會多出什麼,取決於你選哪個後端。html.parser 是 Python 標準庫的一部分(PSF 授權,沒有額外依賴)。lxml 是 BSD 授權,但它底下還有 libxml2/libxslt——這是外部 C 依賴,你要嘛自己編譯,要嘛裝預編譯 wheel。html5lib 是純 Python、MIT 授權。如果你想要最乾淨的依賴組合,內建的 html.parser 最省事——而巧的是,它也是最有問題的那個後端。後面就會看到。

速度稅到底有多重

先把數字放前面,因為這就是標題,而且刻意不講清楚是不誠實的。在一個接近真實情境的 parse 再 extract 任務裡——先解析字串,再抓出所有 <h3 class="title"> 和所有 <a href>——BeautifulSoup 在這組比較裡是最慢的,而且差距不小。

BeautifulSoup 速度稅:232 ms 對 15 ms 的 C 解析器

這些計時數據沿用自 selectolax 的基準環境(同一台機器、同樣 3 次測試方法,截至 2026-07-13);這篇評測沒有重新跑任何自己的時間基準,以避免 CPU 互相干擾,也避免重複計算。以下是 p50 中位延遲,單位毫秒:

頁面大小bs4 (html.parser)bs4 (lxml)selectolax-Lexborlxmlbs4-hp 慢多少bs4-lxml 慢多少
1 KB0.3230.2810.0270.03612.0x10.5x
10 KB2.0491.7050.1600.16612.8x10.7x
100 KB20.59916.5401.4641.42314.1x11.3x
1 MB232.558181.85514.90114.17715.6x12.2x
10 MB2788.7462261.557159.935172.93317.4x14.1x

所以 bs4(html.parser) 大約比 selectolax-Lexbor 這類 C 解析器慢 12–17 倍,而換成 lxml 後端後也只把差距拉回到 10.5–14 倍,仍然落後整整一個數量級。原因不是 bug,而是架構設計:不管底層解析器是誰,bs4 都會為每個節點建立完整的 Python 物件(TagNavigableString)。這一層物件化的成本,是 C 解析器根本不用付的稅。

你會發現倍數會隨頁面變大而上升——1 KB 是 12.0x,10 MB 則是 17.4x。這代表這不是可以被攤平的固定啟動成本,而是每個節點都要付一次的成本,而且節點越多,成本就越高。

再換個角度看,因為「慢 10 倍」聽起來通常比實際情況更可怕。1 MB 的頁面上,差距是 232 ms 對 15 ms。若你的工作是「抓幾百到幾千個、每個幾百 KB 的頁面」,這個絕對差異幾乎感覺不到——你不會有體感,也不會因為優化它而得到什麼。若你的工作是一個百萬頁級的流程,同樣的比例就會變成能不能跑完的關鍵差別。數字一樣,結論完全不同。判斷時要看你的實際量,而不是只看 benchmark。

不,換後端也救不了它

有個很常見的迷思是:只要把 bs4 指到 lxml 後端,就能得到 lxml 的速度。其實不行,而且這點值得講清楚。以一批 10 萬節點的 CSS 查詢為例(選出所有 <a> 並讀取 href,且樹已經先建好),吞吐量差距非常明顯:

解析器查詢 p50節點/秒
lxml33.30 ms3,002,646
selectolax-Modest34.19 ms2,924,550
selectolax-Lexbor39.46 ms2,534,027
bs4 (lxml)250.56 ms399,111

bs4(lxml) 每秒只能處理大約 39.9 萬個節點,仍比三個 C 引擎慢 6.3–7.5 倍,即使它的底層後端本來就是 lxml。原因在於:後端只加速了樹的「建立」,但查詢與遍歷仍然會經過 soupsieve 與 bs4 的 Tag 物件,而且每個匹配到的節點都還要再被 Python 包裝一次。所以「給 bs4 lxml,它就會跟 lxml 一樣快」這種直覺是錯的:後端只加速了一個階段,而最慢的那個階段根本不是那一段。

記憶體與冷啟動也補上了成本。對一份 10 MB 文件來說,bs4 的常駐記憶體大約是 selectolax 或 lxml 的 1.5–1.75 倍(218–226 MB 對 129–145 MB)——根本原因還是一樣:每個節點都得有一個 Python 物件。另外,匯入 bs4 大約要 33.4 ms,而 lxml.html 只要 14.1 ms,所以前者的匯入速度慢了 2.36 倍。對長時間運行的程式來說,這是小數點後的差異;但如果你做的是 CLI 工具,或是經常冷啟動的 serverless function,這就是一個值得知道的真實成本。

為什麼多執行緒救不了它

如果你對 CPU 密集型慢工作負責人的第一反應是「開執行緒硬上」,bs4 會讓你失望。以一個 1 MB 頁面重複解析 48 次為例,單執行緒與四執行緒的結果如下:

解析器1 執行緒4 執行緒加速比
selectolax-Lexbor0.563 s0.159 s3.54x
lxml0.459 s0.378 s1.21x
bs4 (lxml)6.945 s26.842 s0.26x

請把最下面那一列再看一次。四個執行緒讓 bs4 慢了大約 3.9 倍,不是更快。實驗訊號很明確:它大概率持有 GIL。bs4 的樹建立完全是 Python 寫的,所以會被 Global Interpreter Lock 序列化;把執行緒堆上去,只會在一個根本無法真正平行執行的工作上增加排程開銷。selectolax 能拿到約 3.5 倍的加速,是因為它的 C 核心會釋放鎖;bs4 沒這個空間。

面對未來的 free-threading 版本,實務上的結論是:如果你真的需要平行化 BeautifulSoup,請用多行程(ProcessPoolExecutor),不要用執行緒。selectolax 和 lxml 可以吃到執行緒層級的並行,bs4 不行。這裡也補一個嚴謹性上的說明——這只是 4 個執行緒、1 MB 頁面的一次觀察,而「持有 GIL」這個機制,是根據 wall-clock 行為推斷出來的假設,不是我逐行 instrument 後確認的結論。方向很明確,但精確機制仍是推論。

預設後端是陷阱。先看這段

如果你只從這篇評測記住一件事,那就記這個。單純寫 BeautifulSoup(html)、不傳第二個參數時,預設會用 html.parser,而 html.parser 不會處理 HTML5 的可選結尾標籤規則。這句話聽起來很學術,直到它悄悄把你的資料弄壞。

BeautifulSoup 後端容錯矩陣:html.parser 12/15,lxml 與 html5lib 15/15

我把 15 個刻意做壞的 HTML 範例丟給三個後端測試,而且每個案例在跑之前都先預先定義了與後端無關的結構性斷言(也就是說,不存在事後挑贏家)。結果如下:

後端符合預期 / 15
lxml15
html5lib15
html.parser12

這三個失敗案例都有同一個根源。拿一個沒關閉的表格來說:<table><tr><td>a<td>b<tr><td>c<td>d</table>。在 html.parser 下,抽出的儲存格文字會變成 ['abcd','bcd','cd','d']——每個 <td> 都把後面的內容一起吞掉,因為解析器把儲存格巢狀包起來了,而不是把它關閉。lxml 和 html5lib 則正確回傳 ['a','b','c','d']。純 <li> 也一樣:<li>a<li>b<li>c 在 html.parser 下會變成巢狀的 ['abc','bc','c'],另外兩個後端則是乾淨的 ['a','b','c']。重複屬性也會翻轉——<div id="first" id="second"> 在 html.parser 下保留的是 "second",而 lxml/html5lib 保留 "first";HTML5 規範要求保留第一個。

這危險的地方不只是煩人,而是它不會丟錯誤。一個隨手寫 BeautifulSoup(html) 的爬蟲,只要碰到未關閉的表格或清單——這在老網站、手寫 HTML、以及漏了關閉標籤的模板裡其實很常見——就可能把相鄰欄位文字黏在一起,給你髒資料,整個過程卻完全不吭聲。修正方式只要多傳一個參數:BeautifulSoup(html, "lxml")BeautifulSoup(html, "html5lib")

公平地說,html.parser 也不是一無是處;另外 15 個裡有 12 個做壞的樣本,在三個後端上結果都一樣。像 <b><i></b></i> 這種錯誤巢狀、缺少 html/body 骨架、未加引號的屬性、孤立的結尾標籤、未關閉的註解、巢狀表單、大小寫混用等等,bs4 的容錯其實都很強。真正的分歧幾乎都集中在可選結尾標籤這一類。這也不是什麼新發現——bs4 自己的「不同解析器差異」文件早就用白話寫明了 html.parser 比較「沒那麼寬容」。而這次的壞 HTML 矩陣,多補的是那些具體、可重現、會讓「沒那麼寬容」變成錯誤輸出的案例。

你不會失去什麼:API 與 CSS 才是它最強的地方

所以 bs4 慢、單執行緒,而且預設後端有坑。那為什麼大家還是照樣用?因為它「親切」的那一半是真的,而且實測下來確實成立。

BeautifulSoup 的 soupsieve CSS 覆蓋率勝出,41/41 加 20/20

我做了 29 個 API 探針,涵蓋搜尋、CSS、樹狀導覽、文字抽取與 DOM 修改。29 個全數通過,而且每個結果都是拿實際回傳值跟預期值做比對,而不是憑肉眼判定。這些能力裡,有兩個是 C 解析器通常不提供的易用性優勢:

  • find / find_all 支援函式條件。 你可以寫 soup.find(lambda t: t.name == "a" and "btn" in t.get("class", [])),用一行 Python 就表達很複雜的條件,不需要先抓全部再自己過濾。
  • 有名稱、雙向的樹狀導覽。 .parent.next_sibling.find_parent.stripped_strings.descendants——這些走訪方式看起來就像人話,而且前後都能走。有些在 selectolax 要多做幾步,甚至根本沒有。

這就是它「幫你省開發時間」的那一半,而且是具體可驗證的,不是行銷話術:29 個綠燈。

也要順手提醒兩個容易踩的坑,因為公平的評測要把兩面都講清楚。第一個是布林屬性:<input disabled> 在 bs4 裡會把 disabled 回傳成空字串 ""(selectolax 則回 None)。兩者都算 falsy,所以如果你寫 if node.get("disabled"),其實會默默漏掉這個真的存在的布林屬性。安全的判斷是 "disabled" in tag.attrs。第二個是 get_text(strip=True):它在去除空白後會直接把節點文字黏起來,不會自動加分隔符,所以 "...with " 加上 "link1" 會變成 "withlink1"。當你需要詞與詞之間的邊界時,請傳 separator=" "。這兩個坑都不是 bs4 專屬,但都是真實存在的跨庫陷阱。

再來是很多人會意外的一點:選 bs4 並不代表你得放棄 CSS 覆蓋率。它的 CSS 引擎 soupsieve 是這整組比較裡最完整的實作。在 41 個基礎案例的矩陣上(沿用自 selectolax 的基準環境),soupsieve 拿到 41/41,是全場唯一滿分,領先 selectolax-Lexbor 的 39/41,以及 cssselect(lxml/parsel)的 37/41。之後我又跑了 20 個 soupsieve 文件中宣稱支援的延伸案例,它也拿到 20/20,包括 Lexbor 直接拒絕的選擇器::lang(en)、soupsieve 專有的 :-soup-contains('featured'):is():where()、還有 :has(> a)。真正缺的只有 XPath(soupsieve 只支援 CSS),以及 parsel 的 ::text / ::attr() 偽元素,那是 Scrapy 的擴充。如果你平常都靠 XPath,這種遷移會很痛。

這一節的結論很乾淨:選 BeautifulSoup 你犧牲的是速度,不是 API 易用性,也不是 CSS 覆蓋率。

兩個上線後值得預留預算的坑

除了預設後端之外,還有兩個行為會在長時間運行或非 UTF-8 工作負載裡咬你一口。

參考循環:長迴圈裡要呼叫 decompose()

每個 bs4 的 Tag 物件都同時持有對父節點的參考,以及對子節點的參考,這就形成了參考循環。CPython 的 reference counting 沒辦法自己回收循環物件;那是世代式垃圾回收器的工作。為了看這件事到底多重要,我在關閉 GC 的情況下建立並刪除一棵樹 300 次,然後數記憶體中還活著的 Tag 物件:

BeautifulSoup 的參考循環在關閉 GC 時殘留 120,900 個物件,開啟 GC 時為 0

情境del 後仍保留的 Tag 數量
關閉 GC120,900(300 次循環,完全沒回收)
開啟 GC26,598(世代式 GC 在迴圈中途觸發)
強制執行 gc.collect()0(全部回收)
無循環對照組(字串列表,GC 關閉)delta 0

在關閉 GC 的情況下,del soup 什麼也沒回收——全部 120,900 個物件都還留在記憶體裡,因為參考循環讓 reference counting 失效了。只要手動跑一次 gc.collect(),就會全部清掉。無循環的對照組(單純字串列表,已知不會形成循環)則是零差異,這證明堆積真的來自 bs4 的循環,而不是測量雜訊。bs4 自己的文件也說這些物件「彼此高度交織……正是垃圾回收器最頭痛的那種」,所以這本來就是文件中寫得明白的行為;而這個測試補上的,是殘留物件數,以及 collect() 可以把它清成 0 的證明。

實務規則很簡單:如果你的流程會在緊密迴圈裡解析很多大頁面,而且你的程式(或某些高吞吐設定)把 GC 關掉了,或者 GC 觸發不夠頻繁,bs4 樹就會滯留,記憶體也會一路往上漲。每抓完一頁就呼叫 soup.decompose()——bs4 之所以提供這個方法,就是為了提早打斷循環並回收資源。selectolax 和 lxml 的 C 樹沒有這個問題。

編碼:UnicodeDammit 是 bs4 悄悄的優勢

bs4 有一個快速解析器沒有的元件:UnicodeDammit。它會偵測文件編碼並自動轉成 Unicode。我給它一組 8 種「宣告編碼 vs 實際編碼」的案例:

BeautifulSoup 的 UnicodeDammit 成功還原 8 個編碼案例中的 5 個

案例真實編碼UnicodeDammit 猜測是否還原成功?
utf8_no_declutf-8utf-8
utf16_bomutf-16utf-16le
gbk_chinesegbkgb18030是(超集)
shiftjisshift_jiscp932是(超集)
latin1_declared_utf8latin-1(宣告為 utf-8)iso-8859-1是(忽略了謊言)
latin1_no_decllatin-1cp720
cp1252_no_declcp1252cp862
utf8_declared_latin1utf-8(宣告為 latin-1)iso-8859-1否(照著謊言走)

8 個案例裡有 5 個成功還原。UTF-8、帶 BOM 的 UTF-16、GBK、Shift-JIS,甚至被錯標的 latin-1 都抓對了,而且那些「超集」猜測(GBK→gb18030、Shift-JIS→cp932)仍然能正常解碼。失敗的兩種情況也值得知道:很短的 latin-1/cp1252 位元組樣本,會被誤判成 DOS code page,因為統計式偵測器對短輸入不夠可靠,而且 DOS 方框字元和 Latin-1 的碼點又會重疊;另外,當 <meta charset> 的宣告本身就是錯的時候,UnicodeDammit 會相信那個宣告。bs4 的文件也有提醒:樣本可能「短到 Unicode Dammit 也抓不準」,而資料越多,判斷通常越好。

跟 selectolax 相比,後者對非 UTF-8 位元組不會幫你修正,而是默默弄壞,要求你自己先解碼;因此 bs4 至少會嘗試偵測,而且常常成功。只是這不是保證。對已知編碼的資料,別猜,直接明講:BeautifulSoup(bytes, from_encoding="...")

在真實頁面上,這些後端真的會不同嗎?

壞 HTML 矩陣已經證明,刻意做壞的輸入會讓後端分歧。下一個很自然的問題是:這在真實世界有沒有影響?所以我拿 11 個實際抓下來的頁面——BBC、Wikipedia、Craigslist、MDN、old.reddit、Python docs、Hacker News、Books to Scrape、webscraper.io、whitehouse.gov,以及一個 JS 渲染的 quotes 頁面——分別用三個後端去比較連結、標題與圖片數量。

結果是:11 個頁面全部一致,完全沒有分歧。這代表前面那個陷阱區看到的後端差異,只會出現在刻意做壞的 HTML 上;當現代生產網站的結構夠好——即使有點亂——後端選擇並不會改變你抽到的內容。實務解讀就是:對主流、結構正常的網站來說,html.parser 完全夠用,也能省掉依賴。只有在你抓的是明顯不標準、手寫、或年代久遠的 HTML 時,後端選擇才會開始影響結果,這時才該切到 lxml 或 html5lib。

那次測試還有一個有意思的邊界案例。MDN 頁面裡有一個 <template> 元素,而所有 bs4 後端都回傳了 508 個連結——也就是說,bs4 會把 <template> 裡的內容扁平化到主樹中。這讓 bs4 跟 lxml 站在同一邊,跟 selectolax-Lexbor 則相反;後者嚴格遵守 HTML5 規範(<template> 是 inert 的 DocumentFragment),所以只回傳 497 個連結,把 template 裡的 11 個連結靜默忽略了。因此 bs4 會抓到 <template> 裡的資料——這很實用,但也可能讓你撈到瀏覽器根本不會渲染的「幽靈內容」。兩種做法都不能算錯,只是對規範的解讀不同,你要知道自己拿到的是哪一種。

BeautifulSoup 適合放在哪裡,又不適合放在哪裡

與其把這一切硬壓成一個 0–100 分,不如直接看各維度的成績單,並在每一列附上一個提醒:

維度測試結果讀者提醒
安裝 / 首次執行純包裝器,不需要瀏覽器或額外設定;html.parser 零依賴;都有預編譯 wheellxml 後端需要 C 依賴
速度 vs C 解析器慢 12–17 倍(html.parser)/ 10.5–14 倍(lxml 後端),所有大小都一樣單一基準環境;沿用 selectolax 資料
CSS 查詢吞吐量在 10 萬節點上約慢 6–7.5 倍;lxml 後端也救不了沿用資料;要付 Python Tag 稅
記憶體比 selectolax/lxml 高 1.5–1.75 倍;最重沿用資料;以 RSS 測量
匯入冷啟動慢 2.36 倍(33.4 vs 14.1 ms)沿用資料;屬小項目
執行緒擴展性bs4-lxml 在 4 執行緒下反而慢約 3.9 倍(持有 GIL)單次觀察;請用多行程
API 易用性29/29 探針通過;find 可用函式條件 + 雙向導覽布林屬性空字串與 strip 的詞邊界陷阱要注意
CSS 覆蓋率soupsieve 最強:41/41 基礎 + 20/20 延伸;支援 :lang沒有 XPath,也沒有 ::text
三後端容錯lxml/html5lib 15/15;html.parser 12/15只在壞 HTML 上分歧
真實頁面一致性三後端在 11/11 頁面上都一致;都會扁平化 <template>(508)對結構正常的網站來說,後端影響不大
參考循環 GC樹是循環結構;300 次迴圈留住 120,900 個物件,collect() 後歸零長迴圈需要 decompose()
編碼UnicodeDammit 可還原 8 個案例中的 5 個;短樣本容易誤判,也會照著錯誤宣告走單次觀察
維護狀態活躍(2026 年 6 月的 4.15.0);MIT主站在 crummy/Launchpad,不在 GitHub

那麼 BeautifulSoup 適合誰?適合那些重視可讀 API、以及願意用容錯解析換取開發效率的人,尤其是中等規模的工作:原型、一次性抓取、內部工具、或是開發時間比執行時間更昂貴的團隊。誰該找別的工具?百萬頁級流程,因為速度稅會累積成真金白銀;需要執行緒層級平行化的工作;以及任何深度依賴 XPath 的人。

最後補一點它在真實爬蟲堆疊中的位置,以及我們自己的工具在其中扮演什麼角色。BeautifulSoup 的前提是:你已經有 HTML 了。它不負責抓頁、不負責渲染 JavaScript,也不處理反爬或 CAPTCHA——那是另一個層級的工作,而在現代網路上,這其實才是最難的一段。這也是 AI 抓取 API 會出現在另一層的原因:Thunderbit 的開發者堆疊——REST API、MCP server 與 CLI——負責抓取、JavaScript 渲染,以及反爬問題,然後直接回傳乾淨的 Markdown(POST /distill)或符合 schema 的結構化 JSON(POST /extract),你甚至不用自己寫 selector。兩者不是競爭關係,而是互補。bs4 負責解析你已經拿到的 HTML;Thunderbit 的 API、MCP 與 CLI 則幫你拿到一開始不容易碰到的 HTML。如果你的瓶頸在解析,bs4 是很好的答案;如果瓶頸在取得資料,那就是另一個層次的問題了。

試用 Thunderbit 進行網頁資料擷取

最後結論

BeautifulSoup 提供了這組比較裡最親切的 API、對壞 HTML 最強的容錯能力,以及最完整的 CSS 引擎——代價則是大約一個數量級的速度稅,外加最重的記憶體占用。這就是整筆交易,講白了就是這樣。真正的陷阱是預設的 html.parser:它會悄悄把未關閉的表格和清單弄亂,所以只要輸入可能很髒,就請明確傳入 "lxml""html5lib"。執行緒不會讓它更快——多行程才會。長時間迴圈裡,請對每一頁呼叫 decompose(),避免參考循環一路累積。

最後補兩個限制。這裡的所有測試都只在單一平台上完成(macOS arm64、Python 3.14、預編譯 wheel),而且時間倍數是沿用 selectolax 基準環境的資料(同一組 benchmark,截至 2026-07-13),不是重新跑的,所以也繼承了單平台限制;若換成 Linux x86_64 或自行編譯原始碼的環境,數字可能會有些變動。再來,這些結果裡沒有任何「新發現」:bs4 本來就是一個 20 年歷史的函式庫,因此每個被測試的行為不是文件寫過,就是公開紀錄過。這篇文章的價值不是爆料,而是把文件只會定性描述的取捨,變成可量化的實際數字。

常見問題

BeautifulSoup 很慢嗎? 是,而且可以量化。在 parse 加 extract 的任務上,它用預設的 html.parser 後端,速度大約比 selectolax-Lexbor 這種 C 解析器慢 12–17 倍;換成 lxml 後端則慢 10.5–14 倍,因為它會為每個節點建立 Python 物件。這有沒有影響,要看規模:1 MB 的頁面是 232 ms 對 15 ms,處理幾千頁幾乎沒差,但百萬頁流程就很關鍵。

BeautifulSoup 應該用哪個解析器:html.parser、lxml 還是 html5lib? 如果是結構正常、主流的網站,預設的 html.parser 就夠了,而且沒有額外依賴。但它不支援 HTML5 的可選結尾標籤,所以遇到未關閉的表格或清單時,會悄悄把相鄰文字黏在一起,而且不會報錯。當你的輸入可能是壞掉的、手寫的、或很老的 HTML,請明確傳入 "lxml""html5lib"——在壞 HTML 測試矩陣上,這兩者都拿到 15/15,而 html.parser 只有 12/15。

BeautifulSoup 能用執行緒平行解析嗎? 不能。bs4 的樹建立是純 Python,而且會持有 GIL,所以加執行緒不會更快,只會更慢——測試中,四個執行緒解析 1 MB 頁面,比單執行緒慢了約 3.9 倍。若要平行化 bs4,請用多行程(ProcessPoolExecutor)。像 selectolax 和 lxml 這種 C 核心的庫,才有執行緒層級的平行收益。

BeautifulSoup 對壞 HTML 的處理好嗎? 整體來說是好的——在一系列刻意做壞的樣本(錯誤巢狀、缺少骨架、未加引號的屬性等等)上,三個後端大多都能正確恢復。唯一明顯的弱點是預設的 html.parser 與可選結尾標籤:未關閉的 <td> / <li> 會被巢狀包起來而不是關閉,導致抽出的文字被污染。切到 lxmlhtml5lib 後,這類問題就會消失。

BeautifulSoup 跟 lxml 比,哪個比較好? 它們是不同工具。lxml 在樹建立與查詢上都快得多,也支援 XPath。BeautifulSoup 則把 lxml(以及其他後端)包成更友善的 API,而且透過 soupsieve,CSS 覆蓋率其實更完整。只是不要期待把後端設成 lxml 就能讓 bs4 變成 lxml 的速度——後端只加速解析,查詢與遍歷仍然要付 bs4 每節點 Python 物件的成本,所以在大型批次選取上還是會慢大約 6–7.5 倍。

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

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