做 RAG 项目做到第三个月我越来越确认一件事检索效果的上限往往不是模型决定的而是解析环节决定的。PDF 里排好版的表格、双栏论文、页眉页脚、扫描件任何一环处理不当后面接多少向量化、重排都是白搭。docling 就是我在这个阶段捡到的一个顺手工具——它把 PDF、Word、PPT 和图片统一解析成结构化的 Markdown 和 JSON能跑版面分析、能识别表格、能补 OCR直接替我把“文档到结构化文本”这个最脏最累的活扛掉了。这篇文章写给两类人一类是在搭 RAG 检索链路、被 PDF 解析弄得焦头烂额的后端工程师另一类是经常批量处理合同、论文、行业报告希望把文档变成干净数据的算法同学。我会从安装、核心原理、接口设计、RAG 接入到避坑经验把 docling 完整过一遍尽量讲清楚每一步“为什么这么做”而不只是贴文档。1. 文档解析为什么这么难docling 到底解决了什么1.1 一张 PDF 里藏着的复杂版面很多人第一次接触 PDF 解析以为就是把文字提取出来。真正上手才发现一页 PDF 里有标题、正文、表格、图片、页眉、页脚、脚注还有横跨两栏的论文标题。传统的pdfminer或PyMuPDF直接调用page.get_text()很容易按物理坐标顺序把文字读乱双栏文章会左右两栏混在一起表格文字会和正文连成一片。更麻烦的是扫描件。整个文件根本没有文本层只是一张图必须 OCR。而 OCR 的输出天然就是“一行一行字”没有标题、表格、段落的边界。如果直接把这种平文本拿去切块、做 embedding检索质量会非常不稳定。docling 的定位就是把这些杂活统一接管它把版面分析、阅读顺序还原、表格结构识别、方向校正、OCR 这些能力封装成一条完整 pipeline输出是带结构层级的结果而不是一堆裸文本。1.2 RAG 场景需要“解析器”而不是“提取器”我曾经犯过一个典型错误为了赶进度直接拿正则表达式从 PDF 里抽文本抽完就交给向量库。结果用户问“去年销售额是多少”检索返回的 chunk 把表格拦腰截断数字残缺不全模型自然答错。RAG 的检索质量高度依赖 chunk 质量。如果 chunk 把一个表格从中间切断或者把标题和正文分离那向量化之后语义就不完整。docling 把文档解析成树状结构标题、段落、表格、列表都带着类型和层级信息后面的切分策略就可以做得更聪明。比如表格整体作为一个 chunk标题和紧随其后的段落合并成一个 chunk这样检索命中的上下文才是“完整的一段意思”。这类能力是裸提取工具给不了的。1.3 本地开源模型推理不依赖云端docling 是 IBM 开源的一个文档处理库核心思路是调度多个模型配合工作版面分析模型负责框出页面里的区域TableFormer 负责还原表格结构OCR 后端可以选用 EasyOCR 或者 Tesseract。模型权重默认从 Hugging Face 下载下载之后在本地跑推理不依赖云端 API。这一点对我来说特别重要。很多企业项目对文档内容有保密要求不能把 PDF 传到第三方解析服务里。docling 本地可跑意味着整个解析链路可以闭环在公司内网数据不出域。再加上它是 Python 库可以直接嵌入现有的数据处理流水线不用额外部署一个 HTTP 服务。当然这也意味着你要自己管理模型下载、显存占用和 CPU/GPU 调度后面我会专门讲。2. 快速上手10 分钟内拿到第一个结构化结果2.1 安装与依赖选择docling 支持 Python 3.9 及以上版本建议在一个干净的虚拟环境里安装。我平时用uv建环境省心很多python -m venv .venv source .venv/bin/activate pip install docling[full]这里我强烈建议直接安装[full]版本。如果你只装核心包后面用到 OCR、图表识别时会发现缺了一堆依赖还得回头补装。与其反复折腾不如一步到位。安装过程中可能会拉取 torch、opencv、easyocr 这些比较大依赖网络慢就等一会儿属于正常现象。2.2 三行代码解析一个 PDF安装完成之后第一次解析比我想象中简单得多。核心入口只有一个DocumentConverterfrom docling.document_converter import DocumentConverter converter DocumentConverter() result converter.convert(example.pdf) print(result.document.export_to_markdown())第一行实例化 converter它会在内部初始化默认的版面分析和表格识别模型。第二行convert()读取文件、跑模型推理、还原结构。第三行把结构化文档导出成 Markdown。第一次运行需要下载模型权重后续直接用缓存速度会快很多。我建议你把输出同时保存成 Markdown 和 JSON 两份。Markdown 方便人阅读也方便后续喂给大模型JSON 保留了每种元素的坐标、层级、类型做精细切分和自定义处理时非常有用with open(output.md, w, encodingutf-8) as f: f.write(result.document.export_to_markdown()) with open(output.json, w, encodingutf-8) as f: f.write(result.document.export_to_dict())2.3 支持哪些输入格式docling 不只处理 PDF还支持 Word、PPT、HTML 和图片。我在项目里经常收到一堆.docx和.pptx的会议材料过去要分开写两套解析代码现在直接交给 docling 统一处理。它内部会先把这些格式转成同类中间表示再做版面分析和结构化输出格式保持一致。如果你要传入的是图片直接给图片路径或者图片字节流都可以。对于扫描版 PDFdocling 会自动识别是否需要 OCR对于本身带文本层的高质量 PDF它默认走文本提取路径速度更快。3. 核心能力拆解版面、表格、OCR 与阅读顺序3.1 版面分析先让模型看懂页面结构版面分析是整套 pipeline 的第一步。模型会把页面划分成不同区域并为每个区域标注类型标题、正文、表格、图片、公式、页眉页脚等。每个区域都会生成一个边界框bbox然后系统把这些区域按照阅读顺序重新排序。这一步对双栏论文尤其关键。PDF 本身记录的文本位置是物理坐标如果按坐标顺序读取左栏第一行读完会跳到右栏第一行导致整个文本逻辑混乱。docling 的版面分析先识别出“这一栏是正文、那一栏也是正文”再把同栏内容按从上到下的顺序串起来最后拼接成正确的阅读顺序。注意双栏 PDF 如果绕过版面分析直接提文本几乎必然出现栏位交错。这也是为什么我前面强调别把 docling 当成普通的“提取文字”工具。3.2 OCR 到底什么时候开、什么时候关docling 的 OCR 不是无脑启用的。它默认的策略是如果 PDF 自带文本层就优先用文本层如果检测到扫描件则自动启用 OCR。但在实测中我更喜欢手动控制因为自动判断偶尔会把“图片型 PDF”当成“无文本 PDF”处理或者反过来在有文本层但文本质量极差的情况下浪费 OCR 成本。我一般用PdfPipelineOptions来控制from docling.datamodel.base_models import InputFormat from docling.datamodel.pipeline_options import PdfPipelineOptions, EasyOcrOptions from docling.document_converter import DocumentConverter, PdfFormatOption pipeline_options PdfPipelineOptions() pipeline_options.do_ocr True pipeline_options.ocr_options EasyOcrOptions(force_full_page_ocrTrue) converter DocumentConverter( format_options{ InputFormat.PDF: PdfFormatOption(pipeline_optionspipeline_options) } )do_ocr True表示强制走 OCR。force_full_page_ocr True表示整页图片都做 OCR而不是只对没有文本层的区域做。如果文件是扫描件我建议开启这一项。如果文件本身就是数字版 PDF就关闭 OCR速度会快一个量级。EasyOCR 对印刷体中英文识别效果都不错但速度偏慢。如果你有 GPU记得把 torch 换成 CUDA 版本并确认 OCR 能调用到 GPU。如果没有 GPU批量解析大扫描件会比较煎熬可以考虑改用 Tesseract 后端速度会快一些但识别精度略有下降。3.3 TableFormer表格识别的核心竞争力docling 最让我意外的是表格识别。TableFormer 模型不仅能把表格区域框出来还能把单元格之间的行列关系还原出来输出 Markdown 表格结构。我拿一份带边框的财务报表做过测试它能正确还原多列、多行、表头甚至字段里的换行也处理得不错。解析出的 Markdown 可以直接贴进文档几乎不需要二次修复。表格识别开启方式也是通过PdfPipelineOptions。默认配置下表格结构识别是开启的不需要额外设置。但对于复杂表格像是带斜线表头、跨行合并单元格、无边框表格输出会有小瑕疵。遇到这类极端格式我通常会在导出后用一段规则把 Markdown 表格里的坏行清理掉再交给后续流程。整体来看docling 的表格能力已经能覆盖绝大多数业务报表场景远超普通 PDF 库。3.4 方向校正与阅读顺序还原扫描件里偶尔会出现整页旋转 90 度或 180 度的情况。docling 提供了方向校正能力它会判断页面方向并自动旋转避免识别出一堆颠倒文字。在PdfPipelineOptions里相关开关是do_orientation_check一般建议保持默认开启。阅读顺序还原则是把版面分析出的区域排序形成文档的逻辑流。输出结果里每个 block 会带上parent关系形成一棵树。这个树状结构对 RAG 切分极具价值因为你可以根据层级关系判断某个段落属于哪个章节或者哪些内容属于同一个表格块。这也是 docling 与其他“平铺式”解析工具的最大区别。4. 在 RAG 管道中集成 docling 的实战思路4.1 推荐接入方式解析一次复用多次文档解析往往比较耗时尤其是扫描件。我在实际项目中会把 docling 放在文档进入系统的第一个节点解析后把结果落盘成 JSON Lines 文件。后面不管是做测试、调 chunk 策略、还是换 embedding 模型都不需要重新跑解析直接从中间结果继续就行。import json results [] for file_path in file_list: result converter.convert(file_path) doc_dict result.document.export_to_dict() results.append({source: file_path, doc: doc_dict}) with open(parsed_docs.jsonl, w, encodingutf-8) as f: for item in results: f.write(json.dumps(item, ensure_asciiFalse) \n)这样设计解析和检索解耦效率提升非常明显。我在一个 2000 份合同的数据集上跑过一次纯解析大约需要 1.5 小时但之后测试各种切分和向量化方案只需要读取 JSONL秒级迭代。4.2 按结构切分而不是按字数硬切docling 导出 JSON 内部的字段结构清晰。text、label、prov这些字段会标记每个元素的内容、类型和来源坐标。基于这个结构切分策略可以做得非常精细标题节点单独拎出来作为文档的小节索引。正文段落按段落切段落太长的再按句号切。表格节点整体作为一个 chunk不拆分。列表项合并成一个 chunk避免单条列表项语义不完整。这种做法的好处是检索时命中的 chunk 语义完整。比如用户问“合同中的违约责任条款是什么”检索系统可以精准命中“违约责任”标题下的整段内容而不是被截断的碎片。4.3 表格与正文分开建索引我在实际项目里发现表格块和正文块混在一起建索引效果并不理想。因为表格的文本密度高、含义紧凑向量化之后和普通段落差异很大。更稳妥的做法是正文走常规 embedding 索引表格单独建一个索引或者给表格块加一个typetable的元数据字段在检索时做过滤或加权。docling 输出 JSON 时每个元素都有类型信息天然适合做这种区分。你只需要在构造 chunk 时把label字段透传到元数据里。这样查询“年度营收对比”时就可以优先召回表格块查询“报告结论”时则优先召回正文块。这个策略很实用而且实现成本很低。4.4 与 LangChain 等框架的对接如果你在用 LangChain 或 LlamaIndex也不需要额外开发太多胶水代码。把 docling 的 Markdown 输出作为文档内容把结构元数据作为metadata接入现有的DocumentLoader或者自定义 loader 即可。我自己更倾向于不绑定框架的通用做法解析、切分、向量化都自己控制把中间结果存成 JSONL这样框架升级时不受影响。5. 实测心得与避坑清单5.1 我在真实项目里踩过的几个坑第一个坑是扫描件没开 OCR。一开始我用默认配置去解析一批历史合同结果导出的 Markdown 是空的。原因是这批合同是扫描件docling 默认没有强制 OCR。后来我统一在PdfPipelineOptions里把do_ocr设为 True问题解决。第二个坑是批量处理时反复加载模型。如果对每个文件都新建一个DocumentConverter模型权重会反复加载耗时翻倍。正确做法是全局复用一个 converter 实例docling 内部的模型缓存可以复用速度提升明显。第三个坑是输出 Markdown 里的表格缩进。docling 导出的表格在某些嵌套场景下会带较多空白缩进直接喂给大模型会占用较多 token。后来我在预处理阶段加了清理逻辑把表格前后的无效空白去除token 消耗降了不少。5.2 性能调优的几条路线控制并发PdfPipelineOptions里有num_processes参数可以指定 CPU 核心数。多核机器上批量解析合理设置能显著提速。GPU 加速OCR 和 TableFormer 都支持 CUDA。确认 torch 是 CUDA 版本然后让模型迁移到 GPU 即可。模型缓存模型权重默认缓存在本机第二次运行不会重复下载。如果你在内网环境可以把模型目录提前放到共享盘多台机器共用避免每台机器都下载一遍。5.3 什么场景适合 docling什么场景不适合适合的场景包括RAG 索引前的文档清洗、扫描版合同和报告处理、包含大量表格的财务报表、跨格式的 Word/PPT/PDF 统一解析。docling 在这些场景下能大大节省人力。不太适合的场景我认为是数学公式密集的论文。docling 对公式的支持比较基础无法输出 LaTeX 级别的公式结构。如果核心诉求是识公式还是得用专门的公式识别工具。另外手写笔记为主的文档docling 的效果也会受限。它的 OCR 面向印刷体设计手写内容识别率不稳定不建议作为主要方案。6. 写在最后我保留 docling 的几个理由文档解析这个环节往往是最容易低估、也最容易翻车的地方。项目上线前所有人都关注模型效果上线后才发现问题出在解析阶段表格被切断、双栏乱序、扫描件全是空文本。docling 至少帮我把这些问题收敛到了一个可复现、可调试的工具范围内。我最终保留 docling 而不是换回 PyMuPDF 加正则的方案主要原因是它同时给了我三样东西本地可运行的隐私友好性、直接可用的表格识别、以及保留文档结构的结构化输出。这三样东西组合在一起让 RAG 的切分和检索环节有了更扎实的底座。最后再分享一个我自己用下来的小习惯解析完文档我习惯把 Markdown 和带 bbox 的 JSON 各存一份Markdown 用来喂给 LLM 和展示JSON 用来做程序检索和切分。两份数据各司其职后面再折腾管道时会少很多返工。
