MarkItDown 经常被归到网页爬虫一类,但这个分类其实不对。它没有爬虫程序,没有 JavaScript 引擎,也不能直接抓取 URL 并清理页面中的冗余内容。它真正做的事情是:把你手头已经有的二进制文件——PDF、Word 文档、电子表格、幻灯片等——转换成适合语言模型阅读的 Markdown。
我花了两周时间,在一台 Mac 上把 Microsoft 的 MarkItDown 放到一组真实文档里反复测试:事先为每个表格写好清单,对照每一次转换结果逐项评分,同时记录转换耗时。先说结论:在干净输入上,它速度快、保真度高;但它的安装包里藏着一个你并不一定需要的 73 MB 机器学习运行时;而且它在表格处理上会出现一些问题——这些问题会让“文本有没有保住”这类检查通过,却让“数据有没有落在正确列里”这类检查失败。下面我会把所有数据和判断完整展开。
MarkItDown 到底是什么
MarkItDown 是 Microsoft 推出的一个 Python 工具,用来把文件和 Office 文档转换成适合 LLM 使用的 Markdown。你可以把它指向 PDF、.docx、.xlsx、.pptx、图片、HTML 文件,或者其他一些格式,它就会返回 Markdown。它支持三种调用方式:命令行(markitdown file.pdf -o out.md,也可以通过 stdin 管道输入)、Python API(MarkItDown().convert(...)),以及一个可选的 MCP 服务器,用于 agent 工作流。

最关键的一点是它不会做什么;这也是我在测试里确认过的:它不会爬取网页,不会渲染 JS,不会跟进链接,不会翻页,也不会像 readability 工具那样提取正文。它是一个“整份文档转换器”。你把文件内容交给它,它负责把格式统一。这个区别决定了它是否适合进入你的技术栈,所以我后面会反复提到这一点。
从 GitHub 的“名气指标”来看,这个仓库相当重:截至 2026 年 7 月中旬有 165,282 个 stars 和 11,790 个 forks,采用 MIT 许可,最新版本(v0.1.6)发布于 2026-05-26。不过,这个 star 数更多反映的是 Microsoft 账号和 LLM 工具链热度,并不能直接说明其转换核心已经非常成熟。仓库里还有 833 个 open issues,其中有几条在你安装之前就值得关注(下面会说)。
HTML 转 Markdown:速度快、内容完整,但会把页面杂项也带上
因为我这个系列里其他爬虫评测都使用同样的四个网页样本,所以我也把 MarkItDown 喂给了相同的本地 HTML 文件——不是把它当爬虫来评测,而是想看看它的 HTML 转 Markdown 效果如何。对结构良好的页面来说,它确实表现不错。
四个页面都能在核心安装下直接转换,不需要额外组件,而且正文内容的检测点全部保住了。Wikipedia 的 “Web scraping” 条目(226 KB)转换后,标题层级也被正确保留:1 个 h1、7 个 h2、12 个 h3,和原文结构一致,并且 418 个链接都以规范的 [text](url) 形式保留。Scrape This Site forms 页面 上那个 26×9 的冰球数据表,也被整理成一个干净的 27 行 GFM pipe table(标题行 + 分隔行 + 26 行数据),连空单元格都保留了。速度也没什么问题:从小的 quotes 页面中位数 48 ms,到 226 KB 的 Wikipedia 页面中位数 352 ms。
但这里有个关键点,而且这是设计选择,不是 bug。MarkItDown 不会去掉页面的 boilerplate。它会直接转换整个 <body>,所以站点外壳也会一起进入输出——页面越“花哨”,残留越多。
| 页面 | 输出字符数 | 标题(h1/h2/h3) | 链接数 | 站点外壳行占比 |
|---|---|---|---|---|
| Books to Scrape | 10,478 | 1 / 0 / 0 | 94 | 0.6%(1/159) |
| Quotes to Scrape | 2,973 | 1 / 1 / 0 | 55 | 1.2%(1/86) |
| ScrapeThisSite forms | 3,385 | 1 / 0 / 0 | 31 | 6.7%(5/75) |
| Wikipedia Web scraping | 60,159 | 1 / 7 / 12 | 418 | 12.4%(42/338) |
在几乎没有页面外壳的 Books 首页里,输出行只有 0.6% 是站点杂项。到了 Wikipedia,这个比例变成 12.4%——338 行非空内容里,有 42 行是“跳转到内容”、“切换目录”、“22 种语言”、“取自……”,还有 cookie 和许可证页脚。Wikipedia 上那些维护提示(比如“本文需要更多引用”)也会被忠实地转换成两列表格,所以一张本来没有真实数据表的页面,最后会冒出 9 行表格数据。
这不是 MarkItDown 做错了,而是它的职责就不是正文提取器:忠实地把 HTML 转成 Markdown,和像 readability 那样只抽正文,原本就是两件不同的事。Trafilatura 和 Firecrawl 这类工具的目标,是只返回主体内容;MarkItDown 返回的是整页内容。它底层在 _html_converter.py 里先剥掉 <script> 和 <style>,然后把整个 body 交给 markdownify 库处理——路径里根本没有正文启发式提取逻辑。如果你只想要文章正文,那它就不是这一层该用的工具。
它真正的主场:PDF、DOCX、XLSX、PPTX
文档才是 MarkItDown 的本职工作。我拿真实公开文件做了测试:一篇带文本层的 arXiv 论文、Bitcoin 白皮书、一份没有文本层的纯扫描 PDF(我特意渲染出来)、以及 MarkItDown 自带测试套件中的 DOCX/XLSX/PPTX 文件(里面塞了 UUID,用来发现有没有悄悄丢内容)。
| 文档 | 输入大小 | 输出字符数 | 检测点 | 中位耗时 | 备注 |
|---|---|---|---|---|---|
| arXiv 1706.03762(文本层 PDF) | 2.2 MB | 40,174 | 7/7 | 3.7 s(warm) | 标题、“Transformer”、“BLEU”、“References”均保留 |
| Bitcoin 白皮书(9 页 PDF) | 184 KB | 22,485 | 6/6 | 1.4 s | “Satoshi Nakamoto”、“proof-of-work”、“Conclusion”均保留 |
| 扫描版 PDF(无文本层) | 89 KB | 0 | 0/4 | 15 ms | 输出为空,无报错,无 OCR |
| DOCX(test.docx) | 136 KB | 4,651 | — | 70 ms | 标题 + GFM 表格;嵌入 UUID 完整保留 |
| 带公式的 DOCX | 15 KB | 240 | — | 101 ms | Office Math 被保留为 LaTeX |
| XLSX(test.xlsx) | 12 KB | 808 | — | 57 ms | 每个工作表都会输出为 ## SheetName + GFM 表格 |
| PPTX(test.pptx) | 278 KB | 2,047 | — | 52 ms | 保留幻灯片编号标记、表格,图表也会转成表格 |
在带文本层的 PDF 上,文本召回非常好——arXiv 的 “Attention Is All You Need” 论文 7 个预设检测点全中,Bitcoin 白皮书 也是 6/6;而且在维护者自己的 Office 回归样本里,没有任何一个 UUID 标记丢失,也就说明没有悄悄发生内容损失。还有一个很小但很实用的优点:DOCX 路径(通过 mammoth)会把 Office Math 公式保留成 LaTeX,把 equations.docx 转成真正的 $$...$$ 数学公式。如果你要把数学内容比较多的 Word 文档喂给 LLM,这确实是一个具体而实在的优势,我在别处很少看到有人提到。
不过,这个领域里有两个发现尤其值得注意,因为它们最可能坑到你。
扫描版 PDF 会直接“消失”
如果你喂给 MarkItDown 一个没有文本层的图片型 PDF,它会返回空字符串。0 个字符,不报错,不提醒——大约 15 ms 就处理完了,因为根本没东西可提取。MarkItDown 的 PDF 路径只做文本抽取(底层用的是 pdfminer 和 pdfplumber),核心安装包和任何 pip extra 里都不包含 OCR。
这在批量处理时很要命。如果开发者拿一个 PDF 文件夹来跑,里面有些是扫描件,那么这些文件会悄无声息地变成空结果,而且没有任何信号提示它们被跳过了。我还直接用 pdfminer 的 extract_text 去验证这个样本确实不是文件坏掉了——结果是 0 字符,确实没有文本层,所以空输出就是 MarkItDown 面对真实扫描件时的真实行为。这个现象对应的是一个长期存在的 OCR 回退缺口(#1268),上游已经追踪很久了。文档里提到的可行路径是可选的 Azure Document Intelligence 后端或插件;默认安装都不带。
PDF 输出是“平铺文本”,没有结构
在这两份带文本层的 PDF 里,MarkItDown 都没有输出任何 Markdown 标题标记。PDF 本身不带语义化标题标签,而 MarkItDown 也不会根据字体大小去猜标题,所以每一行都只会落在正文层级。文本召回很高,但结构是平的。
这不仅是我的测试结果。第三方公开基准给 MarkItDown 的 PDF 标题层级得分大约只有 0.0,表格保真度也只有约 0.27,明显低于依赖 Docling TableFormer 的 0.88(可以参考 MarkItDown vs Docling vs Marker 对比 以及 READoc 基准)。我的样本复现了这些结果,这说明证据是相互印证的——我的数字和外部来源一致。那些基准同时也给出一个权衡:MarkItDown 的速度大约比 Docling 快 100 倍,这和我在几秒内完成、而结构模型工具要跑几分钟的结果是吻合的。结论很简单:MarkItDown 能给你干净、快速的 PDF 文本,但不能给你 PDF 的 结构。如果标题和表格必须完整保留,那就应该用像 Docling 或 Marker 这样的结构模型工具。
表格:内容通常在,但结构不一定
表格是“文本有没有保住”和“数据能不能用”分道扬镳的地方。所以我专门做了一个 13 种情况的矩阵——每个 case 一个 <table>,并且都按运行前写好的清单打分——用来精确看哪些结构能稳住,哪些会出问题。

先说结论:MarkItDown 从来没有丢过表格内容。13 个 case 里,预注册的 token 都 100% 保住了。但结构保真度就分成了三类。13 个里有 7 个产出了格式良好的 GFM 网格表(普通表、带表头 colspan 的表、24 列超宽表、无表头表、空单元格表、单元格内嵌块级内容表,以及从右到左的阿拉伯语表)。有 4 个变得参差不齐,因为 Markdown 本身不支持跨行跨列单元格,所以 rowspan、colspan 和格式错误的源表都会导致短行。还有 2 个是彻底坏掉的。
这两个“坏掉”的 case 值得单独点名。嵌套表格(<td> 里再套一个 <table>)会被直接拍扁并内联展开,把自己的管道符和分隔行塞进父单元格里,最后生成一行 14“列”的垃圾数据。还有一种情况是,单元格里的字面量 | 没有被转义——单元格文本 a | b 会被拆成两列,x || y 会变成三列——于是一个两列表格最后会输出两列、三列、四列不等的行,下游任何 Markdown 解析器都会读错边界。奇怪的是,单元格里的星号和反引号都会被转义,只有管道符不会。根因在于 MarkItDown 的 HTML 路径使用的是 markdownify 的默认表格处理,而它自定义的子类只重写了链接、图片和标题,没有重写表格单元格。CSV 转换器里也有一个同类的管道符转义 bug,相关问题是 #2019,不过那个修复并不会影响我这里测试的 HTML 路径。
还有一个更隐蔽、也是我最想让数据工程师看到的点:rowspan。case t03 不只是变得参差不齐,而是会悄悄把数据列错位。一个 rowspan=2 的标签(“Fruit”)只会输出一次,下面那一行就会变成一个短短的两列表(| Banana | 8 |),于是“Banana”会落到 Group 列,而不是 Item 列。每个 token 都在,但如果你用一个很朴素的“取第二列”消费方式,就会拿到错误值。这类 bug 会通过文本保留检查,却会悄悄污染数据集。
这种跨度限制本身也是一个已知且被追踪的设计约束(#1211、#1248)——平面的 GFM pipe 表本来就无法表达跨行跨列或嵌套结构,所以转换器实际上是在用结构能力换取内容完整性。不过它也不是没有好行为:无表头表会自动补一个空表头行(这样数据不会被悄悄提升成表头),空单元格会被保留,<caption> 也会作为表格上方的一行文字保留下来。
安装与启动:这个“轻量工具”没提醒你的隐性成本
这一部分是最让我意外的地方,也正是“轻量级 Python 工具”这个说法最容易夸大的地方。

第一,不要运行 pip install 'markitdown[all]'。在 Python 3.14 上,它会悄悄回退到 markitdown 0.0.2——一个两年前的版本——我在一个干净的虚拟环境里现场复现过。之所以会这样,是因为如果你明确指定 pip install 'markitdown[all]==0.1.6',安装会报错:[all] 这个 extra 里把 youtube-transcript-api~=1.0.0 锁死了,而在当前 PyPI 上,这个范围内的所有构建都被限制在 Python <3.14,只有能兼容 3.14 的版本又不在这个约束范围内。所以解析器只能一路回退,直到找到最后一个能满足依赖的发布版。这和上游一个开放中的 issue #2179 是一致的。解决办法很直接:锁定版本,并分别安装需要的 extras:pip install 'markitdown==0.1.6',然后再装 pip install 'markitdown[pdf,docx,pptx,xlsx,xls]==0.1.6'。这些单独安装都能正常解析,只有合并在 [all] 里的那个包有问题。(这个陷阱和 Python 版本有关——在 Python 3.13 或更早版本上,这个限制未必会触发,所以 [all] 的解析结果可能不同。)
第二,看体积。核心安装就已经有 161 MB 了(一个 13 MB 的空虚拟环境,加上 148 MB 的依赖)。其中 onnxruntime 占 73 MB,numpy 占 34 MB,这两项加起来就是 107 MB,也就是整个核心体积的 66%;而它们之所以会被装进来,只是因为一个硬依赖:magika,一个 Google 的机器学习文件类型检测器。也就是说,一个文本转换器在基础安装里就自带了 73 MB 的 ONNX 推理运行时,哪怕你还没加任何文档额外依赖。把文档 extras 装上后,虚拟环境会涨到 310 MB。这比无头浏览器栈轻得多,但如果你原本以为这是一个“pip install 一下就完事”的微型工具,那要知道它其实还带着 ONNX runtime。
第三,也是我这一整套测试里唯一一个通过所有“新颖性检查”的发现:即使是干净安装,import markitdown 在这台机器上也要花大约 3.35 秒。这个成本几乎全都发生在导入阶段:markitdown._markitdown 会提前导入整个转换器注册表(总计 2.56 秒,占 76%),这又会把 pandas(594 ms,通过 XLSX 转换器)、python-pptx(427 ms)、magika(354 ms)和 requests(270 ms)都带进来——不管你今天到底会不会转换这些格式。对于长时间运行的服务来说,这点导入成本会被摊平,几乎无关紧要;但如果你是 CLI 调用,或者做 serverless 冷启动,这就是一个实打实的每进程成本,而“轻量工具”这个标签并不会让你预期到这一点。(公平说明:这只是一次剖析运行,我把它当作单次观察,而不是多轮分布。)

扩展规模:不会崩,但 PDF 要预留 CPU,表格要预留内存
我把四个大样本分别放进了四个独立进程里跑,这样峰值内存不会被前一次运行污染。没有任何一次崩溃,但成本曲线明显不均衡。

| 对象 | 输入大小 | 输出字符数 | 中位耗时 | 峰值 RSS 增量 |
|---|---|---|---|---|
| NIST SP 800-53r5(492 页 PDF) | 5.9 MB | 1,625,365 | 192.5 s | +40 MB |
| XLSX 50,000 行 × 8 列 | 2.1 MB | 3,722,955 | 62.1 s | +374 MB |
| arXiv 1706.03762(约 15 页 PDF) | 2.2 MB | 40,174 | 12.6 s | +25 MB |
| XLSX 200 行 × 64 列 | 46 KB | 120,129 | 2.9 s | +22 MB |
492 页的 NIST PDF 的中位耗时是 192.5 秒——大约 3.2 分钟,约 0.39 秒/页——因为 pdfplumber 会对每一页做词位置和表单检测。峰值 RSS 只增加了 40 MB,所以它是 CPU 密集型,而不是内存密集型。即便是那份 15 页的 arXiv PDF,在独立进程里也要 12.6 秒,比我文档套件里 warm 状态下看到的 3.7 秒慢了大约 3.4 倍。这个差距就是冷进程成本,也说明真正的驱动因素是逐页处理,而不是文件大小本身。如果你只想为这份 PDF 记一个可迁移的数字,那就用这个独立进程下的 12.6 秒。
电子表格路径则反过来:瓶颈变成了内存。一个 2.1 MB、50,000 行的 XLSX 会膨胀到峰值 +374 MB RSS(并生成 370 多万字符输出),因为转换器会把整张表加载进内存,再拼成一个巨大的 Markdown 字符串。所以最实用的建议很直接:大 PDF 要按分钟级 CPU 来预留,大表格要按数百 MB 内存来预留。这些数字是在 macOS arm64 和 Python 3.14 上单机测出来的,每页、每行的常数都和平台相关——但“PDF 慢且吃 CPU、XLSX 吃内存、不会崩溃”这个趋势是可以迁移的。
Thunderbit 适合放在哪一层——又不适合放在哪一层
这里很容易夸大,所以我先把边界画清楚:MarkItDown 和 Thunderbit 解决的是相邻问题,不是同一个问题。
MarkItDown 转换的是你已经拥有的文件。Thunderbit 则是先抓取页面。Thunderbit 的 /distill 接口 可以把实时网页转换成干净、适合 LLM 的 Markdown——它会处理 JavaScript 渲染、反爬限制和动态内容,而这些都是 MarkItDown 完全没有能力处理的;它的 /extract 接口返回的是结构匹配的 JSON,而不只是原始 Markdown。对开发者来说,这些能力可以通过同一个 AI 引擎以 API(POST /distill / POST /extract)、MCP 服务器和 CLI(npx @thunderbit/thunderbit-cli)的方式调用,这套引擎也正是 10 万+ 用户扩展 背后的技术基础。
所以它们只有一个重叠点:都能输出“适合 LLM 的 Markdown”;但输入域完全不同:Thunderbit 的 distill 处理的是公开网络上的 URL,MarkItDown 处理的是本地文件。它们不是可互换的替代品,我也不会假装它们是。更现实的方案是一起使用:先用 Thunderbit(或类似 Firecrawl 的服务)抓取并爬行网页,再用 MarkItDown 统一处理你手头的本地文档——PDF、演示文稿、电子表格。一个负责网络,一个负责文件柜。
优点和缺点
优点
- 对干净 HTML 的正文召回完整(4/4 页面),标题层级和链接都能忠实保留
- PDF/DOCX 文本召回高(arXiv 7/7 检测点、Bitcoin 6/6),且在维护者自己的 Office 样本上没有悄悄丢内容
- Office Math 公式能保留为 LaTeX——一个非常具体但很实用的优点
- 在所有规模测试中都没有崩溃,包括 492 页 PDF 和 5 万行 XLSX
- 调用方式简单:CLI、
convert()、stdin 管道输入,以及可选的 MCP 服务器 - MIT 许可、由 Microsoft 维护、issue 响应活跃
缺点
- 会保留页面杂项——Wikipedia 上最多 12.4% 都是站点外壳;它不是正文提取器
- 表格在跨行跨列、嵌套和单元格内管道符上会出问题(13 个 case 里 2 个完全坏掉,4 个结构参差),而且 rowspan 可能悄悄导致列错位
- 扫描版/纯图片 PDF 会直接输出空内容,没有 OCR,也不会报错
- PDF 输出完全没有标题层级(这点也和公开基准一致)
- 核心安装 161 MB,还带着 73 MB 的 ONNX runtime;冷启动导入约 3.35 秒
[all]extra 在 Python 3.14 上会悄悄回退到两年前的 0.0.2 版本
适合谁用,谁不该用
如果你的任务是把一堆混合的本地文档——Word、Excel、PowerPoint、带文本层的 PDF——统一转换成 Markdown,供 LLM 管道使用,而且你更在意文本完整性而不是结构保留,那 MarkItDown 很合适。作为批处理里的最后一公里转换器,把干净文本喂给模型,它速度快、保真、而且免费。
如果你的任务属于下面这些情况,就别单独用它,或者要和别的工具配合:你只需要网页正文(那就用 readability 或 Firecrawl 风格工具);你希望 PDF 的标题和表格完整保留(那是 Docling 或 Marker 的领域);或者你的输入里包含需要 OCR 的扫描文档(你需要 Azure 后端,或者直接换别的工具)。如果你本来是想买一个“爬虫”——也就是能抓取并爬行网页的工具——那它根本不是那一类。
我按爬虫类标准做的临时评分里,MarkItDown 得到 60/100;这个分数低,不是因为它作为转换器不行,而是因为我拿“爬虫”的题目去考“转换器”。放在它自己的赛道里,它的文本保真度评分很高;真正的弱点是结构层面(表格、PDF 标题)和打包层面(体积、导入、[all] 陷阱),而不是文本质量。只要你把它当成它本来的角色——文件转 Markdown 转换器——它就是一个稳定、维护良好、但有一些明显边角问题的工具,在接入生产环境前值得先了解清楚。
常见问题
MarkItDown 是网页爬虫吗?
不是。它没有爬虫程序,没有 JavaScript 渲染,没有跟链接,也没有翻页。它做的是把你已经拥有的文件和文档——PDF、DOCX、XLSX、PPTX、图片、HTML——转换成 Markdown。如果你需要抓取和爬行实时网页,你应该用 Thunderbit 或 Firecrawl 这类抓取工具;MarkItDown 是后一步,用来把抓到的内容或本地文件整理成干净的 Markdown。
为什么 pip install markitdown[all] 会装旧版本?
在 Python 3.14 上,[all] 这个 extra 把 youtube-transcript-api~=1.0.0 锁死了,而这个范围内的所有构建都要求 Python 版本低于 3.14。解析器无法满足这个约束,只能悄悄回退到 markitdown 0.0.2,也就是两年前的版本。解决方法是锁定版本后分别安装 extras:pip install 'markitdown==0.1.6',再加上 'markitdown[pdf,docx,pptx,xlsx,xls]==0.1.6'。这个问题正在 #2179 中跟踪。
MarkItDown 会对扫描版 PDF 做 OCR 吗?
默认安装不会。它的 PDF 路径只做文本抽取,所以没有文本层的图片型 PDF 会直接返回空字符串——不会报错,也不会警告。OCR 需要可选的 Azure Document Intelligence 后端或插件,而这些默认都不自带。这是一个长期存在的缺口(issue #1268)。
MarkItDown 对表格的处理效果怎么样?
从内容上看,它做得很好——在我的 13 种情况测试里,它在每个 case 都保住了 100% 的表格内容。但结构表现要看形态:简单表、宽表、无表头表、空单元格表都能输出成干净的 GFM 网格;但 rowspan 和 colspan 会变得不稳定(而且 rowspan 可能把数据悄悄错位到错误列);嵌套表会被拍平成乱码行;单元格里的字面量管道符也不会被转义。Markdown 这种平面的表格格式,本身就无法表达跨行跨列或嵌套。
MarkItDown 处理大文档够快吗?
它处理大文件不会崩,但资源要按类型预留。一个 492 页 PDF 处理了大约 3.2 分钟(约 0.39 秒/页),因为它会逐页做表单检测,属于 CPU 密集型。一个 50,000 行的电子表格大约一分钟完成,但峰值多用了 374 MB 内存,因为它会在内存里拼接成一大段 Markdown。大 PDF 要准备分钟级 CPU;大表格要准备数百 MB 内存。
使用 Thunderbit 做网页数据提取 Get Started Free


