文档入库向量数据库:从解析到 RAG 的完整方案

2026/3/29
RAG向量数据库文档解析DoclingPython

文档入库向量数据库:从解析到 RAG 的完整方案

目录

  1. 问题背景
  2. 核心思路
  3. 文档解析工具对比
  4. 推荐架构
  5. 数据结构设计
  6. 分块策略
  7. 代码示例
  8. 总结与选型建议

问题背景

构建 RAG 系统的第一步,是把各种格式的文档(PDF、Word、Excel)放入向量数据库。这里需要解决两个核心问题:

  1. 文档信息提取 — 如何把不同格式的文档统一转换为可处理的文本
  2. 回溯定位 — 最终要能定位到具体文档的某一页或某一位置

本文调研了主流方案,给出完整的架构建议。


核心思路

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_numbercoordinatesfilename
  • 有内置 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. 其他可选方案

工具特点适用场景
MarkerPDF → Markdown,基于 Nougat 模型学术论文 PDF
pdfplumber简单 PDF 文本/表格提取简单 PDF,轻量场景
python-docxWord 文档解析只处理 DOCX
openpyxlExcel 解析只处理 XLSX
LangChain Document Loaders集成多种加载器已在用 LangChain 的项目

工具对比总结

维度DoclingUnstructuredPyMuPDF4LLM
PDF 质量⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
多格式支持⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐(仅 PDF)
定位/元数据⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
速度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
本地运行✅(基础功能)
开源协议MITApache 2.0AGPL
RAG 集成LangChain/LlamaIndexLangChain/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

关键决策点

  1. 需要支持多少种格式? 如果只有 PDF → PyMuPDF4LLM 够用;多格式 → Docling
  2. 定位精度要求? 如果需要精确到页面坐标 → Docling 或 Unstructured
  3. 是否需要本地运行? 如果数据敏感 → Docling 或 PyMuPDF4LLM
  4. 文档质量如何? 如果大量扫描件 → Docling(OCR)或 Unstructured(VLM 增强)

Docling 基本上就是为这个场景量身定做的——它解决的核心问题就是”把各种文档变成 LLM/RAG 友好的结构化数据,同时保留位置信息以便回溯”。

📝 文章反馈