Docling 经常会被拿来和网页爬虫一起聊,但它其实不是爬虫。它是 IBM Research 开发的一套文档转换工具——现在已经成了 LF AI & Data Foundation 项目——可以把你手头已有的文件(PDF、DOCX、PPTX、XLSX、HTML、图片)转换成 Markdown 或 JSON。它自己的标语也很直接:“让你的文档为生成式 AI 做好准备。”
所以这篇文章测的是一个转换器,不是一个抓取器。下面的所有结果都来自一台仅 CPU 的机器(macOS arm64、Python 3.14.2、Docling 2.111.0),用脚本逐项打分,失败就按失败记录。这个仓库体量很大,而且几乎每天都在变——63,069 个 stars、4,449 个 forks,而且我拉取元数据的当天就有新的提交——所以这里出现的任何 issue 数量或版本号,都只是某一时刻的快照,不是固定不变的常量。
Docling 到底是什么,不是什么
Docling 的核心单位是 DoclingDocument:先把文件解析成这种结构,再导出为 Markdown、HTML、DocTags 或无损 JSON。项目代码采用 MIT 许可证(具体模型的许可证会有所不同),最初来自 IBM Research Zurich;写这篇时最新版本是 v2.112.0,而它是在我开始测试前两天发布的。

它最突出的能力,是处理 PDF 和图片这条路径。但这条路径靠的不是简单字符串解析,而是一整套机器学习模型:RT-DETR 布局模型、TableFormer 表格结构模型、可选的视觉语言模型,以及用于扫描件的 RapidOCR。这些模型负责还原页面布局、阅读顺序和表格结构。也正是这部分最值得评测,而纯 HTML 测试根本看不到这些能力。
这里有个关键区别,能帮你少走一周弯路:Docling 不会主动抓取任何内容。它不渲染 JavaScript,不绕过反爬墙,也不负责爬站点。你把文件给它,它负责理解文件。至于抓取网页,是另一个工具该做的事——这一点在后面很重要,因为很多人会问 Docling 能不能替代 Firecrawl(不能,它们是互补关系,后面我会解释)。
第一次运行时,没人提醒你的那些事
在 Python 3.14.2 上执行 pip install docling 一切顺利。可当你打开虚拟环境一看,会发现它已经是 1.3 GB。哪怕你最终只转换一个 HTML 文件,Docling 也会把整套机器学习依赖一并拉下来:

| 依赖项 | 磁盘占用(MiB,du) |
|---|---|
| torch (2.13.0) | 536 |
| opencv (cv2) | 119 |
| transformers (5.8.1) | 101 |
| scipy | 99 |
| sympy | 76 |
| pandas | 72 |
| rapidocr(+ 内置模型) | 72.1 |
| docling_parse | 30 |
这还只是你真正转换第一个 PDF 之前的体积。真正的摩擦点出现在第一次 PDF 转换时,因为那时候模型才会下载。在一个全新的、隔离的 HuggingFace 缓存环境里,首次 PDF 转换大约花了 224 秒——而且几乎全都耗在下载上,不是计算。布局模型加 TableFormer 模型落地后,磁盘占用约为 506 MiB(TableFormer 342 MiB + 布局模型 164 MiB,经 du 验证),RapidOCR 还会把约 40 MB 的 PP-OCRv4 权重下载到 site-packages 里。第二次转换同一个文件呢?0.55 秒。 模型缓存后,代价只付一次。

有一个数字你应该忽略:冷启动脚本打印的 model_download_mb 是 1060.2。不要把这个当作真实占用。它来自一个会跟随符号链接的 os.walk,而 HuggingFace 缓存会先把每个模型文件存一份在 blobs/ 下,再通过 snapshots/ 里的符号链接重新暴露出来——所以这次遍历把 14 个模型文件算了两次。真正和 du 一致、并且去重了符号链接后的数字是 约 506 MiB(仅 blobs 约 505.4 MiB)。对任何想给 Docling 做基准测试的人来说,结论很简单:下载字节数和磁盘占用要分开报,因为它们根本不是一回事。
还有一个坑,特别容易绊到要把 Docling 打包进容器的人。模型权重分布在两个位置,而且下载节奏也不同。布局模型和 TableFormer 模型会遵守 HF_HOME,在第一次 PDF 转换时下载。但 RapidOCR 的模型不会——它们会直接落到 …/site-packages/rapidocr/models/ 里,完全绕开你的缓存配置。如果你在预构建镜像或者做离线环境,两个缓存都得处理;单靠设置 HF_HOME,并不能覆盖第二个位置。
不过也得公平一点说。自从 Docling 早期版本以来,项目已经推出了 docling-slim——一个大约 50 MB 的核心包,允许你为了 HTML 只安装 docling-slim[format-html],而不用拖进 torch。所以默认 docling 元包确实重,但现在已经可以选择不装那一大坨。我测试的是默认包,因为这仍然是 pip install docling 的结果;但“太重”并不是一个没有解决方案的缺陷——项目里已经有模块化方案,见 issue #2393。
安装过程中,我还碰到一个小但很烦的点:import docling; docling.__version__ 会抛出 AttributeError: module 'docling' has no attribute '__version__'。这个模块并没有导出版本号。可用的写法是 importlib.metadata.version("docling"),它会返回 '2.111.0'。这只是个轻微的开发体验问题,且 自 2026 年 7 月起就已在上游以 issue #3733 的形式存在。
表格保真度:TableFormer 的价值所在
之所以有人会选 Docling 而不是普通的 PDF 转文本,核心原因就是表格。所以我生成了 7 个带机器可读真值的表格 PDF,并按单元格逐项评分。这里有两个重要指标,而且它们并不是一回事:单元格召回率,指的是所有真值里有多少被识别到表格中的任意位置;同列同排率,指的是这些值有多少被放进了正确的行里。把这两个指标混为一谈会美化工具表现,所以我把两项都列出来:

| 表格(压力测试) | 是否检测到 | 单元格召回率 | 同列同排率 | 备注 |
|---|---|---|---|---|
| T1 简单带边框网格(8 行 × 5 列),页面上只有它一个 | 否 | 0.0 | — | 被识别为 <!-- image -->,所有单元格都丢失 |
| T2 无边框表格(只有表头线) | 是 | 1.00 | 1.00 | 完美还原,网格准确 |
| T3 带合并单元格的两级 colspan 表头 | 是 | 1.00 | 0.97 | 所有值都找到了;有一个表头值偏到下一行 |
| T4 带 rowspan 的行标签,且页面上只有它一个 | 否 | 0.0 | — | 被识别为 <!-- image --> |
| T5 colspan 表头 + 无边框表格 | 是 | 1.00 | 0.97 | 所有值都找到了;和 T3 一样,表头行有轻微偏移 |
| T6 财务表,含空白列,右对齐 | 是 | 1.00 | 1.00 | 空白列保留了,没有被挤掉或错位 |
| T7 宽版 12 列网格 | 是 | 1.00 | 1.00 | 宽表没有发生列错位 |
在 Docling 成功识别的 5 个表格里,所有真值都被完整保留下来——单元格召回率全部是 1.00。其中 3 个表格里,每个值也都落在了正确的行里。至于那两个多级表头案例(T3 和 T5),有一个表头值会从原本的行偏出去一格,使同列同排率掉到 0.97——数据本身都在,只是叠层表头上的行归属会有一点轻微抖动。
一些结构上很难处理的案例,表现比我预期得还好。两级 colspan 表头被正确压平成 GitHub 风格 Markdown("Q1 2026" 标签在它跨越的两个列上重复显示,这正是把 colspan 压成 GFM 的正确方式)。只有表头线的无边框网格(T2)也被准确还原。12 列的宽表(T7)没有发生列偏移。一个完全空白的财务列(T6)也被保留为空单元格,没有被删除或合并。这与 官方 TableFormer 的 TEDS 分数是一致的——简单表 95.4、复杂表 90.1、全部表 93.6——而模型卡的表现明显高于 Camelot(73.0)和 EDD(88.3)。
不过要提醒一句关于合并单元格的事,因为有一个公开 issue 讲的是相反情况。issue #3698 报告说 V1 和 V2 在处理合并行和合并列时有问题。在我的测试样本里,简单的 colspan(T3/T5)和 rowspan 值都被正确压平了,只有上面提到的多级表头行偏移问题。可 #3698 失败的场景是更复杂的多行/多列不规则合并,以及 跨页表格——那已经是病理级边界情况了。我的样本属于简单一侧。所以更准确的说法应该是:这里简单的 colspan 和 rowspan 能被恢复出来(多级表头可能会偏一行);复杂和不规则合并仍然是已知的开放问题。不能说“合并单元格都没问题”,也不能说“合并单元格全都坏了”。
一个陷阱:单独放在页面上的表格可能会消失
回头看那个表格——T1 和 T4 根本没有被检测到。Docling 直接输出了 <!-- image -->,并把所有单元格都丢掉了,而且没有报错。T1 明明就是一个很普通的 8 行 5 列带边框网格。这个现象严重到让我不愿意直接把它归为表格解析能力弱,于是我先做了一个脚本化的 A/B 对比,看看真正触发因素是什么。

我先排除了最显而易见的解释。文本层是完整的——pypdfium2 在 T1 中能读出 327 个字符,在 T4 中能读出 221 个字符,所以它们都是真实的数字 PDF,不是扫描图。把 OCR 关掉(do_ocr=False)也没用;表格还是会丢。直接检查 DoclingDocument 后发现,len(doc.tables) == 0,而 len(doc.pictures) == 1——也就是说,布局模型把整块表格区域当成了 图片。
然后我做了决定性的测试。我把完全相同的 T1 和 T4 表格重新渲染了一遍,这次在它们前后各加了几段普通正文,再转换一次。结果两个表格都完美通过:len(doc.tables) == 1,输出的是正确的 GFM 表格,而 T4b 的 rowspan 标签 “North” 也正确地跨了三行。表格完全一样,唯一变化的是它是孤零零地放在一页空白度很高的页面里,还是嵌在正文中。
所以真正的 caveat 不是 TableFormer 很脆弱,而是 Docling 的 RT-DETR 布局模型会利用页面上下文;一个小表格如果单独放在几乎空白的页面上,就很容易被当成 Picture,随后被静默丢弃。这在实际中很容易遇到,因为发票、规格表、裁剪导出文件,常常就是一页一个表,周围没有正文。可行又朴素的做法有两个:给布局模型更多页面上下文,或者在转换后检查 doc.tables,把表格数量为零的页面标出来。这和 issue #3495(一个表同时被识别成 Table 和 Picture)有些关联,但“页面过于稀疏会触发误判”这个具体机制——同一张表,单独放会丢,嵌入文本里就正常——我没有找到此前公开的记录。是我测出来的,不是早就有文档说明的已知问题。
真实扫描件上的 OCR:RapidOCR,不是 EasyOCR
扫描版 PDF 往往是很多转换器悄悄翻车的地方,所以我给 Docling 喂了两个真实扫描件,二者的文本层都经过测量为 0 个字符——pypdfium2 显示没有可恢复字符,这说明任何输出都来自 OCR,而不是文件里藏着的文本层。
单页的 ocr_test.pdf 在 CPU 上 14.3 秒就识别干净了:“Docling bundles PDF document conversion to JSON and Markdown in an easy self contained package” 被原样恢复出来。四页的 nemotron_multipage.pdf 则在 70.1 秒内把四页全部做完 OCR(17.5 秒/页),并在每页都输出了重复的测试句子。默认 OCR 会自动启动——不需要额外参数,也不需要配置。
但这里有个很多文章写错的细节:默认 OCR 引擎其实是 RapidOCR,不是 EasyOCR。我是通过第一次运行时看到 PP-OCRv4 的 .pth 权重被下载,确认这一点的。很多旧博客和 Docling 早期 FAQ 还在说默认是 EasyOCR,那已经过时了。现在 EasyOCR 是一个可选附加组件。依然成立的提醒是:OCR 在大规模场景里很慢,而这里的所有数据都只是 CPU 条件下的上限;如果用 GPU,速度会明显更快。
真实 PDF、阅读顺序,以及每页耗时
合成样本能证明具体行为;真实 PDF 才能证明它真的能用。我测试了两篇原生数字化的学术论文——9 页的 Docling 技术报告,以及 15 页的《Attention Is All You Need》——它们都是双栏排版,还包含表格和公式。
在 15 页的 Attention 论文里,所有 5 个章节标记——Abstract、Introduction、Background、Conclusion、References——都按照 文档原始顺序 出现在线性化后的 Markdown 里,尽管它是双栏排版。每一个内容探针(Transformer、encoder、BLEU、multi-head)也都在,而且那几张著名的多栏结果表被识别成了 4 个表格。这说明它真的能恢复阅读顺序和列合并,这正是 RAG 分块的核心价值——如果线性化器把双栏页面打乱成交错乱码,那你根本没法合理分块。
时间表现也带来一个有点反直觉的结论:每页耗时取决于每页有多少结构,而不是总页数。更密集的 9 页报告平均 14.95 秒/页,反而比 15 页论文的 5.99 秒/页 更慢,因为前者每页塞了更多表格和图形——9 页里 3 个表格,对比 15 页里 4 个表格——每个结构都会触发更多布局与 TableFormer 推理。这个差距并不大,而且从绝对数量上看,密集文档里的表格并不更多。所以在 CPU 上看“每页多少秒”,本质上是结构密度的函数,不是长度的函数。再强调一下,这只是单次 CPU 测试;它是性能上限,不是生产环境下的实际值。
多格式支持,以及“无损 JSON”到底是不是真的
Docling 主打统一的多格式解析,所以我生成了一个 DOCX、一个 XLSX 和一个 PPTX,里面都放了已知内容和真值探针,然后检查两件事:这些探针是否出现在 Markdown 里,以及通过 export_to_dict() 导出的 JSON 循环后能否完整保留。
| 文件 | 转换耗时(秒) | 在 Markdown 中找到的探针 | Markdown 中的表格数 | JSON 循环后是否保留探针 |
|---|---|---|---|---|
report.docx(标题 + 合并 "Total" 表格 + 项目符号) | 0.137 | 7/7 | 1 | 是 |
workbook.xlsx(2 个工作表,含空白列) | 0.016 | 6/6 | 2 | 是 |
deck.pptx(3 张幻灯片,包含项目符号 + 表格) | 0.038 | 6/6 | 1 | 是 |
所有内容探针都出现在 Markdown 里,表格也都被恢复了(包括 DOCX 里合并的 “Total” 行,以及 XLSX 的两个工作表),而且每个探针也都完整保留在 export_to_dict() 的 JSON 里——这正是 DoclingDocument “无损” 说法的关键证据,至少在干净输入上是如此。这些格式走的是原生后端,而不是机器学习模型,所以处理速度只有几十毫秒,而且完全离线。这个结论是诚实的:每种格式各测了一个干净文件,足以说明覆盖面,不足以说明对病理级 Office 文件的极限抗压能力。
HTML:忠实,但不够干净
这一点会直接决定 Docling 能不能进你的 RAG 流程,所以请仔细看。Docling 会转换 整个 HTML 文档,而不是像阅读器那样只提取正文。我用 Docling 自己的输出,统计了导航栏、目录、cookie 提示和页脚这些模板内容到底保留了多少。
| 页面 | 非空 Markdown 行数 | 模板行数 | 模板占比 | 正文起始行 |
|---|---|---|---|---|
| Wikipedia “Web scraping” | 255 | 34 | 13.3% | 28 |
| scrapethissite/forms | 63 | 1 | 1.6% | — |
| books.toscrape | 65 | 0 | 0.0% | — |
| quotes.toscrape | 35 | 0 | 0.0% | — |
在 Wikipedia 这种界面元素很多的页面上,大约 13% 的 Markdown 行都是导航/目录/页脚等模板内容,而真正的文章要到第 28 行才开始——输出一开头就是 “move to sidebar / Contents / Toggle the table of contents”,结尾则是 “CS1 maint… / Search Wikipedia”。而在内容很干净的页面(books、quotes)上,模板占比大约是 0%,所以这更像是模板外壳问题,而不是每页固定要付出的成本。Docling 给你的是忠实的整页 Markdown,不是干净的正文抽取。上游已经在 issue #1865(已关闭)和 #1930(进行中)跟踪 HTML 模板问题。
不过这件事我也要说得公平一点。第一,就 HTML 而言,Docling 根本不跑任何机器学习模型——它只是一个基于 BeautifulSoup 的简单流水线。那种“视觉模型在读你网页”的说法,只适用于 PDF 和图片;如果输入的是 HTML,布局模型和 TableFormer 都不会启动。第二,PDF 路径确实会尝试做页眉页脚等模板分类,所以如果说“完全没有做任何模板清理”就太武断了——只是 HTML 后端会把这些界面元素原样带回来。
对比表现,以及 Thunderbit 在哪一层
大家最常把 Docling 拿来对比的工具是 Firecrawl,所以这里给一个定位表。先说明一个前提,这很重要:这不是同一台机器上的基准测试,而是文档层面的对比。我没有在这些样本上跑 Firecrawl。这里只有 Docling 一列是实测值;Firecrawl 一列来自它的公开文档。
| 维度 | Firecrawl(按其文档) | Docling(本文实测) |
|---|---|---|
| 核心任务 | 抓取并爬取实时网页 → Markdown | 把你已经拥有的文档 → Markdown/JSON |
| 抓取 / JS 渲染 / 反爬处理 | 有(托管浏览器) | 没有——你自己提供文件 |
| 正文抽取 | 有 | 没有——忠实保留整篇文档(Wikipedia 约 13% 是模板内容) |
| PDF 表格结构(ML) | 有限 | 有——TableFormer(官方 TEDS 93.6;在本文检测到的样本上单元格召回率 1.00,同列同排率 0.97–1.00) |
| 扫描 PDF / OCR | 有限 | 有——默认 RapidOCR(成功处理了无文本层扫描件) |
| 支持格式广度 | 网页 | PDF/DOCX/PPTX/XLSX/HTML/EPUB/图片 |
| 部署方式 | 托管 API(+ 可自托管) | 本地 pip 库,离线运行,不需要 API key |
| 安装负担 | API key / 轻量客户端 | 默认安装约 1.3 GB + 约 506 MiB 模型(或使用 docling-slim) |
| 许可证 | 商业 / 源码可见 | MIT |
一句话总结:如果你的数据在实时网页上,需要爬取、JS 渲染和正文清洗,那 Firecrawl 更合适。如果你已经拿到文档本身——尤其是 PDF、扫描件和表格很多的 Office 文件——那 Docling 更适合,它能离线、忠实、保结构地转换,并真正理解表格和 OCR。它们是互补的。现实里的流水线通常会一个负责爬,一个负责转。
说到这里,我也得坦率讲讲 Thunderbit,毕竟我在这里工作,如果假装和我无关,那就太可疑了。Thunderbit 和 Docling 并不做同一件事,我也不会硬把它们说成一样。对开发者来说,Thunderbit 是一个 AI 抓取 API + MCP 服务器 + CLI,它的工作对象是实时网页:POST /distill 会把一个 URL 转成干净、适合 LLM 使用的 Markdown(处理 JS 渲染、反爬和验证码,而这些正是 Docling 明确不碰的),而 POST /extract 会通过你定义的 JSON Schema 返回结构匹配的 JSON。这是 RAG 流程中“抓取并清洗”的一端。Docling 则是本地文档这一端——PDF、扫描件、还有硬盘上已经存在的表格。你的语料如果是网页,就用 Thunderbit 的 API、MCP 工具(thunderbit_suggest_fields、thunderbit_distill、thunderbit_extract)或 CLI(npx @thunderbit/thunderbit-cli)。如果是 PDF 和扫描件,就用 Docling。如果两者都有——这才是大多数真实流水线的情况——那就把它们组合起来用,谁也不是谁的替代品。
最终结论:先给保留意见,再留作业
我不会给你一个 0–100 的总分,因为如果把所有维度硬加权,会把 Docling 从来没承诺要做的事情(比如爬虫能力)也算进去,然后假装这些东西可以直接比较。按我测试的各项维度来看:
- 安装 / 首次运行: 很重——1.3 GB 虚拟环境、约 506 MiB 模型、首个 PDF 约 224 秒、热启动约 0.55 秒——但可以用
docling-slim规避这份重量。 - 表格保真度: 只要检测到了表格,就表现很强(5/5 表格单元格召回率 1.00,同列同排率 0.97–1.00),和官方 TEDS 结论一致。
- 表格检测鲁棒性: 存在稀疏页面陷阱——孤立表格可能会被当成 Picture 丢掉。转换后要检查
doc.tables。 - 扫描件 / OCR: 能用,默认 RapidOCR;但规模化时会慢。
- 多格式支持: 表现扎实,JSON 循环后也能保持完整。
- HTML: 忠实,但不干净——没有正文抽取。
- 开发体验: API 很简洁,
DoclingDocument也很整齐,只是缺少__version__。
适合谁:做 RAG 或数据流水线、数据来源是 PDF、扫描件和 Office 文件的团队,想要离线、保结构的转换,并且真的需要表格和 OCR 理解。 不适合谁:需要实时网页爬取或者干净正文 HTML 抽取的人——那是另一个工具的活。
而且因为这是一篇评测,不是公关稿,所以限制也必须写清楚。这是一次有针对性的探测——7 个合成表格加 2 份真实 PDF,只在一台 CPU 机器上跑——不是 TEDS 规模的精度基准。还有几项我没有测试、但你在把流水线押在 Docling 之前一定要自己验证:可选的 VLM(GraniteDocling)路径、docling-slim 的真实体积、GPU 运行、复杂且不规则的合并单元格以及跨页表格、公式转 LaTeX 的准确性,还有——最容易在生产里让你意外的一项——批量处理时内存增长、线程/GIL 扩展性,以及成千上万次转换中的对象生命周期稳定性。Docling 在它声明的范围内确实很强,靠的是测量结果,不是营销话术;但它也有真实边界,最好在把语料交给它之前先画清楚。记住稀疏页面这个 caveat,预留首次下载时间,并自己验证大规模场景下的表现。
试试 Thunderbit 做网页数据提取 Get Started Free
常见问题
Docling 是网页爬虫或爬站工具吗? 不是。Docling 处理的是你已经拥有的文档——PDF、DOCX、PPTX、XLSX、HTML、图片——并把它们转换成 Markdown 或 JSON。它不会抓取 URL、不会渲染 JavaScript,也不会处理反爬。抓取实时网页是另一类工具的工作,比如 Firecrawl,或者 Thunderbit 的 Web API;Docling 从你提供的文件开始处理。
Docling 的安装体积和首次运行下载有多大?
默认的 docling 元包会把虚拟环境撑到约 1.3 GB,因为它把整套机器学习栈都作为硬依赖拉下来(仅 torch 就有 536 MiB)。第一次转换 PDF 时,会下载约 506 MiB 的布局和 TableFormer 模型到磁盘,再加上约 40 MB 的 RapidOCR 权重,总耗时大约 224 秒——几乎全是下载。第二次转换大约 0.55 秒。如果你只需要轻量格式,可以用 docling-slim(核心大约 50 MB)绕开这条重路径。
Docling 支持 OCR 吗?用的是哪个引擎? 支持。在没有文本层的扫描 PDF 上,Docling 的 OCR 会自动启动,而且我测试时恢复得很干净。默认引擎是 RapidOCR,不是 EasyOCR——这也是很多旧文章里常见的误解。EasyOCR 现在是可选附加组件。OCR 在大规模场景里速度比较慢,尤其是在 CPU 上。
为什么 Docling 把我的表格变成了图片,或者直接漏掉了?
大概率是稀疏页面效应。Docling 的 RT-DETR 布局模型会利用页面上下文,而一个小表格如果孤零零地放在几乎空白的页面里,就可能被分类成 Picture,然后静默丢弃。把同一个表格放在正文上下文中,它就能正常转换。解决办法要么是给布局模型更多页面上下文,要么是在转换后检查 doc.tables,把表格数为零的页面标出来。
Docling 和 Firecrawl 应该选哪个? 两者职责不同,所以通常不是二选一。Firecrawl 负责爬取实时网页、渲染 JavaScript、提取正文。Docling 负责把你已经拿到的文档转换出来,并且对 PDF 表格结构和 OCR 的支持更强,完全离线。如果你的数据源是网页,就用网页工具(Firecrawl,或者 Thunderbit 的 API/MCP/CLI)。如果是 PDF、扫描件或 Office 文件,就用 Docling。现实里的大多数流水线都会同时用到两者。


