ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RAG知识库图文与PDF解析全攻略:OCR、多模态模型与九大工具选型

RAG知识库图文与PDF解析全攻略:OCR、多模态模型与九大工具选型

我做RAG项目这几年,最常被问的一句话就是:图片和PDF到底怎么喂给知识库。图片只能靠OCR甚至多模态大模型去“读懂”,PDF更麻烦,原生文本、扫描图片、混合版面三种形态能让人崩溃。这篇接上之前聊过的文本类数据导入,专啃硬骨头:图文与PDF解析的完整方案,包括OCR技术的取舍、多模态大模型介入的时机,以及九种PDF解析工具的实际选型经验。内容偏实操,适合正在搭知识库、想把手头的合同发票说明书论文之类文档塞进RAG检索体系的朋友参考。

1. 图文与PDF解析:先把要打的仗分清楚

1.1 解析质量决定RAG效果的上限

很多人把RAG效果不好归咎于向量模型、重排模型选得不对,但在我实际排查过的项目里,至少有一半的“答非所问”,根源不是检索链路,而是解析阶段已经把信息丢了。

举个例子,某产品手册里有大量截图,截图下方带一句说明文字。如果只做原生文本抽取,RAG拿到的是那一句孤零零的图注,用户问“界面上那个重置按钮到底在哪个位置”,结果自然惨不忍睹。更典型的是表格,很多合同里的核心条款、金额明细都在表格里,纯文本解析会把表格拆成一串无意义的字符,字段之间的对应关系直接丢失。

所以解析这件事,本质上是给RAG做“信息保全”。你保全了多少信息,决定了大模型能利用多少信息。图文类内容尤其如此,因为它不仅包含文字,还包含空间关系、版面结构、视觉语义,这些都是传统文本解析碰不到的东西。

1.2 原生PDF和扫描版PDF是两条路

PDF解析的第一原则,是先判断它到底有没有“文本层”。原生PDF由数字排版生成,文字本身是可复制可检索的,这类文档直接用解析库提取文本即可;扫描版PDF本质是一张张图片,有的还带一层识别粗糙的隐藏文本,直接用工具抽出来就是乱码,必须走OCR。

混合型PDF最坑,明明前几页有正常的文本层,翻到中间突然插入几页纯图片扫描件。如果整份文档都用同一条解析流水线,要么把正常文本也送去OCR浪费时间,要么把那几页扫描件直接吞掉。

我现在的做法很简单:用PyMuPDF逐页抽取文本,统计每页的有效字符数。如果连续多页文本量低于阈值,就判定这一份文件需要在对应页面上触发OCR。判定逻辑不复杂,但能省下大量无效的OCR调用。

1.3 RAG知识库到底能不能“存图片”

这个话题经常有人问。直接往向量库里塞原始图片,技术上做不到,因为大多数文本向量模型根本不懂图像内容。但“存图片”的需求是真实存在的:客服知识库里,用户想直接看到产品故障示意图;法律场景里,合同扫描件的原始影像必须留存。

合理的思路是把图片“转译”后入库:用OCR识别图片中的文字,或用多模态大模型生成一段图片描述,把这段描述文本放进chunk里参与向量化检索,同时保留原始图片的文件路径或URL作为可回溯的附件。这样既实现了“图片进RAG”,又不破坏检索链路的一致性。

另外需要提一下知识库的形态区分。RAG知识库擅长处理非结构化文本,结构知识库对应关系型数据库里那些严格字段的数据,KG知识库则是用图谱表达实体关系。图文解析中抽取出来的表格数据,完全可以结构化后分流到结构知识库,而图片本身的语义描述则留在RAG里,两者互补,不是替代关系。

2. OCR与多模态大模型:从“识字”到“看懂”

2.1 OCR不是简单的“识别文字”

我刚开始做文档解析时,以为OCR就是调一个现成库,把图片里的字全部抽出来就算完事。后来发现这是最大的误区。真正的OCR流程至少包含三件事:版面分析、文字识别、阅读顺序还原。

版面分析要解决“哪里是标题、哪里是正文、哪里是表格、哪里是页眉页脚”;文字识别是把图像里的字符转成文本;阅读顺序还原则是决定先读左上还是先读右栏。很多OCR工具只做了中间那一步,导致抽出来的文本是一堆散落的字块,完全没法用。

给合同做字段抽取就是一个典型场景。用户想提取“收入、单位、时间”这些关键字段,如果只做文字识别,输出是杂乱的纯文本,字段之间的从属关系全靠猜。真正可靠的做法是先做版面划分,再用规则或模型把相应区域内的文本映射到预定义字段上。这也是为什么我会把OCR工具和多模态模型结合起来用,而不是二选一。

2.2 多模态大模型解决了“视觉理解”

多模态大模型给图文解析带来的变化,是它真正“看懂”了图片,而不只是“读出”文字。给它一张电器接线图,它能识别出不同模块之间的连接关系;给它一张产品操作截图,它能描述出界面上的按钮区域和执行流程。

这种能力对RAG而言特别有价值。图片里的信息被翻译成自然语言描述后,就可以和普通文本chunk一样被向量化、被召回。用户问“这个设备的告警灯亮了应该先检查什么”,如果解析阶段已经把告警灯示意图的描述文本存进知识库,召回和回答的质量会明显上台阶。

但多模态模型不是万能的。它对文字的精确识别,尤其是大量密集小字、生僻字符,并不比专用OCR稳定。而且它的推理成本远高于OCR,动辄需要几秒甚至几十秒处理一张图。所以我的策略是把OCR当作“识字层”,把多模态模型当作“理解层”,两者分工。

2.3 选型决策:什么时候用哪条路

整理一个实用的决策表:

场景推荐方案原因
扫描合同、发票、护照专用OCR(PaddleOCR等)文字识别精度高,结构化字段提取稳定,成本低
图文混排的产品手册OCR + 多模态描述文字走OCR,图片交给多模态转述
电路图、流程图、架构图多模态大模型关键是语义关系和视觉结构,不是文字覆盖
密集表格提取OCR表格识别 + 后处理表格器线、单元格边界需要专门的表格识别能力
少量精品文档直接多模态大模型数量小,容忍一定延迟,胜在理解全局

这里有个基本判断逻辑:文档数量大、文字密集、格式一致,优先走OCR流水线;文档数量小、视觉结构复杂、需要语义理解,优先走多模态大模型。混合场景就混合用,不要情怀优先,算清楚成本和延迟再决定。

2.4 更优的混合方式:OCR抽取文本 + 多模态生成图片解读

我现在的主力方案是混合流水线。

第一步,用OCR把所有可识别文字连同上坐标一起抽出来,包括识别表格结构。第二步,凡是遇到图片元素,用图像抽取工具把它从原文档中裁出来,交给多模态模型生成一段结构化的图片描述。第三步,将OCR文本和图片描述进行合并,图片描述作为一个“图片说明”字段追加到对应的chunk里。

有一点经验值得分享:喂给多模态模型的图片不要贪大,最好按视觉区域裁剪后再送,比如把一页手册拆成“产品外观图”“界面截图”“接线图”三个子图,分别生成描述。整页直接丢给模型,它容易关注到不重要的区域,描述质量反而不稳定。描述生成的结果最好设置缓存,因为同一张图可能在多份文档里反复出现,每次重新调大模型既费钱又不稳定。

3. 九种PDF解析工具选型与实测对比

3.1 一张表看明白九种工具

我在不同项目里摸爬滚打过后,常用的PDF工具大概聚焦在下面这九种,另外还有一个隐藏彩蛋。每个工具都有自己的脾气,选错了倒也不至于全盘崩塌,但会在某个环节反复被坑。

工具类型强项弱项适用场景
PyMuPDF(fitz)文本提取库快,支持输出dict结构带坐标,能提取图像对复杂表格结构无能为力日常提取正文、切分、图像抽取
pypdf文本提取库纯Python,依赖轻,适合合并拆分加密排版信息基本丢失轻量文本抽取、PDF处理
pdfminer.six底层解析器能拿到字体、坐标、布局信息,可控性强速度慢,使用门槛高自定义版面还原规则
pdfplumber表格库基于pdfminer,擅长线框表格和坐标定位对无边框表格识别弱财报、合同里的有线表格
Camelot表格库lattice/stream双模式,能输出DataFrame安装依赖较多,线条破损时需调参规则表格批量抽取
pdf2image渲染工具把PDF页转成高分辨率图片本身不识字,只是前置步骤扫描件OCR前的渲染
PaddleOCROCR工具中文识别精度高,支持表格识别和版面分析部署体积大,依赖较多扫描件、中文文档识别
Apache Tika通用内容抽取支持几百种格式,接口统一PDF细节控制弱多格式统一接入
Unstructured语义解析器分区识别Title/Table/NarrativeText对扫描件仍需OCR辅助直接给LLM准备chunk

彩蛋是marker,一款面向LLM的PDF解析工具,能把PDF转成整洁的Markdown,处理论文、教程类文档效果很不错,但对复杂中文表格和扫描件依然有局限性。

3.2 轻量文本提取:PyMuPDF和pypdf怎么选

日常处理PDF文本,我基本在PyMuPDF和pypdf之间选。PyMuPDF的速度和输出结构都更优秀,它能用doc[i].get_text("dict")返回包含坐标信息的分块文本,这对后续做阅读顺序排序、区域切分非常有用。

pypdf的优点是纯Python实现、依赖极简,在一些受限的部署环境里用起来几乎零负担。但如果你的目标是高质量解析而不是简单处理,pypdf的文本抽取能力有点跟不上,它对XFA表单和复杂排版的还原度较低。

一个容易忽略的点是许可证。PyMuPDF使用的是AGPL类许可证,如果做内部工具没问题,做对外商业化产品就要考虑合规问题,要么购买商业授权,要么换成许可证更宽松的pypdf。选型这种事,技术判断之外,合规判断也得做在前头。

3.3 表格解析:pdfplumber与Camelot

表格解析是PDF解析里最磨人的环节之一。pdfplumber和Camelot是我最常用的两个库。pdfplumber基于pdfminer,能拿到页面元素级的坐标,适合处理结构不太规整、但线条清晰的表格。Camelot提供了两种模式:lattice依赖线条检测,适合有线框表格;stream适用于没有竖线的表格,用空白间隔切分单元格。

实际使用中你会发现,没有哪种工具是绝对省心的。表格线条残缺、单元格内有换行、表头跨页,这些都是高频问题。我的经验是把表格抽取结果统一转成Markdown格式存进chunk,这样大模型阅读表格内容时,结构和语义都相对清晰。

如果真的遇到无边框、弱边框的“假表格”,pdfplumber和Camelot都容易翻车,这种场景我会直接把表格区域裁剪成图片,交给OCR的表格识别模块去处理。识别结果再转成结构化的行列数据,比硬用坐标推断可靠得多。

3.4 面向LLM的解析:Unstructured和marker

Unstructured在RAG圈子里用得很多,核心价值在于分区能力。它的partition_pdf接口会返回一系列元素对象,每个元素带有类型标签,比如Title、Table、NarrativeText。你可以直接按元素类型决定后续的切分策略,而不是对着一个特大文本块手动切。这对构建真正语义化的chunk非常有用。

marker的思路更直接,把整个PDF转成干净的Markdown,保留标题层级、列表和一部分表格语法。我做技术文献导入时很喜欢它,因为Markdown本身就是适合LLM阅读的格式,切分后喂给模型,理解效果通常比纯文本好不少。

但这两者有个共同前提:它们主要处理有可用文本层的PDF。扫描版PDF必须先经过OCR得到文本层,或者搭配额外的OCR流程,否则识别出来的文档还是空洞的图片。

3.5 通用解析:Tika和pdfminer.six

如果你的知识库需要统一接入大量格式,Tika是省事的选择。它把PDF、Word、PPT、HTML等几十种格式统一成文本输出,接口也简单。缺点是它对PDF版式的控制能力弱,拿不到精细的坐标和布局信息。

pdfminer.six正好相反,它是“显微镜级”的PDF解析器,能获取每个字符的字体、位置、大小,适合做自定义版面还原。缺点是慢,处理几百页的文档时明显吃力,而且要用好它,你得懂PDF的底层对象模型。

我给一个务实的建议:多格式混乱的场景,先用Tika把“能不能读”的问题解决掉;需要精细版面的时候,再用pdfminer.six定制规则。大多数RAG项目其实不需要那么精细,Tika够了,千万别杀鸡用牛刀。

3.6 扫描版PDF全流程:pdf2image加PaddleOCR

扫描版PDF在现实业务中占比极高,尤其合同、发票、政府批文、老文献,基本全是扫描件。我的标准链路是:pdf2image把PDF页渲染成高分辨率PNG,然后交给PaddleOCR做识别,再按行和段落还原文本。

pdf2image渲染时,DPI设置我一般取200到300。太高文件太大处理慢,太低识别精度明显下降。中文字体笔画比较复杂,150DPI以下非常容易识别错字。PaddleOCR相比Tesseract,中文场景确实稳一个档次,而且它还提供了表格识别能力,可以直接输出结构化表格。

跑完OCR之后,建议保留两个东西:每页识别文本的置信度,以及识别出的版面块坐标。置信度低的分页不要硬塞进知识库,单独标记出来走人工复核,这是保证RAG底座干净的重要防线。

4. 完整实操:从PDF到可检索的RAG切片

4.1 第一步:文件分类与预处理

拿到一批PDF后,我不会直接塞进解析脚本,而是先做分类。用PyMuPDF逐页检查文本层,记录每页的文本长度、图片数量、页面尺寸。如果是加密PDF,先尝试用qpdf做无密码解密;如果文件损坏,直接记录失败原因并跳过。

分类规则大致如下:文本长度稳定的,打标为text_pdf;绝大多数页面没有文本层的,打标为scan_pdf;部分页面有文本、部分没有的,打标为mixed_pdf。后面两条流水线按这个标记分流处理。

这一步看似繁琐,却是整个解析链路最值得花时间的投资。我见过太多项目因为省掉了分类,让正常PDF也强制过OCR,结果时间成本和识别错误率双双飙升。

4.2 第二步:版式抽取与文本还原

对text_pdf,我用PyMuPDF抽取带坐标的文本块,然后按阅读顺序重组。简单场景下,按块的y坐标排序再按x坐标排序,就能恢复大致正确的阅读顺序。

多栏版式是个例外,尤其是论文和技术手册,双栏甚至三栏排版很常见。如果直接把所有文本块拼接,左栏第一行会和右栏第一行交混在一起。我的做法是先通过文本块坐标和栏宽判断页面是否多栏,再多栏情况下按栏分组,再组间按从左上到右下的顺序拼装。

字体大小也可以用来判断标题层级。把明显大于正文字号的那些行提取出来,作为chunk的标题元数据,后续检索和展示时很有用。

4.3 第三步:扫描件唤醒

扫描版PDF走OCR流水线。下面是一个简化但能跑的流程:

from pdf2image import convert_from_path from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=True, lang="ch") images = convert_from_path("scan.pdf", dpi=300) for i, img in enumerate(images): result = ocr.predict(img) lines = [] for line in result: text = line["rec_texts"] conf = line["rec_scores"] lines.append((text, conf)) # 将lines按阅读顺序拼装为段落文本 page_text = assemble_paragraphs(lines) # 保存page_text,标记OCR置信度 save_page_result(i, page_text, compute_conf(lines))

注意PaddleOCR当前版本的接口有一些差异,建议以官方文档为准。核心思想是逐页识别、逐页保存,保证中途某一页出错不会影响整份文档。识别出的文本还需要做一遍后处理:去掉页眉页脚、还原段落缩进、修正全半角符号。

4.4 第四步:图片与表格的语义转译

这一步是提升RAG理解上限的关键。我会用PyMuPDF把文档里的图片对象导出,按页码和坐标命名,然后交给多模态模型生成描述描述。表格也是一样,复杂的表格区域直接裁剪成图片,交给OCR的表格识别模型或多模态模型生成Markdown格式的表格。

图片描述需要遵循固定的模板,例如“这张图展示了……重点关注……”。模板化的好处是生成的文本风格统一,向量化之后不会因为句式差异导致检索漂移。

多模态模型处理结果最好做缓存,缓存键用文件hash加图片在页面中的坐标。实际项目中,同一份产品图会出现在多份PPT、PDF里,没有缓存的话,一笔几十页的解析任务就要重复调用几十次大模型,成本完全失控。

4.5 第五步:切分、向量化与metadata设计

解析完成之后,进入切分和向量化环节。我强烈不建议直接按固定字符数硬切。更好的做法是基于版式语义切分,例如Unstructured返回的元素边界、PDF中的标题位置、表格块边界,都是天然的切分锚点。

每个chunk应该带一组完整的metadata,至少包含来源文件名、页码、块类型(文本/表格/图片描述)、OCR置信度、是否经过多模态转译。这些字段在后续做过滤召回、溯源展示时非常有用。

向量化时,普通文本chunk和图片描述chunk可以使用同一个文本向量模型,这样它们在同一个向量空间里可以直接比较相似度。这种方案的部署成本低,效果也能满足绝大多数业务场景。

5. 常见问题排查与避坑实录

5.1 加密PDF、超大PDF、乱码PDF的处理

加密PDF是最常见的拦路虎。有些文档设定为“仅查看”,复制和打印被禁用,PyMuPDF提取文本时能拿到部分内容,但某些段落会丢失。这种情况先用qpdf做解密处理。如果文档设置了打开密码,说明内容敏感,要确认有权限后再处理。

超大PDF的问题更多出现在内存和超时控制上。几百MB的PDF如果整本加载,非常容易把解析进程拖垮。我的做法是解析前先按页范围拆分,单独处理每一段,最后合并结果。配合断点续传机制,哪怕进程崩溃,也不用从头再来。

乱码PDF通常是缺少正确的字符映射表。遇到中文变成方块的文档,先不要急着OCR,用pdffonts看一下内嵌字体类型。如果字体是子集且缺少Unicode映射,确实修复是有难度的,最稳妥的方式是直接渲染成图片再OCR。有时候OCR出来的质量和原文本层乱码相比,反而好很多。

5.2 OCR识别错字与多语言识别失败

OCR识别错字在中文场景下相当常见,尤其是扫描质量差的文档,错字可以把“张三”识别成“张王”。单纯的错字问题可以通过混淆专用词典部分修复,但更可靠的策略是引入置信度阈值:低于阈值的文本不直接入库,单独摘出做人工复核。

多语言识别失败也常遇到。PaddleOCR默认模型以中文为主,识别韩文、日文、法文时需要切换对应的语种模型或使用多语言模型。不要图省事用一个模型处理所有语言,效果大概率不理想。另外,小尺寸字体的验证码、表单字段这类图片,OCR前做一下放大和降噪,识别率会有明显提升。

5.3 工具调用异常与空结果

实际开发的解析任务里,异常处理是比识别效果更值得关注的问题。一个很小的PDF文件格式错误都会让脚本报错,比如文件头检测失败。正确的应对方式是在每一大步都包装独立的异常处理,异常记录到日志,单文件失败不影响整批任务。

我用得比较多的兜底策略是“分级降级”:文本提取异常,尝试用pdf2image渲染成图片再OCR;OCR异常,尝试用多模态大模型直接识别;实在无法处理,就把文件移入待人工处理列表。最后这一级即使没有解析成功,也要保留原始文档和失败原因,方便后续补充处理。

5.4 表格解析结果乱七八糟

表格识别最让人头疼的情况是合并单元格、跨页表、无边框表。前面提到Camelot的两种模式,lattice适合有线的表格,但如果线条扫描后出现断裂,就需要预处理图像,增强线条对比度。stream模式在无线表格场景有一定效果,但遇到复杂表头依然容易错乱。

真正常用的方法还是桥梁式的:先把表格区域识别出来,再用OCR的表格结构化能力输出单元格坐标,最后拼装成Markdown表格。阶段性的结论是,工程上没有完美的表格解析方案,交替使用几种工具,加一个抽检机制,才是实际最靠谱的做法。

5.5 入库性能与去重

当解析链路搭建完成,所有chunk开始向量化入库时,新的问题会冒出来:重复解析、重复入库、检索结果被重复内容污染。

我建议为每个文件维护解析版本号。文件内容hash没有变化,而且解析工具版本没有升级,就直接复用已有结果。切分出的chunk之间也可能高度重复,尤其是多份文档引用同一段标准条款的时候,用SimHash或Embedding相似度做一次去重,能显著改善检索结果去重后的干净程度。去重不是丢掉信息,而是避免同一个答案被重复召回导致排序混乱。

6. 几条实操心得与后续扩展

6.1 没有“银弹”,先分类再选型

我在这类项目里学到的最大教训,就是不要试图找一个“万能解析器”。文本型PDF、扫描型PDF、图形密集型PDF,它们的适用工具完全不同。与其到处搜集新工具,不如先做好分类,把合适的工具用到合适的场景里。

很多团队以为RAG效果差是算法问题,反复调embedding、调rerank参数,却忽略了解析阶段已经丢失了大量关键信息。图文解析能力的建设,往往比模型参数调整更值得投入,这也正是我持续整理这些攻略的原因。

6.2 图片不要直接丢弃,要转成“注释”

面对图文混排的文档,最忌讳的做法是只取文字、扔掉图片。产品截图、流程图、UI界面图,往往承载了文字里没有表达的视觉信息。我现在的标准是把所有重要图片自动生成描述文本,以“图片注释”的形式附着在对应文本块周围,既保留了语义上下文,又不干扰主文本结构。

如果业务需要精确的图片检索,再考虑引入多模态embedding模型做图文联合召回。但这套方案部署门槛高、成本和调优难度大,我通常建议先把文本转述方案跑稳,再考虑升级。

6.3 解析链路要可观测、可缓存

解析流程一旦跑起来,每一份文件都要留下日志:解析耗时、失败原因、OCR置信度分布、大模型调用次数和费用。没有这些数据,你就无法知道瓶颈到底在哪里。缓存是省钱利器,尤其是多模态模型调用,不做缓存的项目,账单出来会非常意外。

分批处理任务时,加上断点续跑功能。上千页的文档,中断后重新开始是最浪费时间成本的坑。把已完成的页或文件标记为完成状态,下次启动自动跳过,这个机制帮我省下了大量无效等待时间。

最后再分享一个小技巧:解析完的结果,先别急着向量化。按照文档类型,各随机抽二十个chunk读一遍原文本,如果人看着都觉得语义不连贯、信息残缺,那向量化之后大概率也是白搭。图文和PDF解析这件事,真正的门槛不是工具不会用,而是有没有耐心把每一类问题掰开看透。工具选型只是开始,后面还有一堆脏活累活,熬过去,你的RAG知识库才真正站得住。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进