做知识库、做 RAG、做文档问答的同行,应该都有同一个感受:大模型本身的门槛早就被打得很低了,真正卡脖子的地方反而是“喂给模型的文档到底干不干净”。PDF 里的复杂表格、双栏排版、扫描件、数学公式,随便挑一样出来,都能让解析结果变成一团乱麻。docling 就是冲着这个问题来的——它是 IBM 开源的一个文档转换工具,能把 PDF、Word、PPT、Excel 甚至图片统一解析成结构化的 Markdown 或 JSON,内置了版面分析、表格结构识别、OCR、公式识别一整套能力。我最近把好几个内部项目的文档处理链路都迁到了 docling 上,这篇文章会把从选型、安装、调优到踩坑的完整过程写出来,给同样被非结构化文档折磨的同学一个可以直接抄作业的方案。
1. 为什么我选 docling 做文档解析:定位、能力与应用场景
1.1 非结构化文档解析到底难在哪
先说一个很多人刚开始容易忽略的事实:PDF 不是一种“适合文本提取”的格式。它本质上是页面排版快照,里面内容可能是文本流、扫描图片、矢量图形,或者是三种混在一起。我们做 RAG 和知识库的时候,如果第一步解析就是错的,后面所有环节——切分、向量化、检索、生成——都会跟着遭殃。
举个例子,我处理过一份带跨页表格的财报 PDF,里面有三个合并单元格的“合计”列,表格数据占了整整两页。用传统文本提取工具拉出来的内容是:第一页的几行数字,第二页的几行数字,中间被段落文字隔开,表格的表头列名散落在文本流里。这种数据直接拿去检索,一个简单的问题“去年营收合计是多少”根本查不到有效答案,因为“营收”“合计”“数值”这些关键信息在切分时已经被拆得七零八落。
传统工具为什么搞不定?因为它们本质上在做“文本抽取”,不是“文档理解”。文本抽取只关心“有哪些字”,而文档理解要回答“这些字在文档里是什么角色、什么结构、什么顺序”。同样是表格,在文本流里它是一堆连续字符,在文档理解模型眼里它是一个二维结构,包含行、列、合并关系、表头层级。docling 的价值就在这里:它把文档解析从“抽字”升级成了“理解结构”。
还有阅读顺序的问题。双栏论文、带有侧边栏的产品手册、页眉页脚复杂的报告,纯文本工具按页面坐标顺序吐字,经常把左栏和右栏的内容完全打乱。你以为在给模型喂知识,实际喂进去的是被随机打乱的句子。这类问题不借助版面分析模型,很难从根上解决。
1.2 docling 的核心组成:版面、表格、OCR 与公式
docling 不是单一函数,而是一套完整的文档理解 pipeline。它内部把解析过程拆成几个阶段,每个阶段都由专门的模型负责。
首先登场的是版面分析模型 DocLayNet。它会把页面里的每个区域识别出来:标题、正文、表格、图片、列表、页眉、页脚、侧边栏等,然后按照合理的阅读顺序重新排列。这一步解决的就是“双栏乱序”“被页眉干扰”“标题正文层级不清”这些麻烦。实际体验下来,DocLayNet 对常见版式的识别相当稳,尤其是论文和财报这类结构规律较强的文档,出来的层级信息可以直接当 Markdown 的标题和段落用。
其次是表格结构识别模型 TableFormer。它负责把表格区域变成真正的二维表格数据——识别表头、行、列、合并单元格、单元格的跨行跨列关系。这是最复杂的部分,也是传统工具死得最惨的部分。我后面会专门讲怎么调优。
然后是 OCR 管线。扫描件和图片型 PDF 没有可提取的文本层,必须先把图像里的文字识别出来。docling 默认集成 EasyOCR,也支持 Tesseract 等引擎,可以设置语言包,中英文混排也能处理,只要显式开启 OCR,效果足够应付绝大多数扫描合同、旧书扫描件、盖章文件。
最后是公式识别。带数学公式的论文,docling 会把公式区域识别出来并转成 LaTeX 表达式。对理工科论文、数学教材这类内容,这一步能让公式从“图片”变成可检索、可喂给 LLM 的文本描述。
除此之外,docling 输入格式不局限于 PDF,DOCX、PPTX、XLSX 也支持,输出有 Markdown、JSON、HTML、纯文本等多种选择。JSON 输出是很多人忽略但极其有用的能力:你可以在 JSON 里拿到每个段落、表格、图片的坐标、类型、层级关系,做精细化的文档处理和切片非常方便。
1.3 与 PyMuPDF、Unstructured 等工具的横向对比
如果你之前用过其他工具,可能会好奇 docling 到底比它们强在哪。直接看对比表:
| 工具 | 表格提取 | 扫描件 OCR | 公式识别 | 阅读顺序 | 模型依赖 | 上手成本 |
|---|---|---|---|---|---|---|
| PyMuPDF/pdfplumber | 需要手写解析坐标 | 不支持 | 不支持 | 不支持 | 无 | 低 |
| Unstructured | 中等,复杂表格弱 | 需要额外配置 | 不支持 | 部分支持 | 中等 | 中 |
| docling | 强,能处理合并单元格 | 内置,可配置 | 支持 | 强 | 高 | 中,API 简单 |
PyMuPDF 这类工具的优势是轻、快、没有模型依赖,适合处理“文本结构干净”的 PDF,或者只想快速抓取纯文本的场景。但如果你的文档里有复杂表格和扫描页,很快会耗死在手写解析逻辑上。我自己以前为某个 PDF 的表格写过上百行坐标处理代码,过几个月文档更新一下格式,代码就废了。
Unstructured 是另一个流行方案,它的生态做得很好,LangChain 集成也早。但它对表格结构识别和公式识别的深度不如 docling,复杂表格解析结果经常还是“伪表格”,读起来像 Markdown 表格,实际行列关系并不对。docling 的路线更重、模型更强,换来的是结构还原度更高。如果你追求的是知识库检索质量和文档问答准确率,这个“重”是值得的。
2. 环境准备与 5 分钟快速上手
2.1 安装依赖与版本坑
不管用什么包管理工具,docling 的安装都不复杂,但有几个前提你需要提前知道。首先 Python 版本不能太低,我建议直接上 3.10 以上,老版本 Python 在安装某些依赖时容易触发编译报错,白白浪费时间。其次它依赖 torch、transformers 这些深度学习库,安装包体积很大,磁盘上最好预留几个 G 的空间。
我最常用的安装命令是:
pip install "docling[pdf]"这里重点提醒:[pdf] 这个 extra 很关键。如果你只装 docling,后面解析 PDF 会发现能力不完整,或者运行时报缺模块。PDF 解析需要的深度学习模型、OCR 依赖都被拆分到了这个 extra 里,跳过它等于装了个残缺版。具体 extras 名称在不同小版本里可能有调整,装之前看一眼官方 README,或者直接 pip install "docling[all]" 一把梭,省心。
在 macOS 上装完还会遇到一个经典坑:运行报FileNotFoundError: failed to find libmagic。这不是 docling 的问题,是系统缺少 libmagic 动态库,用 Homebrew 装一下就好:
brew install libmagicUbuntu 上对应的是:
sudo apt-get install libmagic1我建议装完做一次“冒烟测试”,拿一份带表格的 PDF 跑通全流程再开始正式处理,别等批量跑了十万份文档才暴露环境问题。
2.2 用 Python API 把 PDF 转成 Markdown
docling 的 Python API 简洁得不像一个深度学习项目。最核心的类是 DocumentConverter,三行代码就能完成 PDF 到 Markdown 的转换:
from docling.document_converter import DocumentConverter converter = DocumentConverter() result = converter.convert("annual_report.pdf") markdown_text = result.document.export_to_markdown() with open("output.md", "w", encoding="utf-8") as f: f.write(markdown_text)你知道这背后发生了什么吗?第一次调用 convert 时会自动从 HuggingFace 下载 DocLayNet 和 TableFormer 的模型权重,然后加载 torch、跑版面分析、跑表格结构识别、重组阅读顺序、导出 Markdown。表面上是三行代码,实际上跑完了一整套深度学习推理流程。
我拿一份含标题、两段正文、一张表格、一张图片的 PDF 实测,输出的 Markdown 大致是这种效果:
# 项目总体营收情况 本年度项目营收保持稳定增长,其中核心业务板块贡献了主要增量…… | 业务板块 | 营收(万元) | 同比增速 | | -------- | ---------- | -------- | | A | 12000 | 18% | | B | 8600 | 7% | 相关数据详见下图:注意看,标题层级、表格的 Markdown 语法、图片占位,甚至表格对齐都被正确还原了。这就是“文档理解”和“文本抽取”的差别——它输出的不是文字,是结构。
如果你需要处理 JSON,只需要换一行:
data = result.document.export_to_dict()JSON 里能看到每个文本块的类型、坐标、层级关系、表格单元格的详细结构。做精细化的 RAG 切片时,这个 JSON 是比 Markdown 更有价值的中间产物。
2.3 CLI 快速批处理一整个文件夹
不想写代码的时候,docling 还自带 CLI,处理单个文件非常方便:
docling annual_report.pdf --to md -o ./output--to指定输出格式,支持 md、json、html、text 四种;-o指定输出目录。批量处理整个文件夹,在 Linux/macOS 上可以这样:
for f in pdfs/*.pdf; do docling "$f" --to md -o markdown_output/ doneWindows PowerShell 里写法略不同,要用 Get-ChildItem 遍历。CLI 还有个好处是它会自动保持输入文件目录结构,批量转完不会所有文件挤在一个目录里,后续溯源很方便。不过说实话,只要涉及批量任务,我更推荐直接写 Python 脚本调用 API,因为你能拿到更多控制权,比如失败重试、并发控制、输出 JSON 中间态,这些后面我会细说。
3. 关键参数与模型调优实战
3.1 扫描 PDF 打开 OCR:引擎与语言包
很多人试用 docling 处理扫描件时,发现输出几乎是空的,就是没开 OCR。docling 不会默认对扫描件做文字识别,你需要显式开启 OCR 配置。
我常用的配置方法是构造 PdfPipelineOptions,指定 OCR 引擎和语言包:
from docling.datamodel.base_models import InputFormat from docling.datamodel.pipeline_options import EasyOcrOptions, PdfPipelineOptions from docling.document_converter import DocumentConverter, DocumentConversionOptions pipeline_options = PdfPipelineOptions(do_ocr=True) pipeline_options.ocr_options = EasyOcrOptions(lang=["en", "zh"]) converter = DocumentConverter( format_options={InputFormat.PDF: pipeline_options} ) result = converter.convert("scanned_report.pdf")不同小版本的类名和参数名会有细微调整,核心思路不变:先看你自己环境里 help(PdfPipelineOptions) 的输出,再对着设置。
OCR 引擎方面,docling 最常见的是 EasyOCR 和 Tesseract 两条路线。简单对比一下:
| 引擎 | 中文支持 | 安装复杂度 | 速度 | 显式依赖 |
|---|---|---|---|---|
| EasyOCR | 好 | 低,pip 即装 | 较慢 | torch 自动带上 |
| Tesseract | 一般 | 需要装系统二进制 | 快 | 需自行安装 |
我个人的习惯是:中英文混排选 EasyOCR,纯英文且对速度有要求可以试 Tesseract。EasyOCR 默认语言是英文和中文,你如果不指定语言包,遇到纯中文扫描件识别率会不稳定,所以至少要把 lang=["zh"] 或 ["en", "zh"] 设置好。
开 OCR 之后处理速度会明显变慢,因为每个页面都要先做图像文字识别再做版面分析。如果文档几百页,建议在 GPU 环境跑,CPU 环境等起来会非常痛苦。
3.2 表格结构识别优化
表格是文档解析里最容易翻车的环节。docling 内置的 TableFormer 对常见表格效果已经很稳,但遇到多级表头、合并单元格、跨页表格、无规则线条的表格时,还是需要针对性调优。
我自己的调优步骤是:
第一步,先确认表格识别有没有真正打开。有些场景下表格被识别成“图片块”,输出 Markdown 里没有表格语法,而是图片占位,这时候需要检查版面分析是否把表格误判成了图片。
第二步,提高输入页面的清晰度。对于扫描版本,OCR 的文本质量直接影响表格结构识别。同一页 300 DPI 扫出来的效果,比 150 DPI 好太多。空间分辨率不够,合并单元格和表头边界很容易糊掉。
第三步,观察 JSON 输出里的表格单元格坐标。如果行列坐标明显偏移,可能是 TableFormer 对某些复杂表头的泛化能力不足。这种时候我一般会退一步:如果表格不是核心检索对象,就接受它被拆成普通文本;如果表格非常重要,比如财报、市场数据,那就把表格单独截出来,用更专用的表格解析工具配合处理。
第四步,调整超参与页面裁切。docling 的表格识别通常基于整页做版面分析,如果页面内有大量页眉页脚或广告区域,可以先裁掉干扰区域,再送进去解析。很多人忽略这个细节,结果模型把页眉识别成了表头,整张表格结构全乱。
表格识别没有银弹,最好的做法是做一个小样本集,放十几份典型表格进去跑,把结果人工检查一遍,找到适合自己文档类型的参数组合再批量上。
3.3 图片、公式与复杂版式的处理
图片处理上,docling 默认会把文档里的图片嵌到 Markdown 里,但需要注意它的嵌法。在部分版本中,图片会被转成 base64 字符串直接写在 Markdown 里,一个文档图片多的话,Markdown 文件能膨胀到几十 MB。做知识库时这种文件不仅切分慢,存储也很浪费。更好的做法是把图片导出到独立目录,Markdown 里只保留相对路径。具体参数名可以查 export_to_markdown 的实现,核心思路就是让图片落盘而不是内嵌。
公式识别是 docling 的一个隐藏优势。理工科论文里大量公式如果用图片形式存在,检索“线性回归公式”会完全检索不到。docling 对公式区域能输出 LaTeX 表达式,这意味着公式可以进入文本检索管道,也能被 LLM 理解。我测试过一版数学教材的 PDF,常见的一元二次方程、矩阵表达式都能转出正确的 LaTeX,但非常复杂的公式偶尔会有符号错位,这类内容建议人工抽查,不能全信。
阅读顺序方面,双栏论文是重点考验。DocLayNet 一般能把双栏的左右顺序排对,但有些 PDF 的文本流本身是错乱的,模型依赖视觉特征去补顺序,偶尔也会出错。遇到这种情况,最有效的办法就是直接看 JSON 里各文本块的阅读顺序字段,手动调整,而不要重新去改 PDF。
3.4 输出内容控制:让 Markdown 更适配使用场景
docling 输出的 Markdown 不是一成不变的,不同使用场景对输出的要求差别很大。
如果你是在做 RAG 知识库,我希望 Markdown 尽量“干净”:保留标题层级、表格结构,但不要把页眉页脚、页码这些噪音混进去。docling 的版面分析模型本身就能识别页眉页脚区块,在结果里可以过滤掉。实践下来,过滤页面噪音后,检索命中率有明显的提升,因为向量化时不会再被大量重复的页眉文字干扰。
如果你是在做文档归档或人工阅读,Markdown 里保留图片路径和 LaTeX 公式会更友好。可以通过配置控制图片导出方式和公式输出格式,让最终文件既完整又不过度膨胀。
还有一个常被忽略的点:docling 的 Markdown 表格用的是 GFM 语法,直接复制到 GitLab、GitHub、Notion 都能正常渲染,但部分老旧的 Markdown 编辑器不支持 GFM 表格,兼容性需要提前确认。如果下游工具不兼容,建议从 JSON 里提取表格数据,转成 CSV 或 HTML 表格再输出。
4. 把 docling 接入 RAG 流水线
4.1 LangChain 与 LlamaIndex 的直接集成
docling 现在有官方或社区提供的集成包,LangChain 和 LlamaIndex 都能直接用,不用自己写胶水代码。
LangChain 里的用法大概是:
from langchain_community.document_loaders import DoclingLoader loader = DoclingLoader(file_path="annual_report.pdf") docs = loader.load()这个 loader 会返回 LangChain 的 Document 对象列表,每个 Document 保留 docling 解析出的内容和元数据(比如页码、来源路径)。之后可以直接接文本切分器,按标题、段落切分,比传统的按字符数硬切效果要好很多。按结构切片的好处很明显:一个表格不会被拦腰切成两半,一个标题下的正文会尽量留在同一个 chunk 里,检索时上下文完整度大幅提升。
LlamaIndex 也类似,安装对应 reader 后就可以把 docling 解析结果变成 LlamaIndex 的 Document 对象,再走索引构建流程。
如果你的技术栈是自己写的检索流程,那也没关系——直接用 docling 的 export_to_dict 拿到结构化 JSON,按照 JSON 里的块类型自行切片,控制力更强。我目前主力用的是 JSON 方案,因为可以在切片前做很多自定义处理,比如合并过短段落、过滤页眉页脚、给表格块添加语义描述。
4.2 批量建库的工程化注意点
从单文件演示到批量建库,中间还有一段工程路要走。第一个要面对的是模型加载。docling 在 Jupyter 或脚本里直接多线程跑是不安全的,模型第一次加载会占用大量内存,多个 worker 同时初始化,很容易把机器搞挂。
我的批量处理方案分三步:预热、串行处理、失败重试。
预热是指先随机挑一份文档跑一遍,让模型加载到内存缓存中,同时验证配置是否正确。预热之后,再做循环或异步处理。如果机器内存足够,可以开两三个 worker 并行,但要注意显存和内存峰值,别开太多。
第二个注意点是断点续跑。处理几千份文档时,中途可能因为网络、磁盘、某个畸形 PDF 崩溃。我的习惯是每处理一份就写一个完成标记,比如在输出目录里生成一个 .done 文件。重启任务时跳过已有标记的文件。这个习惯帮我省掉了无数重复劳动。
第三个注意点是超时控制。个别 PDF 会有损坏结构或超大尺寸,导致单份文档处理几分钟都出不来。批量任务里要给单份文档设置超时,超时就记录下来,最后统一排查。不要让一份坏文档卡住整个队列。
分享一个简单的并发骨架思路:用 concurrent.futures 控制 worker 数量,每份文档在子进程里调用 docling,异常捕获后写日志并继续。做到这一步,批量处理基本就稳了。
4.3 实测效果:从乱码 PDF 到可检索知识库
拿一份 50 页财报 PDF 举例,里面有大量表格、脚注、图表。之前用 PyMuPDF 提取文本后直接建索引,检索“营业收入同比变化”时,返回结果断断续续,因为表格数据被打散成零散文本块,向量相似度根本匹配不到完整语义。
换成 docling 之后,表格被还原成规范的 Markdown 表格,标题层级清晰,脚注和正文区分明确。再按结构切分,每张表格作为一个独立 chunk,查询“营业收入同比变化”时,模型能直接命中那张包含“营业收入”“同比”列名的表格。实测下来,同样的问题集,检索命中率提升了三成以上,生成阶段的答案也准确得多。
这个结果并不意外。RAG 的质量上限由文档解析决定,解析把结构保住了,后续每个环节都会受益;解析把结构丢了,后面再怎么调 Prompt、换向量模型都补不回来。
5. 常见问题与排查技巧实录
5.1 首次运行拉模型很慢怎么办
第一次运行 docling 时,它要从 HuggingFace 下载模型权重,这个下载过程在国内环境经常很慢甚至失败。最快的处理办法是设置镜像环境变量:
export HF_ENDPOINT=https://hf-mirror.com然后再运行你的解析脚本。模型会下载到本地缓存目录,后续再跑就不会有下载问题了。
如果不想依赖镜像,也可以手动下载模型文件放到缓存目录。先运行一次,看日志里提示缺失的模型路径,然后把下载好的权重放进去。第一次配置完成后,这份缓存可以被多个项目共享,不用重复下载。
另外,首次运行如果长时间停在“Loading model”阶段,先检查磁盘空间。模型文件加起来有几个 G,磁盘满了会出现卡死的假象。我之前在 CI 容器里遇到过这个问题,排查了半天才发现是根目录空间不够。
5.2 表格行列错乱、内容丢失怎么定位
表格解析出错时,先别急着调参数,按顺序做定位:
第一步,看 OCR 文本对不对。如果是扫描件,打开 OCR 后的文本层,检查表格里的文字是否都识别出来了。OCR 漏字会导致表格单元格内容丢失,这会连锁影响后面的表格结构识别。
第二步,看版面分析结果。输出 JSON,找到表格区域对应坐标,确认表格区域是否完整覆盖了整个表格。如果区域被截断,表格结构必然不对。区域问题通常是页面干扰导致的,可以尝试裁切页面或调整版面分析参数。
第三步,看表格结构模型输出。确认表头、行、列的识别结果是否符合预期。如果行列关系错乱,把输入页面的 DPI 提高,重新生成 OCR 文本再跑。
第四步,判定属于哪种失败模式。是表头识别错?还是合并单元格丢失?还是表格区域被识别成普通文本?不同失败模式对应的调优手段完全不同,最忌讳的就是不做定位、盲目调参。
我自己踩过的印象最深的坑是:一份表格线条非常浅的扫描 PDF,OCR 文本没问题,但 TableFormer 把整个表格当成无结构文本输出了。最后靠提高扫描分辨率 + 调整裁切范围解决了。这类问题没有通用解法,只能靠逐层排查。
5.3 中文识别不准与 OCR 内存占用过高的处理
中文扫描件的识别不准,多数是因为语言包没配,或者输入图像分辨率不够。先确认 OCR 配置里 lang 参数是否包含 "zh",再确认页面 DPI 不是过低。中文字符笔画密集,分辨率不足时形近字特别容易认错。
如果中文夹杂英文、数字,建议用混合语言包,比如 lang=["en", "zh"]。只配中文的话,英文商标、网址、数字串偶尔会被识别成奇怪内容。
内存和显存占用过高是 OCR 绕不开的问题。EasyOCR 默认会申请较大的显存,如果显存不够,可以降低 OCR 图片的分辨率、关闭并发 worker,或者在配置里限制进程数量。批量任务建议分批处理,不要一次性把所有文档都加载到内存。
CPU 环境跑大规模 OCR 确实折磨人。如果条件允许,把 OCR 和版面分析放到 GPU 机器上,处理速度差距能到十倍以上。没有 GPU 时,可以先用低分辨率跑一遍粗筛,只对命中“疑似扫描页”的页面做高精度重识别,既省时间也省资源。
6. 额外分享:让 docling 效率翻倍的小习惯
6.1 先单页调试,再全量处理
很多人在拿到一批 PDF 后直接写循环处理全部文档,这是最容易翻车的做法。再成熟的解析工具,遇到具体行业的文档也会有意外情况。我的习惯是先从样本里挑一页有代表性的页面,比如包含标题、表格、图片、双栏排版的完整页面,做一次单页调试,把 OCR 语言、表格调优参数、输出格式都确认好,再写批量脚本。
单页调试阶段花十分钟,能避免批量处理完几万份文档后,发现表格全乱、图片没导出的灾难性返工。调试时可以打印出这一页的 Markdown 和 JSON,人工核对每一项是否符合预期。确认没问题再放量,这是我从多次痛苦返工里总结出的经验。
6.2 把版本固定下来,避免依赖静默变化
docling 本身迭代非常快,模型、API、默认参数都在快速变化。同一份代码,可能过三个月跑出来的结果就不一样了。如果你的项目要长期维护,务必在 requirements.txt 里锁定 docling 和 torch 的版本,而不是装最新版。
另外,模型权重文件也会更新。docling 加载模型时用的缓存如果不手动清理,可能一直用旧权重;而换了新环境又可能拉到新权重,前后结果不一致。做文档解析这类对稳定性要求高的任务,版本一致性很重要。我一般在项目根目录放一个 scripts/freeze_versions.sh,把需要锁定的包版本一次性固化,新同事接手或者换机器部署时能少踩很多坑。
这些看起来都是小事,但在实际项目里,往往就是这些小事决定了你是在安心做业务,还是在无穷无尽地与解析工具搏斗。docling 把文档理解的底层问题解决得很好,而我们作为使用者要做的,就是把工程细节处理好,让它的价值充分发挥出来。