2.2 文档加载与解析技术 从文件到知识:RAG的第一道关卡 文档加载与解析是RAG数据管线的入口。用户的知识通常以各种文件格式存储——PDF报告、Word文档、网页、Markdown笔记、Excel表格、PPT演示文稿等。将这些异构格式的文件高效、准确地转换为可被RAG系统消费的结构化文本,是构建高质量知识库的第一道关卡。 一个优秀的文档加载与解析系统需要同时解决三个问题:兼容性(支持多少种格式)、准确性(解析保真度有多高)、效率(处理速度和资源消耗如何)。本章将深入探讨各种文档格式的解析技术、工具选型以及工程实践。
文档加载与解析是RAG数据管线的入口。用户的知识通常以各种文件格式存储——PDF报告、Word文档、网页、Markdown笔记、Excel表格、PPT演示文稿等。将这些异构格式的文件高效、准确地转换为可被RAG系统消费的结构化文本,是构建高质量知识库的第一道关卡。
一个优秀的文档加载与解析系统需要同时解决三个问题:兼容性(支持多少种格式)、准确性(解析保真度有多高)、效率(处理速度和资源消耗如何)。本章将深入探讨各种文档格式的解析技术、工具选型以及工程实践。
设计一个统一的文档加载器抽象层,屏蔽底层格式差异:
from abc import ABC, abstractmethod from dataclasses import dataclass @dataclass class Document: content: str # 文本内容 metadata: dict # 元数据(标题、作者、日期等) page_info: list # 页码/位置信息,用于溯源 sections: list # 结构化章节信息 class BaseLoader(ABC): @abstractmethod def load(self, source: str) -> list[Document]: """加载并解析文档,返回Document列表""" pass @abstractmethod def supported_formats(self) -> list[str]: """返回支持的文件格式列表""" pass
通过注册机制管理不同格式的加载器,支持灵活扩展:
class LoaderRegistry: def __init__(self): self.loaders = {} def register(self, format_ext: str, loader: BaseLoader): self.loaders[format_ext.lower()] = loader def get_loader(self, file_path: str) -> BaseLoader: ext = file_path.rsplit('.', 1)[-1].lower() return self.loaders[ext] registry = LoaderRegistry() registry.register('pdf', PDFLoader()) registry.register('docx', DocxLoader()) registry.register('html', HTMLLoader()) # ... 按需注册更多格式
PDF是RAG场景中最常见也最棘手的文档格式。PDF设计初衷是"所见即所得"的打印格式,而非结构化的数据格式,因此解析难度较大。
直接提取PDF中嵌入的文本层。适用于文本型PDF(由Word等工具生成的电子文档)。
工具选择:
局限性:对扫描件、图片型PDF完全无效;对复杂排版(多栏、脚注、页眉页脚混排)的文本顺序可能混乱。
对PDF进行图像渲染后使用OCR引擎识别文字。适用于扫描件和图片型PDF。
工具选择:
pytesseract 在Python中调用。局限性:处理速度慢(每页数秒);识别精度受原始图像质量影响大;数学公式、手写体识别效果有限。
使用基于深度学习的文档理解模型,不仅能提取文本,还能理解文档的逻辑结构。
工具选择:
局限性:计算资源需求高;部署复杂度较大;对特殊排版(如古籍、工程图纸)支持有限。
根据PDF质量和数量选择策略:
| PDF类型 | 特征 | 推荐策略 |
|---|---|---|
| 文本型PDF | 可选中文字、复制粘贴 | PyMuPDF文本提取 |
| 混合型PDF | 文字为主+少量图片 | PyMuPDF + 图片区域OCR |
| 扫描件PDF | 无法选中文字 | PaddleOCR / 云服务OCR |
| 复杂版面PDF | 多栏、表格、图表混排 | LayoutLM / MinerU / RAGFlow |
最佳实践:先尝试文本提取(快速且免费),如果提取结果质量不达标(文本过短、乱码多),再降级到OCR或深度理解方案。
Word文档(.docx)的结构化程度远高于PDF,解析相对简单:
工具选择:
from docx import Document def parse_docx(file_path): doc = Document(file_path) content = [] metadata = {} # 提取元数据 core_props = doc.core_properties metadata = { 'title': core_props.title, 'author': core_props.author, 'created': str(core_props.created), 'modified': str(core_props.modified), } # 提取内容(保留结构) for para in doc.paragraphs: style = para.style.name text = para.text.strip() if text: content.append({ 'text': text, 'style': style, # 'Heading 1', 'Normal' 等 'level': get_heading_level(style) }) return content, metadata
注意事项:
网页是最常见的知识来源之一,但网页内容中充斥着噪声:
工具选择:
import trafilatura def parse_webpage(url): downloaded = trafilatura.fetch_url(url) if downloaded: result = trafilatura.extract(downloaded, include_comments=False, include_tables=True, favor_precision=True ) metadata = trafilatura.extract_metadata(downloaded) return result, metadata return None, None
高级技巧:
Markdown是结构化程度最高的纯文本格式,天然适合RAG系统:
#、##、###)直接对应文档结构,可用于智能分块。markdown-it-py 等库解析为AST(抽象语法树),便于精确提取结构。表格数据需要特殊处理:
openpyxl(Excel)、pandas(CSV/Excel)、camelot(PDF表格提取)。python-pptx。在实际项目中,文档数量可能达到数万甚至数十万:
利用多进程/多线程加速批量文档解析:
from concurrent.futures import ProcessPoolExecutor def batch_parse(file_paths, workers=4): with ProcessPoolExecutor(max_workers=workers) as executor: results = list(executor.map(parse_document, file_paths)) return results
记录已处理文件的清单,支持中断后从断点继续:
import hashlib, json, os def get_file_hash(filepath): with open(filepath, 'rb') as f: return hashlib.md5(f.read()).hexdigest() class ParseStateManager: def __init__(self, state_file='parse_state.json'): self.state = self._load_state(state_file) def is_processed(self, filepath): fhash = get_file_hash(filepath) return fhash in self.state def mark_done(self, filepath, metadata): fhash = get_file_hash(filepath) self.state[fhash] = metadata self._save_state()
大文件解析时注意内存控制:
解析完成后需要对结果进行质量评估:
文档加载与解析是RAG系统数据管线的起点,也是质量的第一道防线。核心要点:
下一节我们将讨论文档分割策略,了解如何将解析后的完整文档切分为适合向量化和检索的文本片段。