RAG 工程实践拦路虎之一:PDF 格式解析杂谈
背景
PDF(Portable Document Format)是一种广泛用于文档交换的文件格式,由 Adobe Systems 开发。它具有跨平台性、固定布局和易于打印等特点,因此在商业、学术和个人领域广泛应用。然而,PDF 文件的解析一直是一个具有挑战性的问题,因为其内部结构的复杂性和多样性,使得提取其中的文本、图片和表格等内容并不是一件容易的事情。
在构建 RAG(Retrieval-Augmented Generation)知识库时,非结构化数据的解析质量直接决定了检索的准确性和生成回答的质量。PDF 作为最常见的文档载体,其解析效果是 RAG 系统落地的关键前置条件。
技术方案
在目前的 PDF 文件解析领域中,我们可以将其大致分为以下几类技术方案:
1. LLM/视觉大模型解析
LLM(Large Language Model)大型语言模型在近年来的发展中,展现出了强大的语言理解和生成能力。通过训练大规模的神经网络,可以实现对 PDF 文件中文字内容的理解和提取。这种方法尤其适用于那些布局复杂、内容丰富的 PDF 文件。基于视觉的大模型(如 LayoutLM 系列)能够理解文档的语义结构,将页面视为图像进行处理,从而更好地识别标题、段落和表格关系。
2. OCR 模型
光学字符识别(OCR)模型专门设计用于将 PDF 文件中的图像转换为可编辑的文本。这种技术在处理扫描版或图像化的 PDF 文档时尤其有用。现代 OCR 引擎通常结合了深度学习技术,能够识别手写体、模糊文字以及多语言混合场景。
3. 传统规则提取
传统的 PDF 解析方式可能包括基于规则的文本提取、图像处理和表格识别等方法。虽然这些方法可能不如深度学习模型那样灵活,但在某些情况下仍然是有效的选择。例如,基于坐标过滤的文本提取可以精确控制获取范围,适合结构相对固定的报表。
各个解决方案目前可能需要配合使用,因为 PDF 格式本身的复杂程度,一项技术方案可能是无法 100% 满足业务需求的。这里面需要考虑的是:
- 文档提取还原度:通过技术手段,能够完整的提取 PDF 中的各项元素,包括文本、表格、图片、链接、图形、目录等等信息。
- 高效/成本:在 RAG 知识库问答的产品中,考虑到文本还需要 Embedding 的过程,因此在提取过程中如何更高效,成本更低也是需要着重考虑的事项。
- 稳定/幂等:我们知道大模型可能是出现幻觉的,如果用大模型来提取 PDF 中的内容,是否能足够保证稳定性。确定性算法通常比概率模型更适合基础数据清洗。
当我们处理解析 PDF 时,我们需要可以将每一项的难点都进行拆分,从需求出发,逐一进行攻破,找到解决方案。
其实技术人员如果能通过技术手段确定 PDF 中的 Block(块)以及阅读顺序,按 Block(块)进行输出转换(Markdown/Html 等),这里面包括的 Block 块元素:文本、图片、表格等等。那么这个提取的效果就会达到我们的最优。而这个目标是我们接下来要重点讨论的。
技术难点
在考虑解析 PDF 文件时,我们需要根据当前的技术栈发展情况,并结合实际的业务诉求,综合考量这其中的技术难点,因为每一项技术难点所涉及的技术方案都会需要一个算法或者技术手段去突破。
而开发者从解析的效果去考虑,可以从简单的做起,逐步突破难点,这对于开发人员自身的自信心提升也是一种正向的导向。在整个 PDF 解析过程中,我觉得以下几项是比较难处理的:
1. 布局解析困难
PDF 文件的布局可能会因为不同的作者、工具或用途而有所不同。有些 PDF 是基于流式布局生成的,而有些则是基于绝对坐标定位的。解析其布局是一个具有挑战性的任务,需要区分页眉、页脚、正文和侧边栏。
2. 格式错综复杂
PDF 文件中可能包含各种格式的内容,包括文字、图像、表格等,因此解析其内容需要考虑到这种多样性和复杂性。不同字体、字号、颜色甚至透明度的叠加都可能影响解析结果。
3. 复合表格
纵向/横向合并的复杂表格,在 PDF 中进行抽象还原是最难处理的问题之一。PDF 本身并不原生支持复杂的表格对象,通常是通过绘制线条和定位文本来模拟表格。解析时需要重建单元格关系。
4. 文本、图片、表格顺序提取
提取 PDF 文件中的文本、图片和表格,并确保它们的顺序正确性,是一个需要解决的重要问题。阅读顺序往往与物理位置不一致,特别是在多栏排版中。
5. 文档结构还原
还原 PDF 文件的文档结构,包括标题、目录等信息,是实现自动化文档处理和理解的关键步骤之一。缺乏层级信息的 PDF 会导致后续切片(Chunking)策略失效。
6. 元素重叠
从 PDF 100% 效果还原的角度考虑,图片/文本之间的重叠,图片合并,合并后不失真等,也是需要考虑的事项之一。有时文本层覆盖在图片层之上,导致直接提取文本会丢失图片信息。
7. 元数据提取
在 PDF 中隐藏的元数据信息是 RAG 产品的关键数据,比如链接、目录、字体等等。这些信息对于文档溯源和权限管理至关重要。
8. 扫描件
PDF 中如果是扫描件,依靠 OCR 模型可能是无法有效的提取,这里面包含了清晰度、模型的稳定性等等问题。低分辨率扫描件会导致高错误率。
9. Latex 公式提取
在一些特殊领域,PDF 文本中包含了 Latex 等数学公式。通过完整的提取和转换是对 RAG 问答的有效补充。普通 OCR 很难识别复杂的数学符号。
技术可行性
我们从解析 PDF 的技术可行性角度,考虑哪些方面值得我们重点关注和突破:
- 文字提取能力,逐行提取:确保能够准确地提取 PDF 文件中的文字内容,并按照正确的顺序进行排列和输出,避免文字乱码(字体编码问题)。
- 简单/复杂表格完整提取:对 PDF 文件中的表格进行完整提取,包括表格内的内容和格式。需要识别边框线和单元格边界。
- 图片提取/合并:提取 PDF 文件中的图片,并保留其原始质量和格式。注意图片是否被压缩或分片存储。
- 文档布局 (Block 块的标识) 识别:识别 PDF 文件的布局,包括页面的排列方式、文本和图片的位置等信息。利用坐标聚类算法。
- 文档结构识别 (标题、目录),内容顺序输出:识别 PDF 文件的结构,包括标题、目录等信息,并确保输出内容的顺序正确。利用字体大小和加粗特征辅助判断。
- 转换为 Markdown 格式:将解析后的 PDF 文件内容转换为 Markdown 格式,以便于后续的处理和分享。Markdown 是 RAG 系统中常用的中间格式。
开源技术方案
结合上面的技术难点/方案及可行性上去分析,我们可以看看目前开源的技术组件中,有哪些是我们可以考虑进行结合的。
当前技术栈主要以 Java+Python 双语作为底层的应用开发语言,接下来我们可以看看在这两个编程语言中,有哪些开源的方案可以使用。
Java 生态
在 Java 生态中,对于 PDF 组件处理的开源方案不多见,Apache PDFBOX 是当前最强的,也是最好的。
| 名称 | 说明 |
|---|
| Apache PDFBox | 提供开箱即用的文本、图片内容提取方式,并且可以基于 Stream 接口重写各项元素的解析实现,并能输出元素的坐标信息。开发者可以根据元素的坐标信息结合算法进行内容的高度还原。唯一的缺点是没有表格组件提取的 API 供开发人员使用。 |
| tabula-java | 基于 Apache PDFBox 组件的表格提取实现,专注于表格区域的数据抓取。 |
Java 代码示例:使用 PDFBox 提取文本与坐标
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import java.io.IOException;
public class PdfExtractor {
public static void main(String[] args) throws IOException {
try (PDDocument document = PDDocument.load(new File("input.pdf"))) {
PDFTextStripper stripper = new PDFTextStripper();
String text = stripper.getText(document);
System.out.println(text);
}
}
}
Python 生态
Python 生态的 PDF 提取组件还是蛮多的,不过也是有不同的侧重,比如 pdfplumber、camelot 等都专注在表格的提取上,提供了开箱即用的方案。
| 名称 | 说明 |
|---|
| pypdf | 一个纯 Python PDF 库,能够分割、合并、裁剪和转换 PDF 文件的页面。轻量级,适合基础操作。 |
| PyMuPDF(AGPL) | 高性能 Python 库,用于 PDF(和其他)文档的数据提取、分析、转换和操作。速度极快,支持丰富功能。 |
| pdfplumber | 查看 PDF 以获取有关每个字符、矩形、线条等的详细信息,并轻松提取文本和表格。MIT 协议。 |
| Camelot | 专注于 PDF 中表格的提取,包括复杂的表格。支持多种后端引擎。MIT 协议。 |
Python 代码示例:使用 PyMuPDF 提取带坐标的文本
import fitz
def extract_text_with_coords(pdf_path):
doc = fitz.open(pdf_path)
for page_num, page in enumerate(doc):
blocks = page.get_text("dict")["blocks"]
for block in blocks:
if "lines" in block:
for line in block["lines"]:
for span in line["spans"]:
text = span["text"]
bbox = span["bbox"]
print(f"Page {page_num}: {text} at {bbox}")
doc.close()
OCR 生态/大模型
在上面 Python 和 Java 生态库的开源组件,基本都是针对文字的 PDF 处理为主,当我们的 PDF 是扫描件时,那上面的组件统统失效,都提取不出来文本信息。此时就需要用到 OCR 的模型进行提取。
考虑到如果是 OCR 提取,那么最终的目的是将 PDF 文件 Page 页码内容提取出完整的图片 Image,所以本质上是对图片内容的理解。
可以考虑的开源组件如下:
| 名称 | 说明 |
|---|
| marker(GPL) | 基于模型将 PDF 文件内容提取为 Markdown 格式,适合快速转换。 |
| surya(GPL) | OCR、布局分析、阅读顺序、线条检测(支持 90 多种语言)。 |
| tesseract(Apache 2) | 老牌 OCR 组件,支持 100 多种语言,需配置语言包。 |
| RapidOCR(Apache) | 基于 ONNXRuntime、OpenVINO 和 PaddlePaddle 的出色 OCR 多种编程语言工具包。 |
| PaddleOCR(Apache) | 基于飞桨的出色多语言 OCR 工具包(实用的超轻量级 OCR 系统,支持 80+ 语言识别)。 |
| EasyOCR(Apache) | Python/C++开发,支持 80 多种语言 OCR 识别,易用性强。 |
技术准备/细节
在解析 PDF 时,我们也会有一些其他方面的知识储备,以便我们快速应对不同的业务需求及应用产品形态。
1. 图形类 API
不管是 Java 还是 Python 里面,对于处理 PDF 中间件的部分,都需要对图形类的 API/算法熟悉和掌握,这里面包含图形的转换、缩放、矩阵坐标、截取等等,都会在 PDF 提取的过程中使用到。
2. PDF 标准
在处理 PDF 中,结合开源的技术中间件,对于 PDF 的 ISO 标准,我们也是需要了解的,这样更加有利于开发人员理解中间件的代码写法及含义。了解 PDF 对象树结构有助于调试解析失败的情况。
3. 边/线/矩阵算法等
对于文本/边框的聚类算法等,在根据元素坐标高效还原时,利用高效的算法可以提高解析速度以及内容还原度。例如使用 DBSCAN 聚类算法来识别表格行和列。
4. OCR/LLM 模型等
了解学习在用 OCR/LLM 模型分析布局、边界检测等等技术上的一些算法及数据工程上的实践。例如使用 Transformer 架构进行文档布局分析。
5. PDF 页面旋转
有时候原 PDF 可能会有旋转 (0、90、180、270 度),需先校正后,再次提取内容。可以通过检查页面字典中的 Rotate 属性来实现自动校正。
6. 字体/乱码
系统/服务器中缺失 PDF 中的字体,导致文本提取乱码。解决方法包括嵌入字体或使用通用字体映射表,或者在提取前将 PDF 渲染为图片再 OCR。
实施策略与 RAG 集成
为了将解析后的 PDF 内容有效应用于 RAG 系统,除了提取本身,还需要考虑后续的切片和索引策略。
1. 基于 Block 的切片
不要简单地按字符数切分。应优先保持语义完整性。如果一个 Block 是完整的段落或表格,尽量不切断它。如果超过最大 Token 限制,再在句子级别进行截断。
2. 元数据注入
在提取文本的同时,务必保留来源文件名、页码、章节标题等元数据。这些信息在检索阶段可以作为过滤条件(Filter),提高召回准确率。
3. 评估体系
建立解析质量的评估体系。人工抽检是关键。指标包括:文本准确率、表格结构还原率、图片关联正确率。定期对比不同解析工具的产出差异。
4. 异常处理机制
PDF 解析极易遇到异常情况(损坏文件、加密文件、特殊编码)。系统必须具备健壮的异常捕获和降级处理能力,例如遇到解析失败的页面自动跳过或标记为待人工处理,而不是导致整个任务崩溃。
总结
PDF 解析是 RAG 工程实践中不可忽视的基础设施。没有高质量的解析,就没有高质量的向量检索。建议采用混合策略:常规文本用传统库(PyMuPDF/PDFBox),复杂表格用专用库(Camelot/pdfplumber),扫描件用 OCR(PaddleOCR/Tesseract),复杂布局尝试视觉大模型。同时,重视坐标信息的保留和阅读顺序的重建,这是实现高精度还原的核心。