html2text 只要安裝 一個套件,體積只有 0.2 MiB——比最接近的 Python 替代方案小九倍,也比 Node 方案小四十倍。它順利跑完轉換測試套件中的四個頁面,並保留了全部 16 個已註冊的 body 探針。這些探針只檢查特定 body 字串有沒有被保留下來;它們不評分層級、清單巢狀、連結目的地、重複內容,或完整表格還原度。
另外,它的授權是 GPL-3.0-or-later,這是本次比較裡唯一不會反映在任何基準測試中、卻足以讓某個函式庫直接出局的特性。
html2text 是什麼
html2text 是一個 Python 函式庫,可以把 HTML 轉成接近 Markdown 風格的純文字。它的源頭可追溯到 Aaron Swartz 的原始版本,目前仍在維護的版本線是 2025.4.15——一個 2025 年 4 月的日期版釋出,GitHub 星數 2,168,95 個未解決議題,最近一次推送是在 2025 年 10 月。
官方參考:html2text 的官方倉庫。
import html2text
h = html2text.HTML2Text()
h.body_width = 0 # 下面會說明;預設值會讓你意外
md = h.handle(html)
執行 pip install html2text 只會拉下 1 個套件、0.2 MiB,冷啟動匯入時間為 0.077 秒。沒有任何相依套件。若放在容器映像或 Lambda layer 裡,和 markdownify 的 1.8 MiB、turndown 的 8.8 MiB 相比,差距非常明顯。
會改變所有數字的預設值

body_width 的預設值是 78。除非你把它關掉,html2text 會把輸出每一行都硬生生在 78 個字元處換行。
對原本用來產生終端機與電子郵件可讀純文字的函式庫來說,這樣的預設其實合理;但如果你要把結果丟進模型或拿來做 diff,這就不太對了。因為插入換行會改變 token 化方式、把長連結切成多行,也會讓輸出比對失去意義。
下面的內容我都先把 body_width = 0,而且我把這件事直接標出來,不藏著:如果開啟自動換行,本文中的每個字元數與 token 數都會不同。若你自己要做轉換器基準測試,這個設定會悄悄讓你的數字無法互相比較。
測量方式
我把 html2text 跑在一組命名好的四頁轉換測試集上,這套測試也用於 markitdown 的比較;同時搭配 預先註冊的探針字串——也就是必須保留下來的 body 字串,以及能證明頁面框架有被帶進來的 boilerplate 字串。四個檔案、同一組探針、四個轉換器共用一個評分器。探針存活只是在看註冊字串有沒有存在,並不代表結構正確;表格與連結欄位分開看,正是因為這個原因。
| 轉換器 | Body 探針 | 輸出字元數 | Token 數(o200k) | Markdown 表格列數 | 連結數 |
|---|---|---|---|---|---|
| html2text | 16/16 | 76,452 | 21,176 | 32 | 545 |
| markdownify | 16/16 | 76,868 | 21,062 | 36 | 599 |
| markitdown | 16/16 | 76,995 | 21,336 | 36 | 598 |
| turndown | 16/16 | 95,188 | 26,236 | 0 | 611 |
fourway-scores.json。四個樣本,Token 以 o200k_base 計算。
四者之中輸出最小,只有 76,452 個字元,Token 數也幾乎跟 markdownify 和 markitdown 持平——21,176 對 21,062 與 21,336,差距 1.3%,我不會把這叫做明顯差異。
在四頁測試集中,它的 32 個表格列數,少於 markdownify 與 markitdown 的 36。這四列落差出現在不規則的 Wikipedia 樣本上,而不是下一節提到的外層管線格式差異。
保留的連結最少,只有 545 個,其他工具則是 598 到 611 個。如果你很在意連結保留,這點值得拿自己的頁面再驗證一次——在這個欄位上,html2text 明顯低於其他工具,不是跟它們差不多。
連 regex 都沒看懂的表格

這段很值得講,因為我差點把結論寫錯。
我一開始的表格列計數器要求同時有開頭和結尾的管線符號——^\|.*\|$。照這個規則,html2text 在五個檔案中只得到 1 列表格:也就是四頁轉換測試集,加上一個額外的合成複雜表格樣本。那個計數器測到的是一種 Markdown 風格,不是真正的表格。

它其實會輸出像這樣:
Team Name | Year | Wins | Losses | Win %
---|---|---|---|---
Boston Bruins | 1990 | 44 | 24 | 0.55
沒有外層管線。這是很常見的 pipe-table 語法,但測試框架沒有對不同 Markdown renderer 做一致性驗證。若你的 regex 是以外層管線格式為前提,它就會看不到這類表格。把計數器改成尋找一串含管線的列,並在中間確認有分隔列後,html2text 從 1 列變成了 四頁測試集中的 32 列,以及 五個檔案合計 91 列。
所以結論不是 html2text 沒有表格,而是 這四個轉換器裡有兩個會輸出外層管線、而它不是。如果你後續還要用自己的模式比對 Markdown,這件事會直接影響結果。這是很重要的資訊,如果我只相信第一個數字,根本會錯過。
它會刪掉什麼,以及在哪裡會少列
有兩個方向相反的發現。
它會去掉 <script> 和 <style>。 以只會出現在這些元素內的標記來計算,html2text 在所有樣本中的輸出都帶有 0 個 script 標記、0 個 style 標記。turndown 則帶有 10 個 script 標記 和 84 個 style 標記——在 Wikipedia 樣本中,這些是 MediaWiki 內嵌 JavaScript 設定與 CSS,共計 14,644 個字元(script-style-stripping.json)。對要送進模型的輸出來說,這是該樣本中最大、而且其實可以避免的多餘文字來源。這裡沒有量化下游成本。
它在難頁面上會少掉表格列。 四頁測試集的對照如下:
| 樣本 | html2text | markdownify |
|---|---|---|
| Books to Scrape | 0 列 | 0 列 |
| Quotes to Scrape | 0 列 | 0 列 |
| Hockey statistics | 27 列 | 27 列 |
| Wikipedia | 5 列 | 9 列 |
| 四頁總計 | 32 列 | 36 列 |
另外那個合成複雜表格樣本,html2text 產生 59 列,markdownify 產生 62 列,讓五個檔案合計成 91 與 98。這不算四頁主比較。它們在乾淨的 hockey 表格上表現一致;但在像 Wikipedia 這種巢狀又不規則的表格上,html2text 只輸出五列,而 markdownify 有九列。
所以模式很清楚:簡單表格,兩者相同;棘手表格,html2text 保留得較少。如果你的頁面像 Wikipedia 那樣帶有複雜表格,先測試再決定。如果你的頁面比較像統計頁,這兩者在這個面向上幾乎可以互換。
授權
| 函式庫 | 授權 | 套件數 | 磁碟用量 |
|---|---|---|---|
| html2text | GPL-3.0-or-later | 1 | 0.2 MiB |
| markdownify | MIT | 5 | 1.8 MiB |
| turndown | MIT | 3(npm) | 8.8 MiB |
官方參考:PyPI 上的 html2text。
這一點我從三個地方都確認過:PyPI 中繼資料、GitHub 倉庫,以及已安裝套件自己的 METADATA 檔,裡面寫著 License-Expression: GPL-3.0-or-later。
這代表什麼,要看軟體是怎麼整合、傳遞和分發的。單純內部使用,或只透過網路提供服務,通常和直接出貨、且包含或結合該套件的 GPL 情境不同;但這篇文章不是法律分析。若團隊要分發軟體,應由法務針對實際整合與分發模式進行審閱。
麻煩的地方在於這個關聯:這個函式庫的體積最小,原本正是因為你想讓可分發成品盡量小才會選它;但偏偏它也是最限制分發的授權。兩個替代方案都是 MIT。
我不是律師,這也不是法律意見——只是這個事實,而且有來源,因為它最可能影響決策,卻最不容易出現在比較表裡。
維護狀況
最近一次釋出是 2025.4.15,最近一次倉庫推送是 2025 年 10 月——距離測試時間大約十個月,背後累積了 41 次 release。requires_python >= 3.9,而且它在 Python 3.14.2 上安裝與執行都很正常。
這比 markdownify(測試前六週才有最近一次釋出)和 turndown(四個月前)更安靜,但也遠比完全沒更新要好。對一個負責把 HTML 轉成文字的函式庫來說,十個月的空窗比較像穩定,而不是棄置。95 個未解決議題才是更值得注意的訊號,建議你在正式採用前先掃一遍,看看有沒有和你的使用情境接近的問題。
記憶體,以及損壞 HTML 會如何影響它
這裡分別測量兩個營運層面的問題。
更完整的壓力測試背景,可參考 十個函式庫的記憶體與錯誤 HTML 比較。
常駐記憶體峰值 透過 /usr/bin/time -l 測得,每個欄位都啟動一個全新程序——匯入底線代表函式庫載入後待命的成本,峰值則包含整份文件。
| 函式庫 | 執行環境 | 匯入底線 | 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 的基準彼此不能直接相比,因為兩者都內含解譯器。
html2text 在這兩個量測維度上都是最輕量的項目。 它的 18.7 MiB 匯入底線,以及在 10 MiB 樣本上的 71.2 MiB 峰值,換算出的增量 RSS 是 52.5 MiB:(71.2 - 18.7) / 10 = 5.25× 樣本大小。請把表格中的絕對值與增量一起看,並記得前面提到的 Python/Node 基準不能直接相比這個前提。
損壞 HTML。 12 份文件,每份都只壞一件事——未關閉標籤、巢狀錯誤的行內元素、含空格卻沒加引號的屬性、多餘的結尾標籤、完全沒有 <html>、重複屬性、在標籤中間截斷的文件、錯誤實體、未關閉的 <script>、錯誤聲稱的 charset、含有標記的註解,以及 600 層巢狀——再加上 兩份同尺寸的正常控制組,因為「它什麼都沒輸出」只有在它對同大小的正常文件也一樣沉默時,才能說明它真的不處理損壞狀況。
html2text 在 14 份中的 0 份拋出例外,也在 0 份輸出空白,並在所有損壞樣本中恢復了 33/33 個 sentinel(malformed-results.json)。其中有一個樣本不計入這個統計:依照 HTML5 規範,未關閉 <script> 之後的內容本來就都屬於 script 內容,因此在那裡遺失是正確行為,若把它保留下來反而才是偏差。
優點與缺點
優點。 一個套件、0.2 MiB、零相依——是這份比較裡最小的。四者中輸出最少,且與 markdownify、markitdown 的 token 數幾乎相同。會輸出可辨識的 pipe-table 語法。可在 Python 3.14 上運作。脈絡悠久且穩定。
缺點。 GPL-3.0-or-later,而其他替代方案都不是。預設 body_width=78,會強制換行,若不關掉就會改變測量與 diff。保留的連結最少(545 對 598–611)。表格採用無外層管線的格式,會讓天真的下游 regex 失效。95 個未解決議題,而且釋出節奏比 markdownify 更安靜。
誰適合用,誰不適合
適合用 html2text 的情況,是你的相依預算真的很緊,且預期的整合與分發模式已經通過授權審查。只有一個套件、又沒有任何相依套件,這在實務上確實是優勢:待審查與部署的依賴面更小。
第一行就把 body_width = 0 設好,除非你真的想要換行過的純文字。
如果你要分發軟體,而 copyleft 會成問題,就不要選它。 markdownify 是 MIT,在這裡 token 數也差不多,而且只多 1.6 MiB 就能在表格上與 markitdown 打平。若你很在意連結保留,也應該跳過它,因為它保留的連結最少。若你的下游工具假設表格列一定有外層管線,也不該選它。
托管 API 的定位
html2text 是在你已經拿到 HTML 之後才做轉換。它不會抓取頁面、不會執行 JavaScript,也不會處理反機器人層——四個轉換器都不會,而在很多真實目標網站上,這其實才是更難的一半。
若要看五個轉換器在相同樣本上的表現,可參考 五種 HTML 轉 Markdown 工具比較。
像我們自己的 Thunderbit 這類託管式抓取/渲染/擷取服務,則是在不同層級運作。這裡沒有把 Thunderbit 納入測試。真正的分界,是「你已經有 HTML,想把它轉成 Markdown」和「由服務去取得 URL 並處理內容」之間;本文並沒有提供同一指標下的品質、延遲或成本比較。
公平地說,如果你手上已經有 HTML,想要 Markdown,而且你的發佈方式不受 GPL 影響,那 html2text 是免費而且極小的選擇。如果你是要抓頁面,或你想要的是結構化資料而不是敘述文字,那就是另一個問題了。
更完整的市場概覽可參考我們的 web scraping API 總整理 與 開源爬蟲主題頁,而 在 Python 中將 HTML 轉成 Markdown 則是實作導覽。
要不要使用 html2text?
如果你很在意體積、刻意關掉換行,而且你的分發模式已通過授權審查,那它是一個很強的候選方案。
在四頁測試集中,它的 token 數與 markdownify 和 markitdown 相差不到 1.3%。但這不代表整體品質一樣:html2text 保留的連結較少,在不規則的 Wikipedia 樣本上保留的表格列也較少。兩個立刻要注意的營運細節是:body_width = 0,以及不含外層管線的表格格式。
如果 GPL 審查把它排除,markdownify 是 MIT,在這裡的輸出大小與 token 數都差不多,保留更多連結與不規則表格列,而且在這個環境中只多用 1.6 MiB 磁碟空間。
試用 Thunderbit 進行網頁資料擷取 Get Started Free
常見問題
html2text 會轉表格嗎?
會。我最初的計數器說它在五個檔案中只產生 1 列表格,但那個計數器是錯的——它要求必須有開頭和結尾管線,而 html2text 會輸出像 Team Name | Year | Wins 這樣的格式,並不一定帶外層管線。這是常見的 pipe-table Markdown,但這個測試框架沒有做跨 renderer 相容性測試。用修正後的計數器來看,html2text 在四頁測試集中產生 32 列,markdownify 是 36 列;若把那個額外的複雜表格樣本也算進來,則是 91 列對 98 列。
body_width 是做什麼的,為什麼要改?
它預設會在 78 個字元處強制換行,這對終端機可讀的純文字是合理選擇,但對其他用途就不太好。換行會在句子中間插入新行、把長網址拆開,並改變 token 化方式。這篇評測中的所有數字都使用 body_width = 0;若用預設值,結果都會不同。
GPL 授權真的會構成限制嗎?
要看你實際的整合與分發模式。內部使用、僅網路服務,以及隨軟體一併散佈,這些都屬於不同的判斷情境,但本文無法替你判定法律結果。若要出貨,請讓法務審閱 GPL-3.0-or-later 的條款;markdownify 與 turndown 則是 MIT。html2text 的授權表述已在 PyPI 中繼資料、GitHub 與已安裝套件的 METADATA 檔中確認。
2025 年 4 月的釋出版本會有問題嗎? 單看這點,大概不算。HTML 轉文字本來就是一個很穩定的問題,而且這個函式庫在 Python 3.14.2 上安裝與執行都正常,背後還有 41 次釋出。真正值得看的反而是 95 個未解決議題——在正式採用前,先掃一下是否有像你輸入資料的案例,因為倉庫越安靜,通常代表你可能得自己修。
這裡沒有測到什麼?
四個轉換樣本,加上一個額外的複雜表格樣本,規模仍然不大。測試確實涵蓋了 12 個人工合成的損壞文件與 2 個控制組:html2text 在 14/14 沒有拋錯、14/14 沒有回傳空值,並恢復了全部 33 個計分 sentinel。不過它沒有涵蓋真實世界損壞頁面、更廣泛的錯誤模式、巢狀清單、定義清單、註腳或數學式。完整的選項面——ignore_links、ignore_images、unicode_snob、single_line_break 以及其他設定——除了 body_width 之外都維持預設。連結落差有被觀察到,但沒有進一步診斷;Markdown 的 round-trip 也沒有測試。


