做RAG项目,很多人会把重心放在Embedding模型和向量库上,实际做下来才发现,真正决定知识库上限的,是数据导入与解析这一关。尤其PDF和图片,内容明明很丰富,解析成文本时却经常出现乱码、错位、漏行、表格散架,最后检索出来的结果自己都不忍心看。今天这篇是RAG数据导入与解析全攻略的第二篇,专门聊图文与PDF解析,包括OCR怎么选、多模态大模型怎么用,以及九种PDF工具的选型思路。这篇适合正在搭建知识库、做文档问答、或者准备自建本地RAG流程的朋友,也适合被扫描版PDF和表格错位折磨过的人。读完你会得到一套可以直接上手的解析方案,以及很多文档里不写的雷区。
1. 图文与PDF解析:决定RAG上限的第一道关卡
1.1 一张扫描件是怎么进入向量库的
先捋一下RAG知识库里数据的基本链路:原始文件 → 解析成文本 → 分块 → Embedding → 向量库 → 检索 → 生成。很多人以为第一步只是“把文件内容读出来”,实际上PDF和图片的解析远比想象中复杂,因为PDF本身就是一种“最终排版格式”,它并不区分文字、表格、图片。你看到的是整齐的段落,但在PDF底层,可能是一堆文字对象、字体描述、坐标信息和图片流。
最常见的两种情况:一种是有文本层的数字化PDF,这种PDF可以直接用工具抽取文字;另一种是扫描版PDF,每一页本质上是图片,根本没有文本层,必须靠OCR把图像里的文字“认”出来。如果知识库里既需要文本,又需要图表、流程图、扫描合同里的签名位置说明,那就不能只做文字抽取,还要考虑版面结构、表格结构、图片描述。很多人问“RAG知识库能存储图片吗”,答案是能,但真正的问题是:如果图片里的信息没有被解析成可检索的文本或结构化描述,存进去也只是个“聋子耳朵”,召回时根本捞不到它。
1.2 解析质量为什么是RAG最大的隐形瓶颈
大家聊RAG瓶颈,总爱说上下文窗口、向量检索精度、重排模型,其实很多问题的根子都在解析环节。举个我实际遇到的例子:某份PDF里的表格是跨页的,解析工具把表头和正文按文字流顺序硬拼出来,结果“金额”这一列的值串到了“日期”列。用户去检索“上季度支出”,召回的片段里数字对不上,生成答案自然错。这种错误不是Embedding模型能修复的,因为错误发生在源头上。
另一个例子是扫描版PDF,文字层完全缺失,直接上文本提取工具得到的是空列表。这时候选择OCR,如果识别率只有60%,后面分块、向量化和检索都在“垃圾进垃圾出”。我在多个项目里验证过同一个结论:解析质量对最终问答准确率的影响,通常比Embedding模型的选择更大。所以“解析”不是开胃菜,而是主菜。
1.3 先分清楚:RAG知识库、结构知识库和知识图谱不是一回事
在做解析之前,最好先明确知识库的类型。RAG知识库处理的主要是非结构化文档,比如PDF里连续段落、产品手册、技术文档;结构知识库处理的是关系型数据库或CMDB、工单系统里的字段化数据;知识图谱则是用实体和关系组织知识,适合做推理和多跳问答。三者应用场景不同,经常有人混在一起。
这直接影响解析目标:如果做RAG知识库,你需要尽量保留文本的语义和上下文,分块时照顾标题层级;如果做结构化知识库,PDF里的表格就要抽成真实的行列结构,甚至直接输出成JSON;如果准备和知识图谱结合,还要识别文档里的实体、属性、关系,这个阶段就要考虑版面分析和信息抽取。所以解析方案不是“能把文字抽出来就行”,而是先问自己:下游要的是文本,还是结构化数据,还是两者都要。
2. OCR选型和实操:把图片“翻译”成检索文本
2.1 OCR核心流程与两个常见认知误区
OCR看起来是“图片出文字”,实际包含几个步骤:版面分析、文字检测、文字识别、后处理。先找到文字区域,再逐行切割字符,最后通过模型识别成文本。复杂背景、弯曲文字、倾斜角度、表格线都会影响准确率。很多刚接触的朋友会陷入两个误区。
第一个误区是“OCR识别率越高越好,最好99%”。真实场景里,合同表格里的填充手写体、分辨率不足的传真件、印章盖在文字上,这类样本很难做到高识别率。做RAG导入时,我们需要的不是“每个字都完美”,而是“能让检索捞到关键信息”;如果识别结果里夹杂少量错字,可以在分块前做一段清洗,或者把识别置信度较低的片段单独标记,后续用多模态模型二次确认。第二个误区是“本地没显卡就跑不了OCR”。传统OCR引擎和轻量OCR模型在CPU上也能跑,只是速度慢一些。很多扫描PDF是批量任务,可以先小样本试跑,再决定是否上GPU。
2.2 三套主流OCR方案横向对比
就目前主流的开源和云OCR方案,我习惯把它们分成三类:
Tesseract OCR:老牌开源引擎,tesseract下载安装后配合语言包,比如中文用
chi_sim,英文用eng。优点是免费、可离线、社区案例多;缺点是面对复杂版面、中文排版、表格混合内容时,需要自己调很多参数,识别效果往往不如国产专门训练过的OCR。比较适合对数据安全要求高、场景相对简单、印刷体为主的文档。PaddleOCR(PP-OCR系列):百度的开源OCR工具,中文和英文识别效果都比较能打,支持80种以上语言,也支持检测、识别、方向分类全流程。CPU可以跑,有服务端和移动端方案;如果你做RAG且大多是中文文档,推荐直接用这个。另外腾讯也推出过OCR相关模型和云服务,属于商业接口方案,适合不想自己训练模型的团队。云厂商OCR的好处是准确率高、接入简单,坏处是文档内容可能会过第三方服务,敏感数据要慎重。
商业云OCR接口:比如百度OCR、腾讯OCR、阿里云OCR。我见过不少团队直接封装云OCR做PDF解析,效果确实省心,但要注意“file format error”这类报错,通常是传了PDF而不是单页图片、或者图片编码格式不对、文件大小超限。把PDF用渲染工具切成PNG再逐页调用,能避开大部分问题。
选择建议很简单:本地离线优先用PaddleOCR,需要极强兼容性或者不想维护模型时用云OCR,英文为主且部署环境受限可以用Tesseract。
2.3 扫描版PDF落地实操:PaddleOCR完整流程
扫描版PDF的常规流程是:先用PyMuPDF把每一页渲染成图片,再调用PaddleOCR识别,识别结果按页拼接回文本。下面给一个可跑的框架:
import fitz # PyMuPDF from paddleocr import PaddleOCR import os pdf_path = "scan_doc.pdf" output_dir = "ocr_output" ocr = PaddleOCR(lang='ch', use_angle_cls=True) def render_page_to_image(pdf_path, page_num, dpi=150): doc = fitz.open(pdf_path) page = doc[page_num] pix = page.get_pixmap(dpi=dpi) image_path = f"page_{page_num}.png" pix.save(image_path) return image_path def extract_text_from_page(pdf_path, page_num): image_path = render_page_to_image(pdf_path, page_num) result = ocr.ocr(image_path, cls=True) lines = [] for res in result: for line in res: text = line[1][0] lines.append(text) return "\n".join(lines)我特别说明两点:渲染分辨率一般用150 DPI,低于这个容易漏字,太高了又会让OCR慢很多,扫描原件本身不清晰时调到200 DPI即可,再高收益有限;PaddleOCR的接口在不同版本里有差异,老版本ocr.ocr(img, cls=True)能直接跑,新版本可能需要传ocr.predict,写代码前先确认版本,这点在官方文档里很容易找到。识别之后不要立刻丢弃原始图片,保留截图关联信息,后续如果出现“检索到但答案不对”,还能人工去对照。
2.4 我踩过的OCR坑:韩文识别、file format error、中文乱码
先聊一个很典型的问题:有人搜到这样的代码片段,用PaddleOCR识别韩文却识别不了。
from paddlex import create_pipeline pipeline = create_pipeline("OCR") # 但直接默认跑的通常是中文/英文模型这是因为PaddleOCR默认模型不一定包含韩文。如果你需要识别韩文,需要显式指定语言参数,比如在PaddleOCR里用lang='korean'或者加载对应多语言模型。新版PaddleX如果只创建默认OCR pipeline,很可能只支持中英,遇到韩文内容输出乱码,别怀疑是你图片格式的问题,先看语言模型对不对。
再聊百度OCR云接口的“file format error”。这个问题我遇到太多次,原因大多是直接把PDF二进制流丢给接口。云OCR接口通常不允许直接传PDF文件,除非你确认该接口明确支持PDF参数。正确做法是先把PDF页面渲染成PNG、JPG或BMP,再转Base64请求识别。注意图片大小如果超过接口上限,需要做压缩或裁切,否则还会继续报格式错误。
中文乱码的问题,多发生在Tesseract上。装了chi_sim语言包后还是乱码,通常有两个原因:一是文档用的是繁体或特殊字体,只装了简体中文包;二是没设置正确的PSM模式,导致页面方向判断错误。建议先用PaddleOCR中文模型做对比测试,如果PaddleOCR效果好,就不要再纠结Tesseract调参了,毕竟工具是服务于项目的。
3. 多模态大模型:复杂图表解析的另一条路
3.1 OCR也有天花板,多模态补上“语义理解”这块拼图
OCR本质上是“像素到字符”的转换,它能告诉你图片里写着什么,但不一定理解这个内容的排版含义和逻辑。比如一张柱状图,OCR可以识别出轴上数字,但很难告诉你“哪个品类在增长最快”;再比如带合并单元格的复杂表格,OCR按坐标切出的文字流经常错位。
多模态大模型则直接把整张图当作输入,输出自然语言或结构化结果。它可以读懂图表语境,能把柱状图转成文字描述,能把复杂表格转成Markdown,还能判断一个页面哪些信息值得保留。这在RAG导入中很有价值,尤其是文档里包含大量截图、流程框图、信息图时。你可能问:那是不是每个页面都应该丢给多模态模型?我的经验是:不一定,后面会讲一个混合策略。
3.2 三种用法:图文双层解析、表格转Markdown、页面筛选
我测试过多模态模型在RAG导入中的几种用法,效果最明显的是这三个:
第一,图文双层解析。把整页图片发给多模态模型,让它先输出一段页面内容摘要或结构化描述,同时再用OCR把识别文本抠出来。这样既保留了原文的精确字句,又多了一份“图片讲了什么”的语义说明。最终把两份结果合并成一个片段,检索时命中率和答案完整性都会提升。
第二,复杂表格转Markdown。传统工具遇到合并单元格、跨行表头容易把表格拆散。多模态模型可以直接下达“请把这张表格转换成Markdown格式,不要丢失表头和行列关系”的指令,输出的结果比很多规则工具还要规整。当然,这依赖模型的上下文能力和表格图本身的分辨率,如果表格页面字太小,最好先切成区域再让模型识别。
第三,页面筛选。不是所有PDF页面都应该进知识库。封面、目录页、空白页、只有一张大图的页面,往往是无用噪声。多模态模型可以判断页面是否为有意义的内容页,并跳过无价值页面。这一步可以在批量解析之前完成,减少后续处理量。
3.3 模型选型与成本控制建议
多模态大模型分开源和商用两类。开源的有Qwen-VL系列、MiniCPM-V等,可以本地部署,数据不出内网,适合文档内容敏感的场景;商用闭源模型包括GPT-4V、Claude、Gemini系列等,如果你已经拥有对应的API调用权限,直接调用最省事,效果也稳定。选型标准主要看三点:中文识别能力、表格结构化能力、单页处理延迟。
成本要提前算,我举个例子:解析一本1000页的PDF,如果每页都调用多模态大模型,按按单页处理的token和图片量估算,费用可能比Embedding高一个数量级。所以不要一刀切。我用的策略是“规则优先,OCR兜底,多模态补盲区”:先尝试用传统工具抽取文本,遇到文本缺失或者复杂版面时再用OCR,只有图片占比高、表格结构极其复杂或信息图密集时才交给多模态模型。这样可以控制成本,也不会把整条链路拖得太慢。
实际操作中,多模态模型的输出不稳定,我建议对输出格式做校验后解析,比如要求模型只返回Markdown正文,然后对返回结果做格式检测,不符合就重试一次。还要注意幻觉问题,它可能在识别不清时“脑补”内容,因此多模态输出结果最好标注“由视觉模型生成”,在检索排序上适当降权。
4. PDF解析九种工具选型与避坑实录
4.1 先把九种工具分分类:表格速览
市面上PDF解析工具非常多,我选了几类有代表性的,按使用场景分成三组。下表是我在项目里常用到的九个工具,每个我都实际跑过,优缺点比较直接。
| 工具 | 定位 | 擅长场景 | 主要缺点 | 安装/部署难度 |
|---|---|---|---|---|
| pdfplumber | 文本与表格抽取 | 规则文本、有线表格、文字坐标 | 扫描件无法直接抽取 | pip安装简单 |
| PyMuPDF | 高效率文本/渲染 | 大批量抽文本、按页转图 | 复杂表格结构需要二次处理 | pip安装简单 |
| pypdf | 轻量PDF处理 | 简单文本、合并拆分、元数据 | 输出顺序不稳定,表格无力 | pip安装简单 |
| pdfminer.six | 底层解析 | 需要字符坐标、自定义解析规则 | 参数多,上手成本偏高 | pip安装简单 |
| camelot | 表格抽取 | 有线表格、规则报表 | 依赖Ghostscript,无边框表格效果差 | 需系统级依赖 |
| tabula-py | 表格抽取 | 边界清晰的表格,直接输出DataFrame | 依赖Java环境 | 需Java |
| unstructured | 文档解析pipeline | 统一处理多种文档格式并分块 | 依赖较重,某些解析后端易冲突 | pip可用,Docker更稳 |
| docling | 文档结构化转换 | 学术论文、富文本、复杂版面 | 扫描版需要另配OCR | pip+模型下载 |
| marker | PDF转Markdown | 高质量转换整本PDF | 资源占用高,中文本地效果依赖模型 | 需下载模型,推荐GPU |
这个表格不意味着某个工具一定比另一个强。pdfplumber和PyMuPDF是我个人最常用的组合,前者擅长拿文字和表格,后者擅长渲染页面和快速扫描文本流。unstructured和docling这类工具做的是“全家桶”式解析,把文件类型识别、抽取、分块都集成起来,适合想快速搭一套RAG数据管线的团队。marker则更适合你想把PDF直接变成干净Markdown的场景,但它的资源和部署成本要提前预留。
4.2 选型决策:没有万能工具,只有合适场景
我的选型决策很简单,先回答四个问题:PDF有没有文本层?内容以什么为主?表格占多少比重?最终输出是纯文本还是结构化数据?
如果PDF是数字化生成且有文本层,用pdfplumber或PyMuPDF做第一遍抽取就够了。如果文本抽取结果为空或者每页只有几个字,说明是扫描版,就需要接OCR。如果内容是财务报告、实验数据、问卷结果这类表格密集文档,建议主用camelot或tabula-py,它们能按行列表格结构输出,而不是散落的文字流。如果PDF版面非常复杂,比如双栏论文、图文混排、带页眉页脚页码,用unstructured或docling能减少很多工作量。如果最终希望得到高质量Markdown等语义化程度较高的结果,marker是值得尝试的选项。
不要在一开始就追求把所有工具都集成到流程里。我建议先用十页左右的样本,人工看一遍解析结果,再决定哪几个工具配合。工具选型没有标准答案,但有一条经验:能用文本层解决的问题就不要先上OCR,能用规则解决的问题就不要先上模型。这句话能帮你避免很多无效投入。
4.3 混合流程参考:pdfplumber + PaddleOCR + camelot
我目前比较推荐的解析流程是“分水岭式混合解析”:对每一页,先尝试文本层抽取,如果文本量足够,直接保留;如果文本量过少,就渲染成图片走OCR;如果检测到表格结构,再用专门的表格抽取工具。下面是一个骨架代码示例:
import pdfplumber import fitz from paddleocr import PaddleOCR import pandas as pd pdf_path = "data.pdf" ocr = PaddleOCR(lang='ch') def page_has_text(page_text): # 规则:页面有效文本少于20个字符,视为无文本层或噪声页 return len((page_text or "").strip()) >= 20 def table_markdown(tables): md = [] for table in tables: df = pd.DataFrame(table.extract()) md.append(df.to_markdown()) return "\n\n".join(md) with pdfplumber.open(pdf_path) as pdf: for i, page in enumerate(pdf.pages): raw_text = page.extract_text() or "" page_tables = page.extract_tables() if page_has_text(raw_text) and not page_tables: # 普通文本页直接记录 print(i, raw_text) elif page_tables: # 有表格的页面:文本 + 表格Markdown print(i, raw_text) print(table_markdown(page_tables)) else: # 无文本层的页面:走OCR doc = fitz.open(pdf_path) pix = doc[i].get_pixmap(dpi=150) image_path = f"page_{i}.png" pix.save(image_path) result = ocr.ocr(image_path, cls=True) lines = [] for res in result: for line in res: lines.append(line[1][0]) print(i, "\n".join(lines))这段代码只是一个演示框架,实际项目里还要加缓存、断点续跑、异常重试。我为什么会把表格单独拎出来?因为page.extract_text()会把表格里的文字按坐标顺序输出,行列关系几乎一定被打乱,而extract_tables()能保持表格结构。把表格转成Markdown再进RAG,检索到某个表格时还能保留上下文,效果比一坨文字流好很多。如果你用的是camelot,对有线表格的识别会更准,但需要系统安装Ghostscript;tabula-py也可以输出DataFrame,但要求Java环境。我选哪一款?如果只是少数几张表,pdfplumber内置提取就够用,表格量大且格式统一,我才会引入camelot或tabula-py。
4.4 九个工具各自的坑位提醒
pdfplumber:它把页面对象封装得很好,但遇到文件带权限密码时会直接报错,所以先用PyMuPDF或pikepdf解密再去解析。对无边框表格,它的extract_tables会识别成一行,这时候要么开启表格线绘制,要么改用camelot的stream模式。
PyMuPDF:它的文本抽取非常快,但文字顺序不一定符合人眼阅读顺序。尤其双栏PDF,左边一栏和右边一栏的内容会被混在一起。解决方法是先按坐标分块,按x坐标归并到左/右区域,再按y坐标从上到下排序。
pypdf:适合做简单操作。它不像pdfplumber那样方便拿表格,输出文本时也可能丢掉空格和换行。在RAG导入里,我很少单独用它做正文抽取,更多是用来读取页数、加密信息和合并文档。
pdfminer.six:底层解析能力很强,但使用成本也高。要得到干净文本,通常需要调LAParams、控制char_margin和line_margin,否则句子的空格会被错误切分。除非你有自定义解析需求,否则不建议作为首选。
camelot:对有线表格非常好用,但对无线表格、图像型表格识别不稳定。安装时要装Ghostscript,很多Windows环境容易栽在这一步。如果只处理少量表格,成本不划算。
tabula-py:依赖Java,服务器上如果没有JRE就没法用。它适合表格边界清晰的PDF,对扫描件则无能为力,别指望它直接读图片里的表格。
unstructured:功能很全,但它会拉起多个依赖包,不同版本之间偶尔有冲突,建议直接用官方Docker镜像跑,避免折腾。它在处理复杂PDF时会把图片区域识别为元素,但不会自动对图片内容做OCR,需要配合OCR后端。
docling:是IBM开源的工具,我在学术论文和富文本PDF上试过,版面还原做得不错。但新版本会下载模型文件,有些模型体积偏大,离线环境要提前准备。
marker:用深度学习模型把PDF转成Markdown,效果非常惊艳,但需要下载模型且建议GPU推理。中文文档的效果取决于基础模型,不一定比OCR流水线强。我用它在英文技术手册上效果最好,中文扫描件还是更信任PaddleOCR链路。
不要指望一个工具能解决所有问题。RAG数据导入的真实情况是:你拿到的文件来源五花八门,有Word生成的PDF、有打印机扫描件、有网页另存的PDF、有CAD导出图纸。每个文件的“内部结构”都不一样,解析本身就是个试错过程。
4.5 常见问题速查表
下面这些问题是我在项目群和实际工作中被反复问到的,整理了速查表:
| 问题 | 可能原因 | 解决思路 |
|---|---|---|
| PDF解析结果为空 | 扫描版PDF无文本层 | 用PyMuPDF渲染成图片,再走OCR |
| 中文内容全是乱码 | 字体没有嵌入PDF,或Tesseract语言包不匹配 | 检查是否嵌入字体,换PaddleOCR中文模型 |
| 表格数字错位 | 文本抽取丢行列结构 | 用pdfplumber.extract_tables或camelot |
| PDF打开提示加密 | 设置了打开/编辑密码 | 用pikepdf或qpdf解密后再解析 |
| 同一段文字顺序颠倒 | 双栏或复杂版式按流式读取 | 按坐标排序重新组织 |
| 图片信息检索不到 | 只抽文本,未处理图片 | 对图片做OCR或接多模态模型描述 |
| OCR接口报file format error | 直接把PDF传给图片识别接口 | 先渲染成PNG/JPG再调用 |
| 韩文/德文识别成乱码 | OCR未加载对应语言模型 | 显式设置lang参数或加载多语言模型 |
另外,有很多人问“有没有本地的RAG文本拆解工具”。上面这些工具绝大部分都能在本地运行,包括pdfplumber、PyMuPDF、PaddleOCR、docling、marker,完全可以把整个解析管线部署在内网。对数据敏感的场景,本地解析加本地向量库就是典型方案。
5. 解析之后的最后一道工序:分块与合并
5.1 为什么推荐解析后统一转Markdown
解析的目的是为了分块和检索,但不同工具输出的格式很不一样:pdfplumber输出纯文本,camelot输出DataFrame,unstructured输出元素列表,多模态模型输出自然语言。如果不统一格式,下游分块逻辑会变得非常混乱。
我推荐在解析层和分块层之间加一个“统一表示层”,格式就用Markdown。表格转Markdown表格,标题用Markdown标题,图片用描述文字,代码块用代码块。为什么选Markdown?因为它对人与模型都可读,而且能保留结构信息。分块时看到##就知道是二级标题,看到|就知道是表格,这比纯文本更容易做出高质量片段。
5.2 给RAG导入加“消毒”步骤:清洗、去重、结构保留
最后一步别急着Embedding,先做一遍数据消毒。我见过太多项目卡在这一步:OCR识别出的文本掺杂了页眉页码、乱码字符、重复水印;扫描版PDF的每一页都被OCR识别出“扫描时间”之类的噪声;从网页另存的PDF里提取出大量导航文字。这些噪声如果不清理,会直接拉低检索质量。
我的做法是设置三条规则:第一,用正则剔除页眉页脚、连续的页码和特殊符号;第二,对重复度高的页面做去重,比如封面和目录页;第三,把标题和正文之间的层级关系显式标注出来,方便分块时按语义切分。做完这三步,再进向量库,召回效果和可解释性都会好很多。这不算复杂,但能省下后面反复调试检索效果的大量时间。
我自己在项目里的体会是:解析环节没有“银弹”,只有不断拿真实文档试错、看结果、再调整规则。遇到扫描版PDF就先渲染几张图肉眼看看,遇到解析错位就先挑出错页面分析工具行为。所有的选型和经验,都是为了在真实数据前少踩几个坑。