在与 markitdown 共享的四个 HTML 样本上,markdownify 在我们的统计规则下输出的 Markdown 表格行数完全一致——都是 36 行——并且成功保留了全部 16 个检查过的内容字符串。它的输出为 21,062 个 token,名义上最低,不过前三个转换器的差距都在 1.3% 以内。安装体积仅 1.8 MiB,采用 MIT 许可证,快照时拥有 2,235 个 GitHub stars。
在这个类别里,它是讨论最少的库;如果只看这些数据,我会优先选它。
什么是 markdownify
markdownify 是一个基于 BeautifulSoup 的 Python HTML 转 Markdown 转换器。它的架构很简单:先用 BeautifulSoup 解析,再遍历 DOM 树,输出 Markdown。测试版本:1.2.3,MIT 许可,2,235 stars,42 个未关闭 issue,最近一次发布为 2026-06-30——维护活跃,共 44 次发布。
官方参考:python-markdownify 的官方仓库。

它的 API 其实就是一个函数:
from markdownify import markdownify
md = markdownify(html)
如果你想自定义元素处理,也可以使用类形式(MarkdownConverter);同时它还提供了不少实用选项,比如标题样式、项目符号字符、代码语言识别、元素剔除等。但大多数情况下,一行调用就够用了,而且确实能用。
执行 pip install markdownify 会拉取 5 个包 和 1.8 MiB,冷启动导入耗时 0.046 s——在我测试的三个转换器里最快。它的依赖树主要就是 BeautifulSoup 及其常见伴侣,而很多 Python 项目本来就已经依赖这些,所以边际成本通常几乎可以忽略。
测试方法
我使用了另一套基准中已经拿来测 markitdown 的四个 HTML 样本,并沿用那套基准里预先登记的探针字符串——其中 16 个是精确到正文层面的内容字符串,用于检查是否在转换过程中丢失,另外还包括页面外壳的基础检查。第五个样本 Nothing but tables 是一个独立的表格密集型诊断项,不计入下面的四样本汇总。
| 转换器 | 正文探针 | 输出字符数 | Token 数(o200k) | Markdown 表格行数 | 链接数 |
|---|---|---|---|---|---|
| markdownify | 16/16 | 76,868 | 21,062 | 36 | 599 |
| html2text | 16/16 | 76,452 | 21,176 | 32 | 545 |
| markitdown | 16/16 | 76,995 | 21,336 | 36 | 598 |
| turndown | 16/16 | 95,188 | 26,236 | 0 | 611 |
fourway-scores.json。四个样本,token 统一按 o200k_base 统计,表格行数则用同一套规则跨四个样本计算——包括对 markitdown 已保存输出的复核,结果与其公开数值完全一致。
有三点特别值得注意。
它在表格行输出数量上与 markitdown 持平。 四个共享样本都各自输出了 36 行,且使用的是同一套启发式统计规则。这并不意味着逐单元格完全相同;它只能说明,这两个输出在这套统计器眼里都呈现出了相同数量的可识别 Markdown 表格行。
它的 token 数最低。 21,062,略低于 html2text 的 21,176 和 markitdown 的 21,336,比 turndown 的 26,236 低了 19.7%。前三者彼此间差距都在 1.3% 以内,我更愿意把这视为平手,而不是胜出;真正有意义的差距是在与 turndown 的对比上。
所有内容探针都保住了。 16 个一个没丢。其他三个转换器也是如此——所以内容保留并不是这些库拉开差距的地方。
它在表格上做对了什么
冰球统计这个样本最能把转换器区分开。markdownify:
| Team Name | Year | Wins | Losses | OT Losses | Win % | ... |
| --- | --- | --- | --- | --- | --- | ... |
| Boston Bruins | 1990 | 44 | 24 | | 0.55 | ... |
它输出的是标准 GFM 表格,带首尾管道符、分隔行,而且——注意 24 和 0.55 之间的空单元格——它会把空单元格保留下来,而不是跳过。这看起来不起眼,但其实很关键:如果转换器把空单元格删掉,后面的每个值都会整体错一列,而输出表面上仍然像个正常表格。

turndown 在同样输入下,会把每个值都变成独立段落,完全没有列结构。html2text 也能生成正确表格,但风格不同,没有外层管道符。
如果 Markdown 最终要喂给模型,正是这些管道符和被保留的空单元格,才能让它回答“波士顿输了多少场”,而不是靠猜。
它比 turndown 少删掉的两件事
表格保真度是最显眼的差异,但还有一个更重要、却几乎没人提到的点。
markdownify 会剔除 <script> 和 <style> 的内容,而 turndown 不会。按只会出现在这些元素内部的标记来统计,markdownify 在所有样本里的输出中 script 标记为 0,style 标记也为 0;turndown 则分别有 10 和 84。在 Wikipedia 样本上,这就意味着 59,561 个字符对比 74,939 个字符——而且 MediaWiki 的 8 行内联 JavaScript 配置和 CSS,就占了其中 14,644 个字符,也就是这部分差距的 95%(script-style-stripping.json)。
对于要送进模型的内容来说,这大概是整个对比里最“昂贵”的垃圾:一段 JavaScript 配置会消耗 token,却不携带任何信息。markdownify 会默认帮你去掉,html2text 也是如此。
按单个样本看,markdownify 和 html2text 在简单表格上完全一致,但在复杂表格上会出现差异:
| 样本 | markdownify | html2text |
|---|---|---|
| 冰球统计 | 27 行 | 27 行 |
| Wikipedia | 9 行 | 5 行 |
| Nothing but tables(独立诊断) | 62 行 | 59 行 |
在两个诊断对比中,markdownify 识别出的表格行都更多。尤其是在 Wikipedia 样本上,这个统计器下它保留了 9 行,而 html2text 只有 5 行。这更像是在提醒你:在正式选型前,最好拿真实的嵌套表格和不规则表格先测一遍,而不是证明每个输出单元格都语义完全正确。
安装和许可证,放到替代方案旁边看
| 库 | 包数量 | 磁盘占用 | 冷启动导入 | 许可证 | Stars | 最近发布 |
|---|---|---|---|---|---|---|
| markdownify | 5 | 1.8 MiB | 0.046 s | MIT | 2,235 | 2026-06-30 |
| html2text | 1 | 0.2 MiB | 0.077 s | GPL-3.0-or-later | 2,168 | 2025-04-15 |
| turndown | 3(npm) | 8.8 MiB | 0.056 s | MIT | 11,386 | 2026-04-03 |
官方参考:PyPI 上的 markdownify。
install-and-import.json 和 metadata-snapshot.json。每个库都是在自己全新的空环境中完成安装的。
如果你的项目已经在用 BeautifulSoup,那么 markdownify 的 1.8 MiB 其实有点“名义上”的意味,因为大量 Python 抓取项目本来就已经带着它,这样一来 markdownify 的边际成本几乎为零。
markdownify 的发布节奏在这三者里是最健康的:共 44 次发布,测试前六周还有一次推送;相比之下,html2text 上一次发布还是 2025 年 4 月。
一行 Python 到底帮你做了什么
整个库就是 markdownify(html),而且我们有必要明确它替你做了哪些决定,因为这次对比里有三项恰好就是围绕这些决定展开的。
它使用 BeautifulSoup 解析,会自动移除 <script> 和 <style>,并输出带首尾管道符、保留空单元格的 GFM 风格表格。仅凭解析器出身并不能保证它面对坏输入时的表现,所以这个问题我们会在下面单独测。
这些都不是你要设置的选项,而是默认行为。在这个类别里,turndown 的默认值会保留 JavaScript,html2text 的默认值会强制按 78 字符换行;相比之下,一个开箱即用就不需要修补的库,本身就有价值。
当然,你需要时也可以启用更多选项——heading_style、bullets、code_language、strip、convert(用于元素白名单/黑名单),以及 MarkdownConverter(用于按元素覆盖默认转换逻辑)。这里测到的内容都没有启用这些选项。
内存,以及坏 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 的基线不能直接互相比较;两边都包含了解释器本身的开销。
markdownify 处在中间位置:导入底线 23.9 MiB,在 10 MB 文档上的峰值为 278.5 MiB。这是 html2text 峰值的 3.9 倍,也大约只有 turndown 的十分之一——而这正是它搭配 BeautifulSoup 所要付出的代价。
坏 HTML。 共 12 份文档,每份都只坏一个点——未闭合标签、错误嵌套的行内元素、带空格且未加引号的属性、孤立的闭合标签、完全没有 <html>、重复属性、在标签中间被截断的文档、错误实体、未闭合的 <script>、虚假的字符集声明、包含标记的注释,以及 600 层嵌套——另外还有 两份同尺寸的良好输入作为对照,因为“它什么都没返回”只有在同尺寸的干净文档上它也同样沉默时,才真的能说明坏输入处理能力。
markdownify 在 14 份中有 1 份抛错,0 份直接返回空结果,在坏输入样本上恢复了 33 个哨兵中的 30 个(malformed-results.json)。其中有一个样本不计入这个统计:按 HTML5 规则,未闭合 <script> 之后的所有内容都算 script 内容,所以在那里丢掉它是正确行为,恢复出来反而才是偏差。
优缺点
优点。 表格保真度与 markitdown 持平,而且是按同一规则统计的。四个样本里 token 最少。MIT 许可。仅 1.8 MiB;如果你的项目里已经有 BeautifulSoup,那么边际成本接近于零。冷启动导入最快,为 0.046 s。持续维护。能保留表格空单元格。可以通过 MarkdownConverter 按元素覆盖转换逻辑。默认的一行调用就是最合适的用法。
缺点。 依赖 BeautifulSoup,所以如果你本来没有它,磁盘占用会比 html2text 大 9 倍。2,235 个 stars 也意味着社区规模比 turndown 小——遇到怪问题时可参考的实战样例会少一些。只支持 Python,在 Node 技术栈里帮不上忙。还有 42 个未解决 issue。
谁适合用,谁不适合用
如果你做的是类似这些样本的 Python 工作负载,优先从 markdownify 开始。 它在这套统计器下与 markitdown 的表格行输出数量持平,处在 token 最低的那一档,而且是 MIT 许可。如果你本来就依赖 BeautifulSoup,它的边际安装体积可能很小;但最好还是在你的环境里实际确认一下依赖增量。
如果你对依赖体积的要求按几百 KB 来算,可以考虑 html2text。 只要它生成的输出结构适合你的页面就行。但在分发前,请先和负责许可证的人一起确认 GPL-3.0-or-later 的影响。
如果你本来就在处理 PDF 或 Office 文档,可以考虑 markitdown。 在这里它的 HTML 输出和 markdownify 的表格行数一样,但更大范围的保真度并没有证明两者等价。
如果你在 Node 环境里,就可以跳过它。 这时候 turndown 才是自然选择——前提是你同时安装 turndown-plugin-gfm,因为 turndown 核心本身不会生成表格。
托管 API 在哪里适合介入
markdownify 只能转换你已经拿到手的 HTML。它不会抓取网页,不会渲染 JavaScript,也不会处理反爬层——这四个转换器都不会,而在很多真实站点上,难点其实恰恰在前半段。
如果想看这五个转换器对同一组样本的表现,可以参考 五种 HTML 转 Markdown 对比。
我们在 Thunderbit 的开发者栈,解决的是上游那部分工作:POST /distill 会抓取一个 URL 并返回 Markdown,而 POST /extract 会返回符合 schema 的 JSON。两者都可以通过 MCP 服务器和 CLI 使用;价格见 Thunderbit 定价页。这些托管端点没有拿来和这里的本地转换器做基准测试,所以这不是性能对比,而是类别差异。
更诚实地说:如果你手里已经有 HTML,而且想要 Markdown,那么 markdownify 是免费的,并且在这里几乎已经把这件事做到最好。可如果你需要先抓取页面,或者你更想要的是结构化行数据而不是连贯文本,那就是另一类采购需求了。
如果你想继续了解更大的工具版图,可以看我们的 网页抓取 API 总览 了解托管方案,或看 开源抓取器总纲 了解自托管方案。Python 中将 HTML 转为 Markdown 则是更实用的操作指南。
要不要用 markdownify?
如果你在 Python 里工作,而且输入内容和这些样本相似,那它是个很强的候选;但在把它设为默认方案之前,最好先用你自己的表格和坏页面测试一下。
它在表格行输出数量上与 markitdown 持平,处在 token 最低的那一档,这轮测试里导入也最快,而且是 MIT 许可。相对来说,它比 html2text 更耗内存,只支持 Python,并且在一个坏输入样本上抛了异常。磁盘占用和许可证也是重要筛选条件,但不是唯一因素。
让我最意外的是 stars 数。turndown 的关注度是它的五倍,但开箱即用时,markdownify 能正确转换它完全做不好的表格。这个类别里的流行度,和实际测得的行为并不一致——也正因为如此,这次对比才值得跑。
试用 Thunderbit 进行网页数据提取 Get Started Free
常见问题
markdownify 在 HTML 处理上真的和 markitdown 一样好吗? 在这四个样本里,它们都输出了 36 个可识别的 Markdown 表格行,都保住了 16 个被检查的字符串,而且字符数只差 0.2%。但这并不是单元格级或渲染级的一致性测试。markitdown 还支持 PDF 和 Office 格式,所以这里的对比只针对测到的 HTML 输出。
它需要 BeautifulSoup 吗? 需要——它的解析器就是这个,而且 1.8 MiB 里大头也来自它。如果你的项目已经在用 BeautifulSoup,markdownify 的边际成本就很小。若没有,而且磁盘体积真的很重要,html2text 只占 0.2 MiB、只需一个包,但代价是 GPL-3.0-or-later 许可证。
它怎么处理空表格单元格? 会保留。对于数据有缺口的样本,它会把空单元格放回原位,因此后面的值仍然留在正确列里。如果转换器把空单元格跳过了,表面上表格还是完整的,但所有值都会整体左移一列;这才是更糟的失败,因为它不会发出任何提醒。
我应该用函数还是类?
普通场景用 markdownify() 函数就够了。只有当你需要覆盖某个特定元素的转换方式时,才用 MarkdownConverter——这里测到的所有内容都使用的是默认选项下的普通函数。
这里没有测什么? 四个共享样本加一个纯表格诊断样本,并不能代表完整语料库。单独的坏 HTML 套件覆盖了 12 种命名破坏和 2 个对照,但仍不是所有坏 HTML 的形式。嵌套列表、定义列表、脚注、数学公式、Markdown 与 HTML 的往返转换、单元格级表格一致性、以及不同配置变体,都没有比较。转换速度也没有纳入测试。


