干了这么多年 RAG 项目,最大的感触就是:数据导入做不好,后面一切花里胡哨的调参都是白费。
尤其到了图文和 PDF 这块,很多人第一版 RAG 跑得挺欢,一上生产就拉胯。用户丢进来一份扫描版合同、一本带复杂表格的财报、或者一页设计稿截图,检索结果直接稀碎。问题几乎都出在同一个环节——解析不够细、不够准、不够干净。
这篇文章是 RAG 数据导入与解析系列的第二篇,重点只讲两件事:一是图文内容怎么处理,二是 PDF 怎么选工具。前者涉及传统 OCR 和当前更热的多模态大模型,后者我会把市面上常见的 PDF 解析工具逐个拆开聊,最后给出适合不同业务阶段的选型方案。做知识库、做合规审阅、做客服问答这类场景的朋友,这篇应该能直接帮你少走两周弯路。
1. 图文内容为什么是 RAG 数据导入的头号瓶颈
1.1 你以为的"读 PDF",和 RAG 实际能读到的,差得远
先说个直观体验。PDF 这个格式表面上看是"文档",实际上一打开可能是一堆扫描图、一张画布、或者一段无人认领的二进制流。RAG 系统在做索引之前,必须先把 PDF 转成机器可读的纯文本,这步做不好,后面向量化、召回全是空中楼阁。
很多团队第一版方案是怎么做的?直接调一款开源库,pdfplumber或者pypdf一把梭,跑完看输出:文字倒是出来了,但顺序是乱的。多栏排版读成了左右穿插,表格里的数字和表头对不上号,页眉页脚混进正文,甚至扫描件输出的是空文本——看起来流程通了,实际上数据是脏的。
更隐蔽的问题是"语义单元"被切断。RAG 检索靠的是切块后向量化,如果一行被截成两块、一个表格被切成七八段碎片,后续提问"去年营收增速是多少",检索模块根本定位不到完整上下文。所以行业里常说一句话:RAG 的瓶颈不在模型,在解析。
1.2 哪些内容必须走 OCR 或多模态方案
不是所有 PDF 都需要上重武器。我习惯把数据分成三类来判断处理策略:
- 文本型 PDF:原本就是文字流,鼠标能选中、能复制,直接用文本抽取工具处理即可,速度快、成本低。
- 扫描型 PDF / 图片型 PDF:整页就是一张图,文字是像素不是字符,必须走 OCR,否则吐出来的就是空白。
- 混合型 PDF / 复杂版式 PDF:既有文字层又有图片,或者多栏、带表格、带水印、带印章,这类最麻烦,需要版面分析后分区块处理,常常要 OCR 和文本抽取配合使用。
判断一件 PDF 需不需要 OCR,有个很笨但很准的办法:Ctrl+A全选再复制,能复制出文字说明有文本层,复制不出东西基本就是图片。或者用pdfinfo看一下页面的/Font和/Image资源,纯图像页通常没有 Font 信息。
很多 RAG 项目跑在精细场景里,合同、票据、研报、学术论文、说明书,大量是扫描版或电子签章版。这类数据在入库前不做 OCR,检索效果就是肉眼可见的滑坡。接下来就进入正题,看两套主流方案:传统 OCR 和多模态大模型。
2. 解析方案全景:OCR、多模态大模型与工具选型思路
2.1 传统 OCR 方案:成熟稳定,专治高并发扫描件
传统 OCR 走的是"检测 + 识别"路线,当前主流开源方案包括 Tesseract、PaddleOCR 以及一些商业 SDK。流程上基本是三段式:图像预处理(灰度化、二值化、降噪、倾斜矫正)、文字检测(文本框定位)、文字识别(分类器或序列模型)。
以 PaddleOCR 为例,我实测下来对中文印刷体的识别精度在 95% 以上,对清晰表格结构也有单独的表格识别模型。它的优势很明显:
- 部署可控,可本地离线跑,数据不出内网;
- 单页识别速度快,批量处理能力强;
- 识别结果可控,方便后处理做校对和格式化。
缺点同样不可回避。一是对低质量图片敏感:拍照歪斜、光晕、模糊、半遮挡,识别率掉得厉害。二是版面结构理解弱:它识别出文字框,但分不清哪些是标题、哪些是正文、哪些是表格内容,需要额外做版面分析模块。三是对非文字元素束手无策:图标、趋势线、柱状图里的数量关系,它一个字都读不出来。
Tesseract 是老牌选手,胜在轻量、免费、多语言支持好,但中文字体和复杂版式的效果不如 PaddleOCR 细腻,我一般只在快捷脚本或工具链里顺手用一下,不当作主力方案。
2.2 多模态大模型方案:能"看"懂版面,是复杂文档的兜底
多模态大模型是这两年解析领域最大的变量。这类模型输入是一张图片或一页版面,输出则是自然语言、Markdown、甚至结构化 JSON。因为具备跨模态理解能力,它们的优势是"全局理解":
- 对复杂版式有感知:多栏、插图、表格、页眉页脚、脚注,能按语义重新组织;
- 能理解图表:趋势、柱状图、饼图的含义,可以直接描述出来;
- 抗干扰能力强:低清晰度、水印、印章遮挡下仍有不错的识别容错;
- 输出结构化:可以直接产出 Markdown 或 JSON,方便后续切块和入库。
代价是成本、速度和数据合规。每页调用一次大模型 API,按页计费,几万页文档跑下来费用可观;速度上远不如本地 OCR 瞬时完成;敏感数据走云端还要考虑合规问题。
实际落地时我不会让它们在"二选一"的关系里竞争,而是组合使用。下一节列出一套组合策略。
2.3 主流选型思路:分层组合,别梭哈一种方案
我自己在项目里跑通的思路是"分层处理":
- 第一步用传统手段快速分流:文本型 PDF 直接文本抽取;扫描型、混合型进 OCR 管线;
- 第二步用版面分析做结构化:识别标题层级、段落、表格区域,重新组装顺序;
- 第三步用多模态大模型兜复杂场景:页面里有图表、有复杂表格、有印章干扰,或 OCR 后文本明显乱序时,再调用大模型重排,输出 Markdown/JSON;
- 第四步做清洗与核对:统一编码、去页眉页脚、修复断行,必要时校对关键字段。
这套思路的好处是把成本花在刀刃上。几百页整洁的多栏 PDF,OCR 就够;几页极端复杂的财报、手写批注件,再动用大模型。两种方案不冲突,配合起来才是完整的"全文解析"能力。
3. 九种 PDF 解析工具逐一点评与选型
3.1 九种工具全景对比
我把实际项目里比较常用的 PDF 解析工具整理成了一张对照表,方便大家先建立整体认知。选定原则是它们都需要覆盖不同场景:文本抽取、版面分析、OCR、转换格式等。
| 工具 | 核心能力 | 适用场景 | 上手难度 | 典型限制 |
|---|---|---|---|---|
| PyMuPDF (fitz) | 文本抽取、页面渲染、链接提取 | 各种语言的高速文本抓取 | 低 | 复杂表格结构化能力弱 |
| pdfplumber | 文本、表格、坐标定位 | 表格抽取、精细坐标分析 | 低 | 大文件速度偏慢 |
| pypdf | 拆合、加密、基础文本抽取 | PDF 预处理和元数据操作 | 低 | 抽取能力相对基础 |
| PDFMiner.six | 文本精确定位、字符级解析 | 研究型/学术文本处理 | 中 | 接口偏底层,学习成本略高 |
| pdf2image | PDF 转图片 | 与 OCR 管线配合 | 低 | 只转图,不识别 |
| PaddleOCR | OCR文字识别、表格识别 | 扫描件管线主力 | 中 | 依赖国内外包安装,Docker 更省事 |
| Tesseract | OCR 文字识别、多语言 | 轻量级/脚本化场景 | 低 | 中文复杂版面效果不如 PaddleOCR |
| OCRmyPDF | OCR 后生成可搜索 PDF | 批量扫描件清洗建档 | 低 | 本质是 OCR 与 PDF 工程的组合 |
| Marker / MinerU | 版面检测 + 解析成 Markdown | 复杂版式PDF转结构化文本 | 中高 | 模型资源占用偏高,首次下载模型偏慢 |
这九个工具覆盖了从"简单抽取"到"版面理解"的完整梯度。下面按功能角色分三个梯队细说。
3.2 文本抽取层:PyMuPDF、pdfplumber、pypdf、PDFMiner.six
PyMuPDF(fitz)是我最常用的"万金油"。它不怎么挑 PDF,解析速度快到离谱,批量处理几千页文件也不卡。除了文本抽取,还能做页面渲染、图片提取、链接抓取。如果只是想快速抽文本入库,page.get_text()一行代码就完事。但要注意,它对表格直接提取会丢结构,输出的是按坐标排列的文本流,表格线框信息基本不留。
pdfplumber在需要精确定位时更好用。它能把每个词、每行文字的坐标暴露给你,还能提取表格单元格的内容,是做"栏目版式分析"时最重要的底层工具。缺点也很明显:处理几百页以上的长文档明显变慢,内存占用偏高。
pypdf(原 PyPDF2)是定位在 PDF 文件工程层的工具。拆分合并、加解密、旋转页面、读取元数据这些操作用它很顺手。文本抽取功能也有,但深度有限,复杂版式下容易乱,我不太建议单独靠它做 RAG 数据清洗,更适合做预处理环节。
PDFMiner.six算是老牌精确派,它按字符级解析,能给出详细的布局信息(LAParams),适合学术论文这种排版相对规整的分析。问题是 API 设计偏底层,代码量偏大。封装好的外部工具很多也依赖它做底层,比如pdfplumber就建立在 PDFMiner 之上。
3.3 图像化辅助层:pdf2image、OCRmyPDF
pdf2image本身不做识别,只是一个"PDF 渲染成图片"的转换器,依赖 poppler。但它在管线里的地位不可小觑——所有想走 OCR 的 PDF,第一步几乎都要经过它。配合 PaddleOCR 或 Tesseract,一行代码把页面变成 PNG,再送进识别模型。
有一个细节值得注意:默认渲染 DPI 是 300,但扫描件如果本身清晰度低,调再高也没用;如果原 PDF 是矢量文字,渲染 DPI 调到 200~300 就能获得很好的识别底图。参数不是固定越大越好,过高的 DPI 会显著拖慢管线。
OCRmyPDF的定位非常明确:给扫描件加上 OCR 后的文本层,生成"可搜索 PDF"。它把 Tesseract 或 OCR 引擎和 PDF 加工流程封装好了,输出还是 PDF 文件,只是文字可以选中、搜索。对 RAG 项目来说,这一步的价值是——你在上游统一保留 PDF 格式,下游再用任意工具二次抽取,效果都会好很多。
3.4 版面感知层:PaddleOCR、Tesseract、Marker/MinerU
PaddleOCR在国内中文场景基本是事实标准。它不止做文字识别,还带版面分析模型(PP-Structure)、表格识别模型(TableRec)。我实践下来的感受是:印刷体中文识别精度很高,表格还原成 HTML/Excel 也像模像样。安装时建议用 Docker 或官方脚本,比手动配依赖省心。
注意它有模型较多,首次使用要下载检测模型和识别模型,离线环境需要预装模型包。
Tesseract如果只做轻量 OCR,tessdata 里简体中文语言包配好就能用。它的上限对清晰印刷体来说还可以,但对低质量图片和多栏版式表现一般。适合工具链里一个"快速判断是否有文字层"的补充角色,或者不需要高精度的场景。
Marker和MinerU是近年开源社区里专注做"PDF 转 Markdown"的工具,核心是把版面检测、公式识别、表格还原串联起来。对论文、财报、教材这类结构化强的文档,输出质量很高,几乎达到了多模态大模型的水平。代价是模型多、依赖重、推理时间较长,小内存机器上跑起来会很吃力。如果团队资源充足,这两个工具可以作为数据管线的"增强环节",尤其适合追求结构化文本的场景。
3.5 工具选型组合参考
不同项目阶段,我的推荐组合略有差异:
- 轻量验证/PoC 阶段:PyMuPDF + pdfplumber + Tesseract。上手最快,半天能出原型;
- 中文扫描件主力阶段:pdf2image + PaddleOCR + 自研版面清洗逻辑。覆盖大部分合同、票据类需求;
- 复杂文档结构化阶段:Marker 或 MinerU 预处理,输出 Markdown 再进切块;特别难啃的页面再调多模态大模型兜底;
- 已入生产、批量稳定运行阶段:统一先用 OCRmyPDF 给全部 PDF 加文本层,再做后续路由,这样全流程稳定、可复现。
4. 完整实操流程:从一份"扫描盖章合同"到可检索的知识库
4.1 流水线设计
以下是我实际项目里用于处理"扫描版购销合同 + 附件表格"的完整流水线。目标:把一份只有图片层的 PDF,转换成带结构的 Markdown 文本,切块后写入知识库。
流程环节:PDF 检测 → 页面渲染 → OCR/版面分析 → 结构化组装 → 清洗入库。
具体步骤:
- 用
pypdf读取页面数量和元数据,判断是否加密。加密文件先解密,有权限限制的先解除限制; - 用
pdf2image将页面渲染成 300 DPI 的 PNG 图片; - 用
PaddleOCR进行版面分析和文字识别,得到每块文字的位置、内容、类别(标题/正文/表格); - 用
pdfplumber对原本就有文本层的页面做交叉验证,补全 OCR 识别不佳的数字和英文; - 根据版面坐标重新组织阅读顺序,把表格识别结果转成 Markdown 表格;
- 清洗:去掉页眉页脚、印章识别出的无关字、乱码字符;
- 按语义切块(这里按章节与表格边界切),送入 Embedding 模型并写入向量库。
4.2 关键代码示例(Python 伪码风格)
下面是我实际在用的一个简化版解析函数,保留了核心步骤和注释,方便直接参考改造:
import fitz # PyMuPDF from pdf2image import convert_from_path from paddleocr import PaddleOCR import pdfplumber ocr = PaddleOCR(use_angle_cls=True, lang="ch") def parse_pdf_to_text(pdf_path, dpi=300): # 1. 判断是否有文本层 doc = fitz.open(pdf_path) text_pages = [] image_pages = [] for i, page in enumerate(doc): text = page.get_text().strip() if len(text) > 10: text_pages.append(i) else: image_pages.append(i) print(f"文本页 {len(text_pages)} 张,图像页 {len(image_pages)} 张") # 2. 对图像页渲染 + OCR images = convert_from_path(pdf_path, dpi=dpi) ocr_results = {} for idx in image_pages: img = images[idx] result = ocr.ocr(img, cls=True) # result 中每一项是 [坐标框, (文字, 置信度)] lines = [line[1][0] for line in result[0]] ocr_results[idx] = "\n".join(lines) # 3. 文本页直接提取 + 表格交叉验证 with pdfplumber.open(pdf_path) as pdf: for idx in text_pages: page = pdf.pages[idx] page_text = page.extract_text() or "" tables = page.extract_tables() # 表格转成 markdown 格式补进文本 if tables: page_text += "\n\n" + format_tables_as_markdown(tables) ocr_results[idx] = page_text # 4. 按页顺序合并输出 full_text = "\n\n".join( ocr_results[i] for i in range(len(images)) if i in ocr_results ) return full_text这份代码在生产里能跑通 80% 的场景。剩下的 20%——比如带复杂印章、合同有手写批注、表格跨页断开的极端情况——我会再挂一个多模态大模型的"兜底路由"。
4.3 兜底路由:什么情况下调用多模态大模型
当一个页面的 OCR 置信度普遍低于 0.85,或者版面分析后同一个标题段落被切成了 5 块,或者页面包含趋势图、组织结构图等非文本元素时,我直接把整页图片发给多模态大模型,要求输出 Markdown 结构。
提示词我常用这样的模板:
你是文档解析助手。请把这张页面完整转换为 Markdown: 1. 保持阅读顺序,不要改变内容; 2. 表格转换为 Markdown 表格; 3. 图片用 [图片] 占位并描述其内容; 4. 不要遗漏页脚页眉之外任何文字。实测下来的效果:复杂表格的还原度明显优于纯 OCR,尤其是带合并单元格、斜线表头、跨行跨列的场景,大模型能按语义重新组织。缺点就是贵和慢,所以只做兜底。
如果成本敏感,还有一个折中的优化:先用 PaddleOCR 的表格识别模块(TableRec)跑一遍,输出 HTML 表格,再接大模型只做"修正",而不是"从零生成",token 消耗能降到原来的十分之一。
4.4 入库前的清洗规范
解析出来的文本直接入库前,得做一轮清洗,否则向量检索会被噪声拖垮。我一般固定做四件事:
- 去页眉页脚:识别重复出现在每页顶部/底部的固定文本块,正则或规则删除;
- 统一编码:罕见的全角/半角、中文标点、破折号变体统一替换;
- 压缩空白:多个空格统一为一个,空行最多保留一个;
- URL、邮箱、电话归一:这些实体信息经常造成误召回,保留但做实体标记,以便后续过滤。
清洗后最好看一眼"干净率"评估指标。我的经验值:如果 100 段文本里仍有 3 段以上乱序或内容残缺,先修解析层,不要急着调检索参数。脏数据进库后,再好的 rerank 模型也救不回来。
5. 常见问题与排查技巧实录
5.1 常见问题速查表
| 问题 | 根因 | 解决方案 |
|---|---|---|
| 文本型 PDF 提取出来是空的 | 页面实际是扫描图,无文本层 | 走 OCR 管线,或先用 OCRmyPDF 加文本层 |
| OCR 结果中单行文字被截断 | 文本框检测粒度太粗/太细 | 调整 PaddleOCR 的det_db_thresh或启用版面分析 |
| 表格数字错位、对不上表头 | 表格跨页或被页眉切断 | 用 pdfplumber 提取表格坐标,分页重排;复杂表走多模态模型 |
| 多栏排版文字左右穿插 | 阅读顺序未经版面分析 | 按坐标块排序,或先用 Marker/MinerU 重排 |
| PDF 打开报错、解析崩溃 | 文件加密或损坏 | 先尝试pypdf解密,损坏文件用 Ghostscript 修复 |
| 大模型输出 Markdown 格式不稳定 | 提示词不够明确 | 固定输出格式 + 强制 JSON Schema 或 Markdown 首尾标记 |
| 批量处理到一半内存爆掉 | 渲染 DPI 过高或文档过大 | 控制并发数,分页渲染,DPI 优先 200 即可 |
| 手写批注/印章干扰正文 | OCR 把印章识别成文字 | 启用手写/印章过滤规则,或用颜色过滤去掉红章后再识别 |
5.2 几个我踩过的坑,提前帮你避开
第一个是"盲目追求高 DPI"。我一开始处理扫描件总把 DPI 拉到 500,结果识别精度没有显著提升,管线速度慢了一半还多,后来固定在 300 左右,速度和效果都理想。清晰度差的扫描件,提 DPI 没有意义,先做一次去噪和对比度增强更重要。
第二个是"表头重复带来的脏数据"。跨页表格每页都会带页眉表头,直接入库会让同一列名反复出现在多个切块里,检索时语义混淆。我的做法是识别表格区域后,只保留首次出现的表头,后续页的重复表头直接丢弃,只保留数据行。
第三个是"切块把表格打断了"。很多切块策略按字数硬切,表格被切成碎片。后来我改成了"结构感知切块":表格整体作为一个语义块,不跨块切分;段落内按句子边界切;标题独立成块。这个改动让召回准确率提升了近 8 个百分点。
第四个是关于印章。红章在中文场景太常见了,OCR 会把它识别成零散的文字碎片。我通常先把图片转成 HSV 色彩空间,把红色区域单独抠出来做遮罩,识别时跳过这块区域,再把章信息作为独立字段存储(用于验真、归档)。操作上不算复杂,但对检索纯度帮助很大。
5.3 RAG 知识库、向量库与结构知识库的边界
聊解析时经常有人混淆概念:RAG 知识库、向量库、传统结构知识库(含知识图谱)分别是什么、什么时候用?
从解析的角度简单归纳:
- RAG 知识库/向量库:存放解析清洗后的非结构化文本向量。适合"语义相似检索",回答开放问题。
- 结构知识库(如知识图谱):存放实体、关系、属性,适合"精确跳转、多跳推理"。构建成本高,通常需要从结构化文本中抽取实体关系。
- 两者不是二选一:成熟项目里,非结构化文本给 RAG 用,关键实体关系同时写进图谱,用于事实校验。解析层的质量同时决定了两条链路的上限。
这也是为什么我反复强调把解析做到"结构化"而非"纯文本":同一份文档,解析出 Markdown 结构,能服务 RAG 的切块;抽取出的表格数据又可以同步进结构化库,一举两得。
6. 这篇内容之后还能怎么扩展
写这篇的时候,我把很多 RAG 项目里"解析层"的经验汇总了一遍。还没展开的后续方向包括:复杂表格的端到端还原、公式与化学式的识别、手写文档的专项模型、以及如何把解析结果做自动质检——如何判断解析出来的文本真的"够好",这个对生产环境的意义非常大,值得琢磨。
我个人的体会是:图文 PDF 解析没有"统一的正确答案",只有"适合当前业务形态的组合方案"。轻量场景别迷信多模态大模型,复杂场景也别硬扛纯 OCR。先用分层策略搭出骨架,再用真实数据反向修正,是最稳妥的路径。
如果这篇文章对你有帮助,后续我可以把"表格还原与切块细节"单独写一篇,展开聊聊那些跨页表格、合并单元格、斜线表头的处理细节。