文档加载器:从PDF、网页、数据库加载数据

做RAG系统,第一步就是喂数据。

但数据在哪?PDF文件里、网页上、数据库里。格式五花八门,编码乱七八糟。我见过不少团队,模型选得挺好,向量库也搭得漂亮,结果卡在数据加载这一步——PDF乱码、网页被反爬、数据库连不上。

说白了,文档加载器就是RAG系统的「入口守卫」。它负责把原始数据变成结构化的文本块。今天咱们就聊聊,怎么用LangChain和LlamaIndex搞定这件事。

核心观点:文档加载器不是简单的「读文件」,而是「理解文件」。不同格式需要不同策略,选错了加载器,后面整个pipeline都会出问题。

为什么文档加载这么重要?

我刚开始做RAG时,犯过一个低级错误。用PyPDF2直接读PDF,结果表格数据全丢了,模型回答时总说「找不到相关信息」。后来才发现,PDF里的表格需要专门的解析器。

文档加载器要解决三个问题:

  • 格式兼容——PDF、Word、HTML、Markdown,每种格式都有自己的「脾气」
  • 内容提取——文字、表格、图片、元数据,哪些该留哪些该扔
  • 分块预处理——加载时就做好初步清洗,减少后续工作量

你想想看,如果加载阶段就丢了30%的内容,后面检索再强也白搭。

PDF加载:最头疼的格式

PDF是RAG系统里最常见的输入格式,也是最坑的。为什么?因为PDF本质上是「打印布局」,不是「文本结构」。同一个PDF,不同加载器读出来的结果可能天差地别。

加载器 适用场景 优点 缺点
PyPDFLoader 纯文本PDF 速度快,依赖少 表格/图片全丢
PyMuPDFLoader 带表格的PDF 保留布局信息 中文有时乱码
PDFPlumberLoader 表格密集型PDF 表格提取最准 速度慢
UnstructuredPDFLoader 复杂排版PDF 自动识别段落 依赖太多
我的经验:如果PDF里表格多,优先用PDFPlumberLoader。如果主要是文字段落,PyMuPDFLoader就够了。别一上来就用Unstructured,它装依赖能装到你怀疑人生。

来看个实际例子。用LangChain加载PDF:

from langchain_community.document_loaders import PyMuPDFLoader

loader = PyMuPDFLoader("report.pdf")
documents = loader.load()

# 每个document包含page_content和metadata
for doc in documents[:2]:
    print(f"页码: {doc.metadata['page']}")
    print(f"内容预览: {doc.page_content[:100]}...")

嗯,这里要注意。metadata里通常有页码、来源文件名,有些加载器还会带上PDF的创建日期。这些信息在后续检索时很有用,可以用来做过滤。

网页加载:小心反爬和动态内容

网页加载比PDF更麻烦。静态页面还好说,遇到JavaScript渲染的SPA页面,直接请求HTML是拿不到内容的。

LangChain提供了几种网页加载器:

  • WebBaseLoader——最简单的,直接请求URL,适合静态页面
  • SeleniumURLLoader——启动浏览器渲染,适合动态页面
  • PlaywrightURLLoader——类似Selenium,但更轻量

我曾经爬一个技术博客,用WebBaseLoader死活拿不到文章正文。后来发现页面是Vue写的,内容通过API异步加载。换成SeleniumURLLoader才搞定。

from langchain_community.document_loaders import WebBaseLoader

loader = WebBaseLoader(
    web_paths=("https://example.com/blog",),
    bs_kwargs=dict(
        parse_only=bs4.SoupStrainer(class_=("post-content", "article-body"))
    )
)
docs = loader.load()
避坑指南:网页加载一定要设置User-Agent和请求间隔。我有个项目没注意,连续请求太快,直接被目标网站封了IP。后来加了time.sleep(1)才解决。

数据库加载:结构化数据的入口

很多RAG系统的知识库其实存在数据库里。MySQL、PostgreSQL、MongoDB,这些都能作为文档来源。

数据库加载的核心思路是:把每一行记录变成一个Document。字段名变成metadata,内容字段变成page_content。

数据库类型 LangChain加载器 典型用途
SQL数据库 SQLDatabaseLoader 产品信息、用户手册
MongoDB MongoDBLoader 日志、配置文档
CSV文件 CSVLoader 表格数据、导出记录
from langchain_community.document_loaders import SQLDatabaseLoader

loader = SQLDatabaseLoader(
    "sqlite:///knowledge.db",
    query="SELECT title, content, category FROM articles WHERE status='published'"
)
docs = loader.load()

我个人习惯把数据库加载和增量更新结合起来。每天跑一次定时任务,只加载新增或修改的记录,避免重复处理。

LlamaIndex的文档加载器

LlamaIndex的加载器设计思路和LangChain不太一样。它更强调「文档解析」和「节点提取」的分离。

在LlamaIndex里,加载流程是:

  1. 用Reader读取原始数据
  2. 用Parser解析成结构化文档
  3. 用NodeParser切分成节点

这种分层设计的好处是灵活。你可以换不同的Parser来处理同一份数据,而不需要改Reader。

from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter

# 加载目录下所有PDF
reader = SimpleDirectoryReader(
    input_dir="./docs",
    recursive=True
)
documents = reader.load_data()

# 切分节点
parser = SentenceSplitter(chunk_size=512, chunk_overlap=50)
nodes = parser.get_nodes_from_documents(documents)
小技巧:LlamaIndex的SimpleDirectoryReader支持自动识别文件类型。你把PDF、Word、Markdown混放在一个目录里,它都能读。适合快速原型开发。

知识体系总览

下面这张图,把文档加载器的核心逻辑串起来了。你可以看到,无论数据源是什么,最终都要变成统一的Document格式,才能进入后续的分块和向量化流程。

文档加载器知识体系 PDF文件 网页数据 数据库 PDF加载器 网页加载器 数据库加载器 统一Document格式 文本分块 → 向量化 无论数据源是什么,最终都要变成统一的Document格式,才能进入后续流程 不同加载器负责处理不同格式的「方言」,输出统一的「普通话」

实战建议:怎么选加载器?

选加载器没有银弹。我一般按这个顺序决策:

  • 先看数据量——小规模用PyMuPDFLoader,大规模用Unstructured(虽然慢但准)
  • 再看内容类型——纯文本用轻量加载器,表格多上PDFPlumber
  • 最后看部署环境——服务器上没浏览器就别用Selenium加载器
注意:千万别在生产环境用未经测试的加载器。我有个同事直接把UnstructuredPDFLoader部署到线上,结果PDF里有个特殊字体,加载器直接崩溃,整个RAG服务挂了半小时。

文档加载器是RAG系统的第一道关卡。选对了,后面一路顺畅;选错了,后面所有环节都在给前面填坑。花时间把这一步做好,绝对值。