Docling 经常被拿来和网页爬虫放在一起聊,但它其实不是网页爬虫。它是 IBM Research 出品的一套文档转换工具——现在已经成为 LF AI & Data Foundation 项目——可以把你手头已有的文件(PDF、DOCX、PPTX、XLSX、HTML、图片)转换成 Markdown 或 JSON。它自己的标语甚至直白得有点可爱:“Get your documents ready for gen 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。Docling 默认会把整套机器学习栈都作为硬依赖装进来,即便你最后只是转换一个 HTML 文件:

| 依赖项 | 占用磁盘大小(MiB,du) |
|---|---|
| torch (2.13.0) | 536 |
| opencv (cv2) | 119 |
| transformers (5.8.1) | 101 |
| scipy | 99 |
| sympy | 76 |
| pandas | 72 |
| rapidocr(含捆绑模型) | 75.6 |
| 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 做基准测试的人来说,正确做法是把“下载字节数”和“磁盘占用字节数”分开报告,因为它们本来就不是一回事。
还有一个容易踩坑的点,尤其是做容器的人会碰到:这些权重分布在两个地方,而且下载节奏也不同。布局模型和 TableFormer 会遵守 HF_HOME,并在第一次 PDF 转换时下载。RapidOCR 的模型则不会——它们会直接落到 …/site-packages/rapidocr/models/,完全绕过你的缓存配置。如果你在预构建镜像或者做离线环境,就得把这两个缓存都处理好,单靠设置 HF_HOME 是不够的。
不过也得公平一点。自 Docling 早期版本以来,项目已经推出了 docling-slim——一个大约 50 MB 的核心包,支持你用 pip install docling-slim[format-html] 只做 HTML 转换,而不用拖进 torch。也就是说,默认 docling 元包那 1.3 GB 的重量是真的,但现在它已经是可选项了。我测试的是默认包,因为 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,并按单元格逐项评分。这里有两个指标,而且它们不是一回事:cell recall 是真值单元格在检测到的表格中被找到的比例;in-row rate 是这些值被放进正确行的比例。把这两个指标混为一谈会高估工具表现,所以这里两个都列出来:

| 表格(压力测试) | 是否检测到 | 单元格召回率 | 行内准确率 | 说明 |
|---|---|---|---|---|
| T1 带边框的简单网格(5×8),单独占一页 | 否 | 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)中,有一个表头值从原本的行滑了出去,使 in-row rate 降到 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)。
不过,关于合并单元格还是要提醒一句,因为有个 open issue 讲的是相反的情况。issue #3698 报告说 V1 和 V2 会错误处理合并的行和列。在我的测试样本里,简单 colspan(T3/T5)和 rowspan 都被正确扁平化了,只有前面提到的多层表头行错位问题。可 #3698 里的失败案例是更复杂、非规则的跨多行/多列合并,以及 跨页表格——那才是最麻烦的那一类。我的样本属于简单场景。所以更准确地说:这里可以正确还原简单的 colspan 和 rowspan(但多层表头可能会错一行);复杂且不规则的合并单元格仍然是已知未解决问题。不是“合并单元格完全可用”,也不是“合并单元格全都坏了”。
一个陷阱:单独放在一页上的表格可能直接消失
再回头看那张表——T1 和 T4 根本没有被识别出来。Docling 输出了 <!-- image -->,然后把所有单元格都丢掉了,而且没有任何报错。T1 明明就是一个普通的带边框 5×8 网格。这个现象严重到让我不敢直接把它归因为“表格解析能力弱”,直到我真正隔离出触发条件,所以我做了一个脚本化的 A/B 测试。

首先,我排除了最显而易见的解释。文本层是完整的——pypdfium2 从 T1 里读出了 327 个字符,从 T4 里读出了 221 个字符,所以这些是真正的数字 PDF,不是扫描图。关闭 OCR(do_ocr=False)也没用;表格依然会消失。直接检查 DoclingDocument,会发现 len(doc.tables) == 0,而 len(doc.pictures) == 1——布局模型把整个表格区域判成了 Picture。
接着是决定性测试。我把完全相同的 T1 和 T4 表格重新渲染了一遍,但这次在周围加了几段普通正文,再做一次转换。结果两个都完美通过:len(doc.tables) == 1,正确输出 GFM 表格,而且 T4b 里的 rowspan 标签 “North” 也正确跨越了它的三行。
所以真正的问题不是 TableFormer 太脆弱,而是 Docling 的 RT-DETR 布局模型会依赖页面上下文;如果一个小表格单独出现在几乎空白的页面上,就很容易被当作 Picture 静默丢弃。这个情况在现实中很常见,因为发票、规格表、裁剪导出内容往往就是这样:一页一个表,没有周边正文。应对方法也很朴素但有效——给布局模型更多页面上下文,或者在转换后检查 doc.tables,遇到表格数为零的页面就报警。这个问题和 issue #3495 很接近(同一个表格既被识别为 Table 又被识别为 Picture),但“页面太空导致被误判”的触发条件——同一张表,单独放时消失,嵌入正文时正常——我没找到公开记录。是我测出来的,不是一个没人知道的老 bug。
真实扫描件上的 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 秒/页 还慢——因为它包含更多表格和图形,而每一个都会触发更多布局和 TableFormer 推理。所以在 CPU 上,"每页多少秒" 其实是结构密度的函数,而不是长度函数。再次强调,这只是一次 CPU-only 运行;它代表上限,不是生产环境的平均值。
多格式支持和“无损 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 中也都能保留——这至少说明,在干净输入上,Docling 的“无损 DoclingDocument”说法是站得住的。这些格式走的是各自原生后端,不依赖机器学习模型,所以速度只有几十毫秒,而且可以完全离线运行。这里的范围也很诚实:每种格式只测了一份干净文件,说明的是覆盖面,不是病理级 Office 文件压力测试。
HTML:忠实,但不够干净
这一点会直接决定 Docling 能不能进你的 RAG 流水线,所以一定要看清楚。Docling 会转换整个 HTML 文档,它不会像 readability 那样只抽取正文。我通过统计 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 和图片;如果你喂给 Docling 的是 HTML,那么布局模型和 TableFormer 都不会启动。第二,PDF 路径确实会尝试做页眉页脚模板分类,所以如果说“完全不做模板去除”就太绝对了——只是 HTML 后端会把这些模板内容原样带回来。
它和别的工具比起来怎么样(以及 Thunderbit 适合什么位置)
大家最常把 Docling 拿来比较的对象是 Firecrawl,所以这里给一个定位表。先说明一点,因为这很重要:这是文档级对比,不是同机基准测试。我没有在这些样本上跑 Firecrawl。这里 Docling 这一列是实测结果;Firecrawl 那一列来自它的公开文档。
| 维度 | Firecrawl(按其文档) | Docling(本文实测) |
|---|---|---|
| 核心任务 | 抓取 + 爬取实时网页 → Markdown | 把你已经拥有的文档 → Markdown/JSON |
| 抓取 / JS 渲染 / 反爬 | 有(托管浏览器) | 没有——你自己提供文件 |
| 正文抽取 | 有 | 没有——忠实保留全文(Wikipedia 上约 13% 为模板内容) |
| PDF 表格结构(ML) | 有限 | 有——TableFormer(官方 TEDS 93.6;在检测到的样本上 cell recall 1.00,in-row 0.97–1.00) |
| 扫描 PDF / OCR | 有限 | 有——默认 RapidOCR(能恢复 0 文本层扫描件) |
| 格式覆盖面 | 网页 | PDF/DOCX/PPTX/XLSX/HTML/EPUB/图片 |
| 部署方式 | 托管 API(+ 可自托管) | 本地 pip 库,离线可用,无需 API key |
| 上手重量 | API key / 轻量客户端 | 默认安装约 1.3 GB + 约 506 MiB 模型(或用 docling-slim) |
| 许可证 | 商业 / 源码可见 | MIT |
一句话总结:如果你的数据来自实时网页,需要抓取、JS 渲染和正文清洗,那 Firecrawl 更合适。Docling 则适合你已经拿到文档本身的场景——尤其是 PDF、扫描件和表格很多的 Office 文件——并且你希望得到忠实、离线、保结构的转换,同时具备真正的表格和 OCR 解析能力。它们是互补的。一个现实中的流水线,通常会用一个工具抓网页,再用另一个工具转换文档。
这里我也直说 Thunderbit 的位置,因为我就在这里工作,如果我假装不是这样,你肯定会怀疑。Thunderbit 和 Docling 并不是做同一件事,我也不会硬把它们说成一样。对开发者来说,Thunderbit 是一个 AI 抓取 API + MCP server + CLI,而它的工作对象是实时网页:POST /distill 会把 URL 转成干净、适合 LLM 的 Markdown(负责处理 JS 渲染、反爬和 CAPTCHA,而这些都是 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 表格的 cell recall 都是 1.00,in-row 介于 0.97–1.00),与这些样本上的官方 TEDS 说明一致。
- 表格检测鲁棒性: 有稀疏页面陷阱——孤立表格可能会被当作 Picture 丢掉。最好在转换后检查
doc.tables。 - 扫描件 / OCR: 可用,默认 RapidOCR;但规模化时会慢。
- 多格式支持: 表现扎实,JSON 往返没有问题。
- HTML: 忠实,但不干净——不会做正文抽取。
- 开发体验: API 很简洁,
DoclingDocument结构也清楚,只是缺少__version__。
适合谁:正在围绕 PDF、扫描件和 Office 文件构建 RAG 或数据流水线、并且希望离线、保结构转换、真正理解表格和 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 这类工具的工作;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。现实中大多数流水线会把两者一起用。


