html2text 只需安装 1 个包,占用 0.2 MiB——比最近的 Python 替代方案小 9 倍,比 Node 方案小 40 倍。它在转换套件的四个页面上全部完成,并保留了全部 16 个已登记的正文探针。这里的探针只检查指定正文字符串是否能保留下来;它们并不评估层级结构、列表嵌套、链接目标、重复内容,或完整表格还原度。
它的许可还是 GPL-3.0-or-later,而这一点恰恰是这次对比里唯一不会体现在任何 benchmark 中、却足以直接让一个库出局的属性。
什么是 html2text
html2text 是一个将 HTML 转成接近 Markdown 的纯文本的 Python 库。它的源头可以追溯到 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 数都会变。要是你自己在做转换器 benchmark,这个设置会悄悄让你的结果无法互相比较。
测量方式
我在一个命名的四页转换套件上运行了 html2text,这套材料也用于 markitdown 的对比;同时使用了预注册探针字符串——也就是必须保留下来的正文字符串,以及能证明页面外壳被带出来的模板字符串。四个文件一致,探针一致,四个转换器共用同一套评分标准。探针存活只衡量注册字符串是否还在,不衡量结构正确性;表格和链接列之所以单独列出,正是因为这个原因。
| 转换器 | 正文探针 | 输出字符数 | 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 行。这个 4 行的差距出现在那个不规则的 Wikipedia 样本上,而不是后面会提到的外层竖线格式差异。
链接数最少,只有 545 个,而其他工具在 598 到 611 之间。如果链接保留对你很重要,这一列值得特别留意——这是 html2text 明显低于整体组别,而不是与它们打平的地方。
代码里的正则看不到的表格

这一点值得单独说,因为我差点把错误结论直接发出去。
我最初的表格行计数器要求行首行尾都必须有竖线:^\|.*\|$。按这个规则,html2text 在五个文件里只拿到 1 行表格:四页转换套件,再加一个单独的合成复杂表格样本。那个计数器测到的是一种 Markdown 写法,而不是表格本身。

它确实会输出表格,格式大致如下:
Team Name | Year | Wins | Losses | Win %
---|---|---|---|---
Boston Bruins | 1990 | 44 | 24 | 0.55
没有最外层竖线。这是标准的 pipe-table 语法,只是这个测试框架没有验证它在不同 Markdown 渲染器中的兼容性。对于一个期待外层竖线样式的正则来说,它就像不存在一样。把计数逻辑改成寻找“包含竖线的连续行 + 中间有分隔行”的模式后,html2text 在四页套件里从 1 行变成了 32 行,在全部五个文件里变成了 91 行。
所以结论不是 html2text 不会输出表格,而是 这四个转换器里有两个会输出外层竖线,另一个不会。如果你会在 Markdown 结果上继续做自己的模式匹配,这一点就很关键。我如果一开始只相信那组数字,完全会漏掉这个事实。
它会删掉什么,又会在哪些地方少掉表格行
有两条方向相反、但都很重要的发现。
它会剥离 <script> 和 <style>。 根据只会出现在这些元素里的标记统计,html2text 在这些样本上的输出里 没有任何 script 标记,也没有任何 style 标记。而 turndown 输出里分别有 10 个和 84 个——在 Wikipedia 样本中,它保留了 8 行 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 行 |
单独的合成复杂表格样本再补充了 59 行 html2text 结果和 62 行 markdownify 结果,把五个文件的总数变成 91 和 98。它不属于前面那个“四页主对比”。在干净的 hockey 表格上,它们完全一致;而在结构复杂、嵌套混乱的 Wikipedia 表格上,html2text 只输出 5 行,markdownify 输出 9 行。
所以模式很清楚:简单表格,两者一样;难表格,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 月——距离测试大约有 10 个月,前面已经累计了 41 个版本。requires_python >= 3.9,而且它在 Python 3.14.2 上安装和运行都很顺利。
相比 markdownify(测试前 6 周刚发布)和 turndown(4 个月前发布),它的更新节奏要安静得多,但也比完全没有维护要活跃得多。对于一个把 HTML 转成文本的库来说,这类问题本来就不太会剧烈变化,10 个月的间隔更像是稳定,而不是失活。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 个哨兵项 (malformed-results.json)。其中有一个样本被排除在统计之外:根据 HTML5 规范,未闭合 <script> 后面的内容都属于脚本内容,所以在那里丢失它们是正确行为,恢复出来反而才算偏差。
优缺点
优点。 只要 1 个包、0.2 MiB、无依赖——在这次对比里远小于其他工具。四个方案里输出最小,token 数也几乎与 markdownify 和 markitdown 持平。会输出可识别的 pipe-table 语法。支持 Python 3.14。传承时间长,而且稳定。
缺点。 GPL-3.0-or-later,而其他方案不是这个许可。默认 body_width=78,会强制折行,除非关闭,否则会改变输出测量或 diff。保留下来的链接最少(545 个,对比组是 598–611 个)。表格采用不带外层竖线的写法,容易让下游用朴素正则处理时出错。还有 95 个开放问题,发布节奏也比 markdownify 更安静。
谁适合用,谁不适合
适合用 html2text 的情况是:依赖预算真的很紧,而且你的集成与分发模式已经通过了许可审查。只要 1 个包、没有依赖,这在运维上就是实打实的优势:需要审计和部署的依赖面更小。
第一行就把 body_width = 0,除非你就是想要折行后的纯文本。
不建议用 的情况是:你要分发软件,而 copyleft 许可会带来麻烦——markdownify 是 MIT,token 表现与它差不多,而且表格表现和 markitdown 持平,只不过在这里磁盘占用多 1.6 MiB。若你的下游非常依赖链接保留,也不建议用,因为它保留的链接最少。还有,如果你的后续工具默认假设表格行有外层竖线,也别选它。
托管 API 在哪里适合介入
html2text 只负责转换你已经拿到的 HTML。它不会抓取页面、不会执行 JavaScript,也不会处理反爬层——这四个转换器都不会,而在很多真实目标网站上,难的恰恰是前一半。
如果你想看同一批样本在五个转换器上的表现,可以参考:五种 HTML 转 Markdown 方案对比。
像我们自己的 Thunderbit 这样的托管抓取/渲染/提取服务,工作在完全不同的层级。本文没有对 Thunderbit 做基准测试。这里的边界是“拿到 HTML 后做转换”与“给一个 URL 再由服务去抓取并处理”之间的区别;本文不提供它们在质量、延迟或成本上的同口径对比。
更公平的说法是:如果你已经拿到了 HTML,只想要 Markdown,而且你的分发方式不受 GPL 限制,那么 html2text 免费、而且小得惊人。如果你要抓页面,或者你更需要的是结构化行数据而不是散文,那就是另一类需求了。
关于更广阔的工具生态,我们的 网页抓取 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,只是这个测试框架没有做跨渲染器兼容性验证。用修正后的计数方式,html2text 在四页套件里输出 32 行,而 markdownify 是 36 行;把单独的复杂表格样本算进去后,分别是 91 行和 98 行。
body_width 是做什么的,为什么要改它?
它默认会把输出硬性折到 78 个字符,这对终端可读的纯文本来说很合理,但对其他场景就不友好了。折行会在句子中间插入换行,把长 URL 拆成多行,还会改变 token 切分。本文的所有数字都使用了 body_width = 0;如果用默认值,结果都会不同。
GPL 许可真的会构成限制吗?
这取决于具体的集成和分发方式。内部使用、仅网络调用、以及分发软件,属于不同的筛查场景,但本文不判断它们的法律结果。要分发软件的团队,应让法务审查 GPL-3.0-or-later 条款;markdownify 和 turndown 都是 MIT。html2text 的 license expression 已在 PyPI 元数据、GitHub 和已安装包的 METADATA 文件中确认。
2025 年 4 月发布会是问题吗? 单看这一点,可能不会。HTML 转文本本来就是一个相对稳定的问题,这个库在 Python 3.14.2 上安装和运行都很顺利,而且背后还有 41 个历史版本。真正值得看的数字是 95 个开放问题——在决定采用前先扫一遍,看看有没有与你输入场景相似的情况,因为一个安静的仓库,往往意味着以后要靠你自己修。
这里没有测试什么?
四个转换样本和一个额外的复杂表格样本,终究还是一个小套件。测试确实覆盖了 12 个合成畸形文档和 2 个控制样本:html2text 在 14/14 中没有抛错,14/14 中没有返回空结果,并恢复了全部 33 个计分哨兵。它没有覆盖真实世界中损坏的页面、更广泛的畸形模式、嵌套列表、定义列表、脚注或数学公式。完整的选项面板——ignore_links、ignore_images、unicode_snob、single_line_break 等等——除了 body_width 之外都保持默认值。链接缺口被观察到了,但没有进一步归因;Markdown 往返转换也没有测试。


