
做RAG项目的人八成都会在“数据导入”这一步被PDF和图片狠狠教做人。你以为PDF是“一份文档”把它喂给解析器就能得到干净的文本实际上一打开有的是扫描件、有的是三栏排版、有的是整页表格还有的干脆就是图片套了一层PDF壳。我见过不止一个团队模型选型、向量库、问答链路都搭好了最后卡在“文档解析出来是一堆乱序文本”这个环节上整个项目原地踏步。这篇是“RAG数据导入与解析全攻略”的第二篇聚焦图文与PDF解析什么时候该上OCR什么时候该用多模态大模型九种常用PDF解析工具分别适合什么场景以及一套我自己在项目中反复验证过的分层解析流水线。无论你是刚接触RAG还是已经在做知识库导入这部分内容都能直接用在你的项目里。1. 为什么RAG项目里图文和PDF总在最前面翻车很多人的RAG流程是“上传文档→解析文本→切分→向量化→入库”。看着顺理成章但问题几乎都出在第二步。PDF在RAG链路上的地位非常特殊它不是一种“格式”而是一种“容器”里面装什么都有可能。想指望一个解析库通吃所有类型的PDF基本是痴人说梦。1.1 PDF的“文本层”和“外观层”是两回事PDF本质上是一个排版模型它记录的是“哪些字符放在页面的哪个位置”而不是“段落”“标题”“表格”这些语义结构。于是就有了两个完全不同的层面文本层由PDF内容流里的字体、字符编码、位移指令构成可以直接用工具提取出字符串。外观层最终渲染出来的视觉效果。扫描件只有外观层没有文本层有些“打印生成”的PDF有文本层但字符顺序可能是乱的。RAG解析最典型的翻车场景就是把带文本层的PDF直接提取文本结果双栏论文被按行交错读取左右两栏混在一起段落被字体嵌入打散字间莫名其妙加空格表格的单元格内容提取出来变成一长串行列关系全部丢失。所以做PDF解析的第一步不是选工具而是判断这份PDF属于哪一类。类型判断错误后续一切优化都没有意义。1.2 图片入库的本质问题向量库存的是“语义”顺便把大家经常搜的一个问题说清楚——RAG知识库能存储图片吗直接给结论能但要看你用什么方式存。普通向量库如Milvus、Qdrant、Chroma存的是向量和元数据本身不限制你存图片二进制或图片路径。关键问题在于RAG检索是靠“文本语义向量”匹配的。你上传一张产品说明图如果不做任何处理检索模块拿用户的提问去向量匹配图片的二进制数据根本无法参与语义计算。所以图片进RAG一般是三条路图文描述化用OCR或多模态大模型把图片内容转成文本描述把描述文本向量化入库多模态向量库使用CLIP之类的多模态嵌入模型图片和文本映射到同一向量空间路径元数据关联把图片文件路径存进元数据问答时返回给用户查看。日常项目中第一和第三种最常用成本也最低。把图片“翻译”成结构化文本再去走标准RAG链路才是目前最稳妥的主流方案。2. 动手解析之前先花两分钟判断PDF类型很多解析项目失败不是工具不行而是压根没做“分诊”。PDF类型判断对了工具选型就成功了一半。2.1 三种常见分类我在实际项目中把待解析的PDF先分成三类一类文本型PDF。由Word、LaTeX、HTML等工具直接导出有完整文本层可以直接提取文本。典型特征在PDF阅读器里能选中文字、能复制。二类扫描型PDF。本质上是一堆图片的集合可能来自扫描仪或传真机没有任何文本层。在阅读器里选中文字会失败或者只能整页选择。三类混合型PDF。一部分页面有文本层另一部分是图片或扫描页比如扫描版书籍里夹了几页打印排版的书签页。这种最容易踩坑——首页提取出文本就以为整份文档都能提取。2.2 快速判别方法判别方法很简单用PyMuPDFfitz几行代码就能跑import fitz def classify_pdf(path, max_pages5): doc fitz.open(path) sampled min(doc.page_count, max_pages) text_pages 0 image_pages 0 for i in range(sampled): page doc[i] text page.get_text(text).strip() images page.get_images(fullTrue) if len(text) 20: text_pages 1 elif images: image_pages 1 if text_pages sampled * 0.8: return text_pdf if image_pages sampled * 0.6: return scan_pdf return mixed_pdf判断逻辑不复杂文本层超过阈值就走文本解析管线图片为主就走OCR管线混合型建议按页拆开、分别走不同的解析路径。这一步做好了后面不会在错误的工具上浪费时间。3. OCR选型PaddleOCR和Tesseract怎么选确定了是扫描件或图片为主OCR就是绕不开的一步。市面上OCR工具多如牛毛但RAG场景下真正值得长期用的我总结下来主要是三条路线PaddleOCR、Tesseract、商用云OCR。各有各的适用面得分清楚。3.1 PaddleOCR中文场景的默认首选如果你的文档以中文为主PaddleOCR基本是首选实际项目里我用它处理过合同扫描件、书籍扫描页、手写批注整体识别效果在开源方案里属于第一梯队。核心优势在于它是一个完整的OCR体系不只是“识别文字”DB文本检测先框出文字区域方向分类器自动矫正旋转90度、180度的页面CRNN/SVTR识别模型支持中英文、数字表格结构识别能输出表格行列结构版面分析模型区分标题、正文、图片、表格区域。下面是一个基础调用示例from paddleocr import PaddleOCR ocr PaddleOCR( use_angle_clsTrue, # 启用方向分类对付歪的扫描件 langch, # 中英文模型 show_logFalse ) result ocr.ocr(scan_page.png, clsTrue) for line in result[0]: # line [坐标框, (识别文本, 置信度)] text line[1][0] score line[1][1] print(text, score)几个关键点use_angle_cls要打开因为扫描件方向五花八门置信度低于0.8的结果建议单独拎出来记录不要直接丢弃很多关键数字就是低置信度区域里的在RAG场景里OCR的输出除了文字内容一定要保留坐标框。坐标信息后面可以用来恢复阅读顺序。PaddleOCR在CPU机器上也能跑但识别的确比较慢一页A4扫描件大约需要几秒到十几秒。有条件就上GPU批量处理体验完全不一样。3.2 Tesseract轻量但配置麻烦Tesseract是经典的开源OCR引擎胜在轻量、老牌、有大量语言包支持。但它有一个很尴尬的问题对版式复杂的文字识别率不如深度学习方案。我用它处理过印刷体英文文档效果尚可一旦落到中文、带背景色、低对比度扫描件错误率会明显上升。如果你确定要用Tesseract有几个参数很关键tesseract scan.png out -l chi_sim --psm 6--psm 6表示整行文本适合标准文档--psm 3是全自动版面分析适合有段落的页面--psm 4按列处理适合多栏排版。Tesseract的问题还出在预处理上同一张图片不做阈值二值化和转灰度识别效果可能差出一大截。我之前拿一份淡黄色底色的合同去测直接识别乱码转灰度、加大对比度之后就能读通了。这意味着你在Tesseract前面还得垫一套图像预处理流程复杂度并不低。3.3 什么时候直接上商用OCR商用OCR如百度、腾讯的OCR接口在工程上非常省心不用部署模型、不用调参数、接口稳定。如果你在RAG项目里OCR量不大或者团队没有算法人力直接用商用OCR反而划算。它通常还有表格识别、印章检测、手写体识别等额外的能力。代价是成本、限流和数据出网问题。处理敏感文档时要评估合规性是否允许把文档内容发到第三方接口这个比技术选型更敏感务必提前确认。热词里有人搜到“以下OCR代码识别不了韩文from paddlex import create_pipeline”这类问题这里顺带说清楚PaddleOCR如果没有下载对应语言方向的模型只识别中英文是很正常的。韩文需要指定langkorean并下载对应模型部分OCR引擎甚至要手动安装语言模型包。做多语言识别前先确认语言包是否就位别急着怀疑框架。4. 多模态大模型复杂版式的兜底方案我一直认为OCR和解析工具解决的是“90%的常规场景”剩下10%的复杂版面——比如图文混排的PPT导出件、带公式的论文扫描页、印章和文字重合的合同——传统工具很难应付这时候就该上多模态大模型。4.1 什么场景真的需要多模态大模型我是这么判断的先用传统工具跑一遍如果出现以下三种情况再升级到多模态大模型版面结构严重受损双栏、三栏、文本框漂浮在任意位置提取的文本顺序完全没法读语义格式需要重排不只是提取文字还要把内容整理成标题、段落、表格、列表这样的结构化输出图文强关联页面里的信息靠“图文字”联合表达比如流程图、架构图、带标注的截图光有文字就失去了意义。用多模态大模型解析PDF本质上就是“把页面渲染成图片让模型看图说话”。它没有传统OCR的字符坐标机制但优势在于理解语义和版面结构——知道哪块是标题、哪块是正文、哪个表格的“表头”在哪里。4.2 实际操作与参数设计目前常见的做法是把PDF页面转成图片然后调用多模态模型接口from openai import OpenAI client OpenAI(base_urlyour_vlm_endpoint, api_keyyour_key) def analyze_page_with_vlm(image_path: str) - str: with open(image_path, rb) as f: img_base64 base64.b64encode(f.read()).decode(utf-8) resp client.chat.completions.create( modelqwen-vl-max, # 或其他多模态模型 messages[ { role: user, content: [ {type: image_url, image_url: {url: fdata:image/png;base64,{img_base64}}}, {type: text, text: ( 请把这张页面内容完整转写成Markdown格式。注意以下要求\n 1. 保持阅读顺序不要跳段\n 2. 表格输出为标准Markdown表格\n 3. 标题层级用#表示\n 4. 保留关键图片的作用说明\n 5. 无法识别的部分用占位符表示不要编造。 )}, ], } ], temperature0.1, ) return resp.choices[0].message.content这里有几个实用建议temperature压到0.1以下解析任务要确定性不要创造性提示词里明确“不要编造”多模态模型对图片中不清晰的文字有时会“脑补”在RAG场景里幻觉就是数据污染一页一调用不要一次性把几十页塞给模型即便接口支持输出质量和稳定性都会下降记录来源页号输出的Markdown块要保留页码标记方便后面溯源。成本方面多模态大模型的单价远高于传统OCR但胜在省人工。一个几十页的复杂文档传统OCR提取完还得人工整理版面而大模型直接一步到位转成结构化Markdown。我现在的用法是“传统工具多模态模型”组合常规页面走解析工具极复杂页面抽样评估后交给大模型。两者按比例混合成本可控效果也能保住。5. 九种PDF解析工具选型清单这部分是很多人催更的重点。市面上的PDF工具简直多到让人选择困难我按项目里的实际用途把它们分成几类每类说清楚“能干什么、不能干什么、什么时候用它”。5.1 文本提取三件套PyMuPDFfitzPyMuPDF是我处理PDF的默认首选。速度快单页文本提取毫秒级而且自带渲染能力可以把页面渲染成PNG也可以精确拿到每个文本块的坐标。它最核心的价值就是“文本块坐标”这个坐标数据是后面做阅读顺序还原的基础。写法很简单import fitz doc fitz.open(doc.pdf) for page in doc: blocks page.get_text(dict)[blocks] for b in blocks: if b[type] 0: # 文本块 for line in b[lines]: for span in line[spans]: print(span[text], span[bbox])它的短板是不擅长理解段落语义“标题”“正文”这种层级关系需要你自己判断表格提取出来的也是线性文本行列结构约等于没有。pdfplumberpdfplumber的表格提取能力比PyMuPDF强得多它基于PDFMiner的底层做封装能识别页面上的线条和字符位置关系从而还原表格。对“有明确框线”的表格效果很好import pdfplumber with pdfplumber.open(table_doc.pdf) as pdf: for page in pdf.pages: tables page.extract_tables() for table in tables: for row in table: print(|.join(cell or for cell in row))但pdfplumber执行速度比PyMuPDF慢文件一大会有明显卡顿。我的用法是PyMuPDF做主流程只有检测到表格区域时才把这一页单独交给pdfplumber。PDFMiner.sixPDFMiner.six是做PDF底层解析的老牌库pdfplumber就是站在它肩膀上做的。优势是能拿到非常底层、细粒度的字符布局数据适合二次开发。缺点是API较底层直接上手效率不高普通项目没必要直接用。5.2 表格解析专用camelotcamelot是专门为PDF表格而生的工具分Lattice和Stream两种模式Lattice依赖表格线条拟合适合有线框的表格Stream适合无线框、靠空白分隔的表格。准确度不错但对表格样式非常挑太花的表格容易翻车。实际体验来说它比pdfplumber对复杂表格的容忍度更高一点尤其是跨行、跨列的合并单元格场景。代价是camelot依赖OpenCV安装体积大、安装过程容易出问题。5.3 扫描件管线配套pdf2imagepdf2image不是一个解析工具而是把PDF页面转成图片的桥梁。它在扫描件管线里的地位极高——有了图片后面不管是接PaddleOCR、Tesseract还是多模态大模型都顺理成章。内部调用poppler的pdftoppm需要注意Linux环境先装poppler-utilsapt-get install -y poppler-utilsfrom pdf2image import convert_from_path images convert_from_path(scan.pdf, dpi200, fmtpng) for i, img in enumerate(images): img.save(fpage_{i1}.png)dpi设置很关键。OCR一般200-300就够高了只会让文件变大、处理变慢识别率提升非常有限。dpi低于150小字号文字容易糊识别率骤降。5.4 版面还原与高结构化工具UnstructuredUnstructured是专门为非结构化文档设计的工具包主打“文档分区”能识别标题、正文、列表、表格、图片框等不同元素并输出带类型标签的元素列表。它的分块设计天然契合RAG流程所以很多LangChain的文档加载器背后都是它。它的缺点是重。底层涉及检测模型和一堆依赖部署复杂第一次跑会下载不少模型文件。但对于“文本型PDF需要按语义结构分块”的场景它确实省掉了很多手工处理。markermarker是这几年PDF转Markdown方向的热门工具。它把DeepLearning模型封装在管线里对版面分析、公式识别、表格还原支持都很好。输出就是标准Markdown能直接喂给下游分块逻辑。学术论文、技术文档这类结构化强的PDF它表现出色。我实测的体验是marker对“印刷质量好、版式规整”的PDF效果好得离谱公式也能转成LaTeX格式但遇到表格线缺失、文字压图这类烂版面也需要后处理兜底。marker依赖PyTorch没有GPU时跑起来偏慢不过结果值得等。DoclingDocling是IBM开源的文档转换工具主打高质量PDF转结构化格式。它对版面的理解比Unstructured更细致支持表格、阅读顺序恢复、甚至目录结构抽取输出格式包括Markdown和JSON。在中文文档上表现也比预期好。如果你需要的是一个“解析完直接得到干净结构”的工具Docling值得在评测清单里排个位置。选型这件事我建议不要迷信任何单一工具。工具之间是互补关系关键是理解每类工具的能力边界工具强项弱项适用场景PyMuPDF快、坐标信息完整无语义理解、表格弱主流程文本提取pdfplumber表格提取稳慢表格页面专项处理PDFMiner.six底层布局信息上手慢二次开发camelot复杂表格还原依赖重、调参多表格密集文档pdf2image页面渲染成图仅是桥梁扫描件管线Unstructured文档分区、语义块识别依赖重结构化文本PDFmarker排版还原、公式支持需GPU、速度一般论文/技术文档Docling版面结构还原、目录抽取生态较新要求高质量结构化输出PaddleOCR中文识别、检测识别一体需要部署扫描件Tesseract轻量、多语言包版式复杂时脆弱简单印刷体批量识别多模态大模型语义理解、结构化输出贵、有幻觉风险复杂版面兜底表格里写不下的场景统一定性批量流水线优先考虑速度和稳定性交互式单文档处理优先考虑效果和可读性。6. 一套可复现的分层解析流水线工具聊完落到工程上最重要的就是“怎么串起来用”。我自己项目里沉淀的流程是四层结构先判断PDF类型再按类型分流解析后统一结构化输出最后再做分块。每一步都不复杂合起来能覆盖绝大多数PDF解析需求。6.1 整体流程设计PDF进入 │ ├─ 页面级类型判断 │ ├─ 文本型 → PyMuPDF提取文本块 │ │ ├─ 含表格页 → pdfplumber/camelot提取表格 │ │ └─ 复杂版面 → marker/多模态大模型 │ └─ 扫描型 → pdf2image渲染页面 │ ├─ PaddleOCR识别 │ └─ 复杂版面 → 多模态大模型 │ └─ 统一输出Markdown文本块 └─ 按元数据(页码/块类型)进入分块模块这套流程的核心思想是“页面级分流”而不是“文档级分流”。同一份文档里页面之间差异很大按页分流能最大化利用每个工具的长处。6.2 关键配置建议很多人在这一步问我要“参数配置”我先说几个最影响结果的PyMuPDF提取时启用layout-aware模式page.get_text(blocks)比直接get_text(text)保序能力强得多pdfplumber只针对含表格的页面喂整份文档会让速度成倍下降PaddleOCR的det_limit_side_len设为960过大的图会按长边缩放字太小反而识别不准多模态大模型最好限制单页图片短边不低于1000px太小的图会让模型看不清细节会主动“补全”错误内容。6.3 分块策略与元数据设计解析完成的文本进入分块阶段时一定要带元数据。我这里通常给每个文本块打这样几个标签source_page来自第几页block_typetitle / paragraph / table / list / caption / image_desctool_source是PyMuPDF、PaddleOCR还是多模态模型产出的。为什么强调tool_source因为不同来源的文本质量差异很大。比如OCR置信度低的文本在后续检索排序里权重就应该低一点多模态模型产出的描述文本需要在检索时降低被匹配到的机会只作为补充上下文。这个字段帮你拥有了一层层调节检索效果的抓手。分块大小按RAG链路对chunk size的适配决定。我一般在400-800字之间表格单独成块不要把表格内容硬性拆成多段。拆表格是所有分块策略里最容易出问题的没有之一。表格一旦被截断检索时语义就会变成一堆零散数字毫无意义。7. 常见问题与排查思路这部分是无数人踩过坑之后的经验汇总。我在项目里常被问到的问题高度集中在以下几类逐一说明。7.1 表格线不全、竖排文字、中文乱码“表格内容提取出来乱成一团”先查是不是表格线不完整。camelot的Lattice模式高度依赖线条表格线断裂就会漏判行列。解决办法是先把页面渲染成图片用OpenCV做膨胀操作把断线补上再调用表格提取工具这个技巧我用过很多次效果立竿见影。竖排文字识别问题PaddleOCR默认对横排效果好竖排古籍、标签文字容易出问题。要么在OCR前面加一个旋转预处理要么改走多模态大模型。后者对竖排的适应性明显更好因为它理解的是语义而非单纯字符形状。中文乱码多半是编码映射问题。老PDF用自定义字体编码PyMuPDF提取出来可能是乱码或Unicode私用区字符。这种情况提取层的修复空间很小直接渲染页面走OCR才是最省事的方案。7.2 OCR多语言和生僻字多语言OCR的坑很典型Tesseract需要先安装对应语言包PaddleOCR要切换对应语言的模型不是“装个框架就能识别全世界”。遇到韩文、日文混合文档我通常会先跑一次语言检测再决定调用哪个模型不要用一个模型硬碰多语言。生僻字的问题更加隐蔽。有些识别出的字没有对应Unicode或者模型直接识别成近似字这类错误通过“置信度过滤”未必能发现因为模型自己并不知道它错了。唯一的兜底方案是对关键字段——人名、编号、金额——增加人工抽验环节。RAG解析可以全自动但关键数据的抽验必须有人把关。7.3 性能与成本控制批量解析时我强烈建议加一个“页面缓存层”。把每个页面的解析结果存成JSON缓存文档重复导入时直接走缓存解析耗时从几十分钟降到几秒这个收益太明显了。GPU资源充足当然可以全量走深度学习模型但大部分场景不必。先用快速方法过滤掉简单页面复杂页面再上重模型这个分层的思路能帮你省下80%的机器成本。还有一个容易被忽视的点并发控制。PaddleOCR和marker这类深度学习工具在CPU机器上对页面并发很敏感开64线程反而会因为内存争抢直接崩掉或者剧烈抖动。实测下来4到8并发是最稳的区间既能把CPU用满又不至于把内存打爆。8. 最后分享一点个人体会做PDF解析这件事最容易犯的错误就是“找到一个工具就梭哈”。我在项目里踩过的最大一次坑是迷信marker的版面分析能力拿一整批民国文献扫描件直接跑结果输出的Markdown里全是错乱表格还得靠人工重新清洗。后来我把流程改成先分类、再分流文本型走轻量方案、扫描件走PaddleOCR、复杂版面才交给多模态大模型不仅效果好了解析成本反而降了一大截。解析流程本身没有放之四海皆准的统一答案但“分层解析”这套思路是通用的先判断类型再组合工具最后按元数据管理结果。你把这三步想清楚RAG的图文导入环节基本就不再是瓶颈了。后续有时间我再写第三篇重点讲分块策略和检索质量之间的那些坑包括chunk size怎么调、metadata怎么设计都是实操里最磨人的细节。这一篇里的解析流程如果能帮你少填几个坑目的就达到了。