文档入库向量数据库:从解析到 RAG 的完整方案
2026/3/29
RAG向量数据库文档解析DoclingPython
文档入库向量数据库:从解析到 RAG 的完整方案
目录
问题背景
构建 RAG 系统的第一步,是把各种格式的文档(PDF、Word、Excel)放入向量数据库。这里需要解决两个核心问题:
- 文档信息提取 — 如何把不同格式的文档统一转换为可处理的文本
- 回溯定位 — 最终要能定位到具体文档的某一页或某一位置
本文调研了主流方案,给出完整的架构建议。
核心思路
PDF/DOCX/XLSX/PPTX → 统一解析为 Markdown → 语义分块 → 附加定位元数据 → Embedding → 存入向量数据库
选择 Markdown 作为中间格式的原因:
- 按段落/标题天然分块,方便语义切分
- 保留文档结构(标题层级、列表、表格、代码块)
- 几乎所有 LLM 和 Embedding 模型都对 Markdown 格式有良好的理解
文档解析工具对比
1. Docling(⭐ 推荐首选)
IBM Research 出品,LF AI & Data 基金会项目,专门为 RAG/GenAI 场景设计。
from docling.document_converter import DocumentConverter
converter = DocumentConverter()
result = converter.convert("report.pdf")
md = result.document.export_to_markdown()
支持格式: PDF、DOCX、PPTX、XLSX、HTML、图片(PNG/TIFF/JPEG)、LaTeX、音频(WAV/MP3)、WebVTT、纯文本
核心优势:
- 一站式多格式支持,不需要为每种文件类型选不同的库
- PDF 解析能力最强:页面布局分析、阅读顺序推断、表格结构还原、公式识别、图片分类
- 输出 DoclingDocument 统一中间表示格式,保留完整的层级结构
- 自带页码和位置信息(bounding box),天然支持回溯定位需求
- 可选 VLM(视觉语言模型)增强解析质量,如
GraniteDocling - 本地运行,不需要调用外部 API,适合敏感数据
- 内置 OCR 支持(扫描件 PDF)
- MIT 开源
DoclingDocument 的定位能力:
每个文档元素都带有:
- 页码(page number)
- 页面坐标(bounding box)
- 元素类型(标题、段落、表格、图片等)
- 层级关系(属于哪个章节)
这使得后续的回溯定位变得非常自然。
2. Unstructured
商业+开源混合模式,格式支持最全面。
from unstructured.partition.auto import partition
elements = partition(filename="report.pdf")
# elements 是结构化列表,每个带 type, metadata(页码、坐标等)
核心优势:
- 支持 30+ 种文件类型
- 每个 Element 自带丰富元数据(
page_number、coordinates、filename) - 有内置 Chunking 功能
- 提供 SaaS 平台和 API,适合不想自建解析管线的团队
劣势:
- 高级功能(VLM 增强、生成式 OCR)需要付费
- 开源版 PDF 解析质量不如 Docling
- SaaS 版本依赖外部服务
3. PyMuPDF4LLM
PyMuPDF 的 LLM 专用子模块,追求极致性能。
import pymupdf4llm
md_text = pymupdf4llm.to_markdown("report.pdf", page_chunks=True)
# 返回按页切分的 markdown + 元数据
核心优势:
- 速度最快(C 语言底层 MuPDF 引擎)
- 支持页级元数据
- 轻量,依赖少,安装简单
劣势:
- 只支持 PDF,不支持 Word/Excel
- 表格和复杂布局的解析不如 Docling
- 定位信息相对粗糙
4. 其他可选方案
| 工具 | 特点 | 适用场景 |
|---|---|---|
| Marker | PDF → Markdown,基于 Nougat 模型 | 学术论文 PDF |
| pdfplumber | 简单 PDF 文本/表格提取 | 简单 PDF,轻量场景 |
| python-docx | Word 文档解析 | 只处理 DOCX |
| openpyxl | Excel 解析 | 只处理 XLSX |
| LangChain Document Loaders | 集成多种加载器 | 已在用 LangChain 的项目 |
工具对比总结
| 维度 | Docling | Unstructured | PyMuPDF4LLM |
|---|---|---|---|
| PDF 质量 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 多格式支持 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐(仅 PDF) |
| 定位/元数据 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 速度 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 本地运行 | ✅ | ✅(基础功能) | ✅ |
| 开源协议 | MIT | Apache 2.0 | AGPL |
| RAG 集成 | LangChain/LlamaIndex | LangChain/LlamaIndex | 需自建 |
推荐架构
┌──────────────────────────────────────────────────────┐
│ 文档入口层 │
│ PDF / DOCX / XLSX / PPTX / HTML / 图片 │
└──────────────────┬───────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ Docling(统一解析) │
│ → DoclingDocument(统一中间表示) │
│ → export_to_markdown() + 完整元数据 │
└──────────────────┬───────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ 分块层(Chunking) │
│ 按 Markdown 语义结构分块(标题/段落/表格) │
│ 每个块附加定位元数据(页码、坐标、章节) │
└──────────────────┬───────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────┐
│ Embedding + 向量数据库 │
│ 存入 chunk text + embedding + metadata │
│ 支持:Milvus / Qdrant / Chroma / Pinecone / pgvector │
└──────────────────────────────────────────────────────┘
数据结构设计
每个存入向量库的 Chunk 结构:
{
"id": "doc_001_page_3_chunk_2",
"text": "实际文本内容...",
"embedding": [0.12, -0.34, ...],
"metadata": {
"doc_id": "doc_001",
"doc_name": "产品需求文档v2.pdf",
"doc_type": "pdf",
"page_number": 3,
"section_title": "3.2 系统架构",
"chunk_index": 2,
"start_char": 156,
"end_char": 423,
"bbox": [72.0, 350.2, 520.0, 480.5],
"created_at": "2026-03-29"
}
}
元数据字段说明
| 字段 | 说明 | 用途 |
|---|---|---|
doc_id | 文档唯一标识 | 按文档筛选 |
doc_name | 原始文件名 | 展示给用户 |
doc_type | 文件类型(pdf/docx/xlsx) | 区分处理逻辑 |
page_number | 页码 | 定位到具体页 |
section_title | 所属章节标题 | 语义定位 |
chunk_index | 块在文档中的顺序 | 恢复阅读顺序 |
start_char / end_char | 字符偏移量 | 精确定位到段落 |
bbox | 页面坐标 [x1, y1, x2, y2] | 高亮显示原文位置 |
created_at | 入库时间 | 增量更新 |
检索时的回溯流程
用户提问 → 向量检索 → 返回相关 chunks
↓
从 metadata 取 doc_id + page_number + bbox
↓
打开原文档,跳转到对应页码,高亮 bbox 区域
分块策略
不要简单按固定字符数切分,建议采用语义感知的分块策略:
1. 按 Markdown 结构切分
以标题(##、###)为天然分界,保持章节语义完整性:
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "h1"),
("##", "h2"),
("###", "h3"),
]
splitter = MarkdownHeaderTextSplitter(headers_to_split_on)
chunks = splitter.split_text(md_text)
2. 特殊元素保护
- 表格保持完整:一个表格不拆分到两个 chunk
- 代码块保持完整:代码块不拆分
- 列表保持完整:同层级列表项放一起
3. 控制块大小
对于过长的章节,用 RecursiveCharacterTextSplitter 二次切分:
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 目标 300-800 tokens
chunk_overlap=50, # 相邻块重叠 10-20%
separators=["\n## ", "\n### ", "\n\n", "\n", "。", ",", " "]
)
4. 分块大小经验值
| 目标 | 建议 chunk size |
|---|---|
| 语义完整性优先 | 800-1200 tokens |
| 检索精度优先 | 300-500 tokens |
| 平衡方案 | 500-800 tokens |
代码示例
完整 Pipeline
from docling.document_converter import DocumentConverter
from langchain.text_splitter import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# ============ 1. 文档解析 ============
converter = DocumentConverter()
result = converter.convert("report.pdf")
doc = result.document
# 获取 Markdown 文本
md_text = doc.export_to_markdown()
# 获取带元数据的元素列表(页码、坐标等)
# Docling 的 DoclingDocument 提供了每个元素的定位信息
elements = doc.iterate_items() # 每个元素带 page, bbox 等
# ============ 2. 结构化分块 ============
# 第一层:按标题结构切分
headers_to_split_on = [("#", "h1"), ("##", "h2"), ("###", "h3")]
md_splitter = MarkdownHeaderTextSplitter(headers_to_split_on)
md_chunks = md_splitter.split_text(md_text)
# 第二层:控制大小
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50
)
final_chunks = text_splitter.split_documents(md_chunks)
# ============ 3. 附加定位元数据 ============
for chunk in final_chunks:
chunk.metadata.update({
"doc_name": "report.pdf",
"doc_type": "pdf",
"doc_id": "report_pdf_v2",
# page_number 和 bbox 从 Docling 元数据映射
# "page_number": ...,
# "bbox": ...,
})
# ============ 4. 向量化入库 ============
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(
documents=final_chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
# ============ 5. 检索 ============
results = vectorstore.similarity_search("系统架构设计", k=5)
for r in results:
print(f"[{r.metadata['page_number']}页] {r.page_content[:100]}...")
批量处理多文件
import os
from pathlib import Path
def ingest_directory(dir_path: str, vectorstore):
"""批量入库一个目录下的所有文档"""
converter = DocumentConverter()
supported = {'.pdf', '.docx', '.xlsx', '.pptx', '.html'}
for file_path in Path(dir_path).rglob('*'):
if file_path.suffix.lower() in supported:
print(f"Processing: {file_path.name}")
result = converter.convert(str(file_path))
md_text = result.document.export_to_markdown()
# 分块 + 附加元数据 + 入库
chunks = process_and_chunk(md_text, file_path)
vectorstore.add_documents(chunks)
print("Done!")
总结与选型建议
| 场景 | 推荐方案 |
|---|---|
| 首选(多格式 + 定位需求) | Docling — 多格式、定位能力强、RAG 专用设计 |
| 只要 PDF、追求极致速度 | PyMuPDF4LLM |
| 格式最多、愿意付费 | Unstructured |
| 简单场景、快速验证 | LangChain Document Loaders |
关键决策点
- 需要支持多少种格式? 如果只有 PDF → PyMuPDF4LLM 够用;多格式 → Docling
- 定位精度要求? 如果需要精确到页面坐标 → Docling 或 Unstructured
- 是否需要本地运行? 如果数据敏感 → Docling 或 PyMuPDF4LLM
- 文档质量如何? 如果大量扫描件 → Docling(OCR)或 Unstructured(VLM 增强)
Docling 基本上就是为这个场景量身定做的——它解决的核心问题就是”把各种文档变成 LLM/RAG 友好的结构化数据,同时保留位置信息以便回溯”。