先说个有意思的现象:搜索框里“rag知识库能存储图片嘛”这个问题被问了无数遍,但大多数人的困惑点其实不在向量库,而在更靠前的解析环节。图片当然能存进知识库,但真正进Embedding和检索链路的是图片解析后的文本或结构化描述,而不是像素矩阵本身。这个认知不清,会让整个RAG项目的资料接入阶段就埋下隐患。
这篇文章是RAG数据导入与解析系列的第二篇,上一篇聊完纯文本数据的清洗和分块,这次集中解决两块硬骨头:一是图片内容的解析,二是PDF这种“容器型”文档的解析。文章会覆盖OCR与多模态大模型两条图文解析路线的差异,也会把市面上常用的九种PDF解析工具逐个拉出来横向对比,最后给出一套可以直接照抄的自动路由导入链路和排坑记录。如果你正在做一个涉及扫描件、合同、论文、产品手册的知识库项目,这篇应该能帮你少走不少弯路。
1. RAG管线里的图文数据:到底卡在哪一步
1.1 知识库能存图片,但存的不是“图片”
RAG的核心机制是向量检索,而向量检索的质量取决于Embedding模型对内容的语义表达能力。如果直接把图片的二进制数据或像素矩阵扔进Embedding模型,得到的向量几乎不携带业务语义,检索出来的结果自然没法用。虽然市面上有CLIP、SigLIP这类多模态Embedding模型,但它们对文本和图片的语义对齐能力有限,主流RAG框架和向量库对多模态检索的支持也远没有纯文本检索成熟,工程上落地成本很高。
所以在实际项目里,几乎所有人都采用同一条路径:图片先解析成文本,文本再分块、Embedding、入库。搞清楚这个前提,再回头看“知识库能不能存图片”这个问题,结论就很明确——能存,但存进去的是解析出来的内容和描述。图片本身最多作为附件保留在元数据里,供前端展示和溯源用。
这也意味着,图文解析是整个RAG项目里离业务语义最近、最值得投入的一环。图片里如果有一句话被OCR漏掉,或者一个表格被解析散架,后面分块再精细、向量模型再先进,也都难以补救。
1.2 PDF是容器,不是单一文件格式
很多新手会把PDF当成一种“文本文件”,直接用某个库一把梭提取文本,结果发现有的PDF提取出来是空的,有的全是乱码,有的格式完全错乱。原因很简单:PDF是一种容器格式,内部可以混装多种内容对象。
一份PDF里可以同时存在文字层、嵌入字体、矢量图形、位图图片、表格线条、注释、书签、表单域。更麻烦的是,同一份PDF的不同页可能是完全不同的东西——前两页是扫描图片,第三页是Word导出的文字版,第四页是CAD打印的矢量图。哪怕同一页里,也可能左边是清晰文字,右边是一张带文字的截图。
这种复杂性决定了没有任何一款工具能包办所有PDF。选型时必须在“提取文字层”和“走图像OCR”两条路线之间做判断,甚至同一份文档要混合使用两种策略。这也是为什么PDF解析工具多如牛毛,但每个工具都有明确短板的原因。
1.3 解析质量直接决定RAG上限
RAG项目的效果瓶颈,很多时候不在模型参数、不在Prompt调优,而在于喂进去的文本本身就是残缺的。我用一个例子说明:某合同扫描件里,关键条款写在一个表格中,如果用常规PDF文本提取,表格结构被完全打散,条款内容变成了一串没有边界的文字;分块时,上半句和下半句被切到两个块里;检索时,问“违约金的计算方式”,返回的块只有半句话。这种问题,靠后面的检索优化根本救不回来。
所以图文解析的产出物,不能是简单的“把字提出来”,而应该是保留标题层级、段落边界、表格结构、阅读顺序的结构化文本。常见的做法是统一输出为Markdown或带层级标签的JSON,让后续的文本分块器能感知文档结构,而不是面对一坨打散的字符流。
2. OCR与多模态大模型:两条图文解析路线的真实边界
2.1 OCR路线:成熟、便宜,但拿到的只是“字符流”
OCR(光学字符识别)是这个领域最成熟的方案。PaddleOCR、Tesseract、百度OCR、阿里OCR都属于这一类。它们的核心能力是把图片里的文字区域识别成可编辑文本,部分OCR还附带版面分析能力,能识别标题、段落、表格的粗略位置。
OCR路线的优势非常明显:速度快、成本低、可本地部署。PaddleOCR在中文场景的识别精度相当能打,而且是开源免费,一张普通发票的识别耗时通常在毫秒级到秒级,跑在普通CPU上就能完成任务。对于纯扫描件、截图、拍摄照片这类内容,OCR是性价比最高的选择。
但OCR路线的天花板也很清晰:它拿到的本质上是“字符流”,不是“结构”。即便带版面分析,OCR对复杂表格、跨栏排版、图文混排的理解仍然有限。表格里的单元格归并、表头层级、文字阅读顺序,常常会被OCR破坏。另外一个隐藏问题是语言覆盖——PaddleOCR默认中文模型很强,但如果文档里有韩文、日文混合,或者少数民族语言,缺模型的情况下就会直接识别失败,甚至输出乱码。这一块后面排坑章节会专门展开。
2.2 多模态大模型路线:能看懂版式,但成本和速度要想清楚
多模态大模型(GPT-4o、Qwen-VL、InternVL等)是近两年图文解析的新变量。它们和OCR最大的区别是:模型不仅能识别文字,还能理解版式逻辑和语义。给一张复杂的合同首页,多模态模型能输出带标题层级和表格结构的Markdown;给一张产品功能截图,它能理解哪部分是小标题、哪部分是正文、哪部分是注释。
这种能力在解析复杂图文混排、表格嵌套、票据、论文首页时优势非常明显。我实测过一份带复杂表格的论文PDF,PaddleOCR把表格内容全部打散成一行行文本,而Qwen-VL直接输出了规整的Markdown表格,后续分块完全不用额外处理。
代价同样明显:慢、贵、上下文有限。多模态模型的推理耗时远高于OCR,批量处理几百页文档时成本要精打细算;同时大模型的输入窗口有限,一个长PDF往往要拆成多页分次解析,然后再拼接,这个拼接过程如果不注意,容易丢内容或产生重复。所以多模态路线适合“重度文档、精度优先”的场景,不适合“海量文档、成本敏感”的场景。
2.3 选型不是二选一:混合策略与判断标准
做了几个项目之后,我的体会是图文解析极少需要“纯OCR”或“纯多模态大模型”走到底,更实用的做法是混合策略。
一个比较通用的判断标准是:文档里文字密度高、结构简单(合同正文、报纸文章、纯扫描页)——用OCR就够;文档里表格复杂、图文混排、版式有强逻辑(论文、产品手册、报表截图)——用多模态大模型或OCR叠加LLM后处理。此外,还有一类场景是OCR先做初筛、再用LLM做结构化提取,例如OCR识别出整页文字后,用Prompt让LLM按字段抽取出合同编号、甲方、乙方、金额、日期。
| 对比维度 | OCR路线 | 多模态大模型路线 |
|---|---|---|
| 解析速度 | 快,批量友好 | 慢,页级推理 |
| 单页成本 | 低,可本地免费 | 高,按Token或按次计费 |
| 中文识别 | 强 | 强 |
| 表格结构理解 | 弱,易散架 | 强,能输出Markdown |
| 版式与阅读顺序 | 依赖版面分析模型 | 自然理解 |
| 多语言覆盖 | 依赖语言模型包,容易漏配 | 模型本身覆盖广 |
| 部署难度 | 低,PaddleOCR一键装 | 高,大显存或走API |
这里要特别提醒:混合策略不是“OCR一把梭再扔给LLM”,而是要根据页面复杂度动态决定。最简单的落地方式是把所有页面先走一遍OCR,统计每页的文本密度和表格线数量;如果某页文本量极低、图片占比极高,再单独走多模态模型。这样既控制成本,又保证复杂页面的解析质量。
3. 九种PDF解析工具横评:选型表与场景判断
3.1 先看总表,再按场景对号入座
PDF解析工具多到让人眼花,但真正被高频使用的就那么几款。我把它们分为三类掌管不同场景:文本层提取类、表格提取类、图像OCR类,再加上几个新锐的深度学习解析工具。先给一张总表,再逐个说人话。
| 工具 | 类型 | 核心能力 | 明显短板 |
|---|---|---|---|
| pypdf(PyPDF2) | 文本层提取 | 合并拆分、文本抽取、元数据读取 | 复杂排版、表格、扫描件无能为力 |
| PyMuPDF(fitz) | 文本层提取 | 极快,文本+图片+注释全量读取 | 表格结构不感知 |
| pdfplumber | 文本层+坐标 | 表格区域识别、字符坐标定位 | 扫描件需搭配OCR,速度一般 |
| Camelot | 表格专用 | 基于线框的表格精确还原 | 无线表格识别差,依赖Ghostscript |
| pdf2image + OCR | 图像OCR | 扫描件/图片型PDF全页识别 | 版面结构弱,需后处理 |
| OCRmyPDF | 图像OCR | 扫描PDF转可搜索PDF | 本质是OCR前端,不做语义理解 |
| Unstructured | 解析全家桶 | 多格式统一接口,自带分块 | 依赖重,版本兼容问题多 |
| Marker | 深度学习解析 | 高质量版面还原为Markdown | 模型大,速度慢,显存要求高 |
| MinerU | 深度学习解析 | 公式、表格、双栏版面综合解析 | 部署门槛偏高,资源消耗大 |
3.2 工具逐个说:优点、短板、适用场景
pypdf:纯Python实现,适合做PDF的合并、拆分、旋转、加密解密和简单文本提取。它的extract_text()在干净的文字版PDF上表现稳定,但不要指望它处理复杂排版。我通常用它做PDF的预处理,比如把一个几百页的PDF按目录先拆成多个独立文件,再用其他工具精解析。
PyMuPDF(fitz):这是我用下来速度最快的文本层提取库。它的C语言绑定让页面文本读取速度比pypdf快一个量级,而且除了文本还能提取图片、矩形框、链接和注释。在处理数字原生的PDF(由Word、LaTeX导出)时,用它提取文本几乎是首选。缺点是它只认文字层,不认图片里的字,对表格也没有结构感知。
pdfplumber:它的强项是字符级别的坐标定位。你可以拿到每个字符的坐标、字体大小、行宽等细节,并基于这套坐标自定义规则还原表格。对带明显线条的规整表格,pdfplumber的识别效果很好。但它的速度偏慢,处理上百页文档时能明显感觉到卡顿。
Camelot:表格专项选手。它通过分析页面的线条结构还原表格单元格,对有线表格的还原精度很高,输出还很方便。Camelot有两个模式:lattice依赖表格线,stream依靠文本相对位置。它的硬伤有两个:一是无线表格基本抓瞎,二是需要系统里装Ghostscript,环境配置麻烦。
pdf2image + OCR:这不是单一工具,而是组合方案——先用pdf2image把PDF每页转成PNG图片,再交给OCR引擎识别。对扫描件和图片型PDF来说,这是最直接的路。PaddleOCR是这套组合里我最常用的OCR引擎,中文识别率高,还支持方向分类,可以处理旋转扫描件。
OCRmyPDF:它的定位是“把扫描PDF变成带文字层的可搜索PDF”。本质上它是在PDF每一页的底层偷偷叠加一层透明文本,让原本只能图片预览的文件可以被搜索、复制。这个中间产物非常实用,我常常把它当作质量追溯的“中间格式”保留下来,方便人工校验解析结果。
Unstructured:野心很大的解析全家桶,目标是统一处理PDF、Word、HTML、PPT等多种格式,输出结构化文档,并内置了分块逻辑。它的好处是接口统一,一个函数就能完成“加载+分区+分块”,写项目时开发效率很高。坑也明显:依赖包极多,版本升级频繁,不同版本之间结果差异大,锁定版本后最好别随便升。
Marker:基于深度学习模型的PDF解析工具,能把PDF还原成接近原始排版质量的Markdown,对标题层级、列表、粗体斜体的还原都做得不错。论文、网页导出的PDF用它解析效果很好。但模型体积大,首次推理要加载不少资源,CPU上跑速度很感人,建议有GPU再考虑。
MinerU:这个工具中文名叫“解析利器”,是开源社区里热度很高的新秀。它对公式、表格、双栏版面、阅读顺序都有专门的模型处理,解析教科书、论文这类学术文档效果突出。MinerU提供的magic-pdf命令行工具封装得不错,一条命令就能完成单篇解析。缺点是部署有门槛,Python环境和模型下载要先折腾一阵,资源占用也偏高。
3.3 特别提醒:在线转换工具与敏感文档
聊工具选型时必须插一句:市面上大量“PDF转Word”“PDF在线转换”网站,虽然方便,但不适合处理敏感文档。合同、发票、内部报告这类文件一旦上传到第三方服务器,就脱离了你的控制。做RAG知识库的,手里往往有一堆业务核心资料,安全和隐私是第一位的。
我的习惯是本地优先:能本地跑的工具就本地跑,PaddleOCR、PyMuPDF、MinerU都能离线使用。只有本地实在跑不动的超大模型推理(比如用多模态大模型精解析),才通过API走云端,而且事先要把可直接识别的隐私字段脱敏或单独审批。这一条建议虽然老套,但踩过坑的人都懂它的价值。
4. 实战组装:按文件特征自动路由的PDF→知识库导入链路
4.1 第一步:探测文件特征,不做无脑解析
一份资料进来,不应直接丢给某个工具,而是先探明它的底细。我通常会做三个层面的探测:文件类型、是否含文字层、图片占比。这三个结果决定了后续走哪条解析管线。
文件类型好理解,PDF、图片(PNG/JPG)、Word各走各的路。关键是PDF内部的探测——用pypdf或PyMuPDF抽取每页文字,如果某一页能抽到足够多的文字,说明这页有文字层,可以直接走文本提取;如果几乎抽不到文字,说明是图片型页面,需要走OCR或图像解析。
from pypdf import PdfReader def detect_pdf_style(pdf_path, threshold=50): reader = PdfReader(pdf_path) styles = {"text_pages": 0, "image_pages": 0, "total_pages": len(reader.pages)} for page in reader.pages: try: text = page.extract_text() or "" except Exception: text = "" if len(text.strip()) > threshold: styles["text_pages"] += 1 else: styles["image_pages"] += 1 return styles这个函数按页统计文字量,超过阈值就认为是文字页,否则是图片页。如果一个PDF里两种页面都有,那就按页切分、分别走不同解析器,最后再合并结果。
4.2 第二步:按路由结果调用不同解析器
探测完成之后,接下来就是组装解析管线。我目前最常用的一条本地管线长这样:文字页用PyMuPDF提取全文,图片页用pdf2image转成图片再交给PaddleOCR,最终所有页统一输出为Markdown。
下面是一段简化但可运行的核心逻辑,基本能应付大部分混合型PDF:
import fitz from pdf2image import convert_from_path from paddleocr import PaddleOCR ocr = PaddleOCR(use_angle_cls=True, lang="ch") def parse_mixed_pdf(pdf_path, dpi=180): doc = fitz.open(pdf_path) output_parts = [] for page_index, page in enumerate(doc): text = page.get_text().strip() if len(text) > 50: output_parts.append(f"## 第{page_index + 1}页\n\n{text}") else: images = convert_from_path(pdf_path, first_page=page_index + 1, last_page=page_index + 1, dpi=dpi) img = images[0] result = ocr.ocr(img, cls=True) page_text = [] if result and result[0]: for line in result[0]: page_text.append(line[1][0]) output_parts.append(f"## 第{page_index + 1}页\n\n" + "\n".join(page_text)) return "\n\n".join(output_parts)代码逻辑不复杂:逐页判断文字层,有文字的直接提取,没文字的OCR。这里有一个参数值得注意,dpi=180是pdf2image转图时的分辨率。DPI太低,小字会糊,OCR准确率直线下降;DPI太高,图片体积膨胀,处理速度变慢。实测下来180到220是一个很好的平衡区间。
如果你手里的PDF是学术论文这类版式复杂的,建议直接上MinerU,它对公式、表格、双栏的处理比“PyMuPDF+OCR”的简易方案高一个级别。我的经验是把MinerU当作“复杂文档专用通道”,和通用管线并存,由路由层统一调度。
4.3 第三步:统一输出Markdown,再做结构化分块
不管走哪条管线,最终产物统一成Markdown的好处非常多。Markdown天然表达标题层级、列表、表格、代码块,而这些恰好是文本分块器最需要的结构信息。后续切分时,可以基于#和##识别章节边界,基于表格行做细粒度切块,比面对一坨纯文本要优雅得多。
我给自己定过一个统一的解析产物规范:每页一个二级标题,段落之间空行,表格用标准Markdown表格语法,图片在原文出现的位置保留占位标注(类似[图片-第3页]),方便溯源。这样设计之后,后面的分块环节只需要做一件事:读入Markdown,按标题层级递归切块。
import re def markdown_to_blocks(md_text, max_chars=800): lines = md_text.split("\n") blocks = [] current_block = [] current_len = 0 for line in lines: if re.match(r"^## ", line) and current_block: blocks.append("\n".join(current_block)) current_block = [] current_len = 0 current_block.append(line) current_len += len(line) if current_len >= max_chars: blocks.append("\n".join(current_block)) current_block = [] current_len = 0 if current_block: blocks.append("\n".join(current_block)) return blocks这套简单的切块逻辑严格以标题为边界,块与块之间不会把章节内容切开。如果业务上需要更精细的语义切分,可以把Markdown转成JSON,再按段落和表格行动态组装成不同粒度的块。
5. 排坑实录:韩文识别失败、扫描件乱码、表格散架与文件格式报错
5.1 PaddleOCR识别不了韩文:先查语言模型
聊到OCR排坑,最常见的疑问就是“PaddleOCR识别不了韩文/日文,请问怎么设置”。我见过不少人在用PaddleOCR做多语言识别时直接翻车,怎么调参数都没用。这里要先弄明白PaddleOCR的语言机制。
PaddleOCR的识别能力是分语言模型打包的,不是装一个包就自带全球语言。要用韩文识别,必须在初始化时指定lang="korean",并且要确认模型列表里有对应的韩文识别模型。如果只是把lang参数改了,但模型没下载、没配置,识别结果自然就是空或乱码。
from paddleocr import PaddleOCR # 韩文需要在运行时单独下载对应模型 ocr = PaddleOCR(use_angle_cls=True, lang="korean")踩坑点在于:PaddleOCR首次运行会尝试自动下载模型,这个下载过程依赖网络环境,模型文件较大时容易中途失败,而且失败后不会给出明确报错,只会默默识别失败。解决方案是手动到官方模型库下载对应语言的检测、分类、识别模型,放到~/.paddleocr/whl/目录下对应位置,再初始化就不会触发自动下载。识别之前,最好先准备一张纯韩文的测试图跑一遍,确认模型真的加载成功,再进入批量管线。
5.2 Tesseract中文乱码:语言包与字符集问题
Tesseract是老牌OCR引擎,但中文用户直接用它处理中文文档时,经常遇到一个结果:识别出来的中文全是乱码,或者干脆显示方块。原因也很朴素——Tesseract默认只装英文语言包,没有中文语言数据。
处理方式是在安装时额外下载chi_sim语言包。Windows安装包可以勾选中文语言组件,Linux下可以单独安装tesseract-ocr-chi-sim包。指定语言时用lang="chi_sim+eng"可以同时识别中英文。另外,Tesseract对图片质量异常敏感,低分辨率、有噪点的扫描件直接丢给它,准确率会跌到没法用的程度。我很少在正式RAG管线里用Tesseract处理中文,它的英文、数字、印刷体识别还行,中文场景PaddleOCR是更省心的选择。
5.3 百度OCR返回file format error:二进制传参的坑
很多人调用百度OCR等云服务时,会看到类似error_msg: "file format error"的报错。这个错误看起来像是文件格式不被支持,但实际排查下来,八成是请求参数里传图片的方式不对。
百度OCR的接口要求:图片要么传base64编码后的字符串,要么传图片的URL。如果你直接把本地文件的二进制内容塞进请求体,或者base64编码时没有去掉换行符、直接把整个bytes对象往里扔,服务端就会报文件格式错误。一个稳健的处理姿势是:读取文件、做base64编码、把字节串转成字符串、去掉换行符,再放进请求参数。
import base64 import requests with open("invoice.jpg", "rb") as f: img_base64 = base64.b64encode(f.read()).decode("utf-8") resp = requests.post( "https://api.example.com/ocr", json={"image": img_base64}, headers={"Content-Type": "application/json"}, ).json()这里有个容易忽略的细节:有些图片本身没问题,但因为扫描后保存成了CMYK格式或16位深的PNG,云端OCR服务不支持,也会报文件格式错误。遇到这种情况,最省事的办法是先用Pillow把图片统一转成RGB模式的JPEG或8位PNG,再走API,基本能消掉这类报错。
5.4 表格散架与图片型PDF的连锁问题
表格散架是RAG图文解析里最隐蔽的坑。表面看OCR识别出的每行文字都对,但表格原本的“字段名——值”对应关系被彻底打断了。比如一份发票,OCR输出的是连续几行文本,金额、税率、税额的位置关系全部丢失,分块之后检索“这张发票的税率是多少”,返回的块里根本没有完整的信息对。
应对思路无非两条:线上表格清晰、有线框的,用Camelot或pdfplumber按坐标提取;表格复杂或无线框的,直接把整页图片交给多模态大模型输出Markdown表格。如果必须用OCR,至少要在后处理阶段根据字符坐标做行合并和单元格拼接,而不是直接把OCR的每一行当作独立文本。
图片型PDF的另一个连锁痛点是页数爆炸。一本几百页的扫描书,每页都是一张大图,OCR一张张跑下来不仅耗时,还容易在中间页出现内存压力。我的做法是:把PDF先按页拆成多个小批次,每批20-30页,分批OCR、分批写盘,最后再合并。这样既避免一次性加载过多图片导致内存溢出,也方便在出错时定位到具体批次重新跑。
最后的几点体会
图文解析在RAG项目里是最容易翻车、但也是最值得投入精力的环节。我见过太多团队把大量时间花在调Embedding模型和Prompt上,却忽略了资料接入阶段“Garbage in, garbage out”的铁律。实际上,一个文档解析管线的合理程度,对最终检索效果的影响远大于你对检索代码的微调。
我在实际项目中沉淀了一个小技巧:始终保留一个“可搜索PDF”中间产物。用OCRmyPDF这类工具把扫描PDF转成带透明文字层的版本,一方面方便人工用PDF阅读器搜索核对,另一方面遇到解析质量争议时,可以直接定位到原始页面做对比,省去了重新翻原始文件的尴尬。这个习惯在批量导入大量扫描件时尤其有用。
最后想说的是,工具选型没有银弹,不同文档类型、不同业务场景下的最优解差异很大。最稳妥的做法是搭一套多通道路由管线,让文件特征自己决定走哪条解析路径,而不是试图用一把锤子敲所有的钉子。这篇文章里的方案都是我踩过坑、也验证过能落地的路线,你可以根据自己的硬件条件和数据特征做裁剪,但有一点别省:解析结果一定要保留结构和溯源信息。这一步做扎实了,后面的RAG整个链路都会轻松很多。