# Document 组件与文档加载器的使用

Document 是 LangChain 中的核心组件,它定义了一个文档对象的结构,包含文本内容和相关元数据。Document 也是文档加载器、文档分割器、向量数据库、检索器之间交互传递的状态数据。

在较新的 LangChain 版本中,Document 主要承担基础记录功能:

Document = page_content 页面内容 + metadata 元数据
1

在 RAG 应用中,通常不会手动录入知识数据,而是读取特定来源的数据,例如本地 Markdown 文件、HTML 网页、PDF 文档、DOC 文档、URL 链接等。读取完成后,再将原始文档切割成特定大小的文档片段,最后存储到向量数据库中。

因此,一个完整的 RAG 应用外部通常会有一条额外的数据处理链路,专门处理:

读取数据 -> 切割数据 -> 存储数据
1

这条链路往往比较耗时。例如上传一个较大的文档,需要执行加载、切割、文本嵌入、写入向量库等操作,实际系统中通常会使用队列或异步任务处理。

RAG 应用架构中的文档加载链路

在这条架构链路中,文档加载器的作用是从各式各样的数据源中提取相应信息,并转换成标准 Document,从而屏蔽不同类型文件的读取差异。

# BaseLoader 的统一方法

LangChain 中所有文档加载器的基类是 BaseLoader,它封装了统一的加载方法。

BaseLoader 与常见文档加载器

方法 作用
load() 同步加载文档,返回文档列表
aload() 异步加载文档,返回文档列表
load_and_split() 加载文档后,根据传入的分割器切分文档
lazy_load() 懒加载文档,返回迭代器
alazy_load() 异步懒加载文档,返回异步迭代器

load() 适合小文件或一次性读取的场景。lazy_load() 更适合大文件、文件夹、批量文档等情况,因为它不需要等所有文档全部加载完成后才返回结果。

load_and_split() 可以在加载后直接切分文档,但在更复杂的工程中,通常会把“加载、清洗、切分、入库”拆成独立步骤,这样更容易定位问题。

# 最新版 LangChain 用法提示

Document 和 Loader 在最新版 LangChain 中仍然是推荐使用的 RAG 数据入口方式。需要注意的是,load_and_split() 虽然在一些版本中仍能看到,但新代码不建议把加载和切分绑在一起,最好显式拆开。

推荐写法:

from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

loader = TextLoader("./电商产品数据.txt", encoding="utf-8")
documents = loader.load()

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    add_start_index=True,
)
chunks = text_splitter.split_documents(documents)
1
2
3
4
5
6
7
8
9
10
11
12

大文件或批量文件更推荐懒加载:

from langchain_community.document_loaders import TextLoader

loader = TextLoader("./large.txt", encoding="utf-8")

for document in loader.lazy_load():
    # 可以在这里逐个清洗、切分、写入队列或继续处理
    print(document.metadata)
1
2
3
4
5
6
7

不优先推荐把加载和切分直接合在一起:

# 不优先推荐:步骤耦合,不方便检查加载结果和切分结果
chunks = loader.load_and_split(text_splitter)
1
2

原因是 RAG 数据处理经常需要在加载后先检查内容质量,例如是否乱码、是否有网页导航噪声、metadata 是否完整。如果直接 load_and_split(),中间状态不容易观察。

# TextLoader 的使用

TextLoader 是最简单的文档加载器之一,它可以加载文本文件,例如 .txt、源码文件、Markdown 等文本结构文件。它会把整个文件内容读入一个 Document,并在 metadata 中添加 source 字段,用来记录源数据来源。

示例代码如下:

from langchain_community.document_loaders import TextLoader

# 1. 构建加载器
loader = TextLoader("./电商产品数据.txt", encoding="utf-8")

# 2. 加载数据
documents = loader.load()

print(documents)
print(len(documents))
print(documents[0].metadata)
1
2
3
4
5
6
7
8
9
10
11

输出结果中,documents 是一个 Document 列表。第一个 Document 的 page_content 保存文本文件内容,metadata 中会包含类似下面的信息:

{"source": "./电商产品数据.txt"}
1

这说明 TextLoader 不只是读取文本,还会把来源信息写入 Document。后续切分、向量化、检索、展示引用时,都可以继续使用这个 source。

# Document 的结构

一个 Document 至少要关注两个字段:

字段 作用
page_content 文档正文,会参与切分、向量化和 Prompt 组装
metadata 文档元数据,用于来源追踪、过滤、权限控制、引用展示

示例:

from langchain_core.documents import Document

document = Document(
    page_content="用户申请退款后,平台会在 1-3 个工作日内完成审核。",
    metadata={
        "source": "refund_policy.md",
        "doc_id": "refund-policy-v3",
        "page": 3,
        "version": "2026-08",
    },
)
1
2
3
4
5
6
7
8
9
10
11

在知识库系统中,metadata 不是可有可无的字段。常见 metadata 包括:

字段 含义
source 原始文件路径、URL 或数据源名称
doc_id 文档唯一标识
page 页码
line_number 行号
chunk_id 切分后的片段 id
version 文档版本
tenant_id 租户或业务隔离字段
acl 权限字段

如果 metadata 不完整,后续会出现很多问题:答案无法引用来源、权限无法过滤、文档更新后无法删除旧片段、线上问题无法回溯。

# 文档加载器的使用边界

文档加载器只负责把外部数据转换成 Document,不应该把所有逻辑都塞到 Loader 里。

更清晰的边界是:

Loader:读取数据并生成 Document
Transformer:清洗、过滤、翻译或增强 Document
TextSplitter:切分 Document
Embedding:把文本转换成向量
VectorStore:保存向量并提供检索
Retriever:封装检索策略
1
2
3
4
5
6

如果 Loader 里同时做清洗、切分、向量化和写库,后续排查会很困难。比如检索结果不好时,很难判断问题来自原始读取、文本清洗、切分参数还是向量库检索。

# 常见加载器类型

LangChain 封装了大量文档加载器,常见类型包括:

加载器 适用场景
TextLoader 文本文件、源码、日志等
CSVLoader CSV 文件
UnstructuredCSVLoader 非结构化 CSV 读取
UnstructuredHTMLLoader HTML 文件
BSHTMLLoader 基于 BeautifulSoup 的 HTML 加载
PyPDFLoader PDF 文件
UnstructuredPDFLoader 非结构化 PDF 解析
UnstructuredMarkdownLoader Markdown 文件
DirectoryLoader 文件夹批量加载
JSONLoader JSON 文件
UnstructuredPowerPointLoader PPT 文件
UnstructuredFileLoader 通用文件加载

明确文件类型时,优先选择对应的专用加载器;无法确定文件类型或需要快速兜底时,再使用通用文件加载器。

# 使用建议

  1. 小文本文件可以直接使用 TextLoader。
  2. 多文件批量导入时,优先考虑懒加载或目录加载器。
  3. PDF、Word、Excel、PPT 不要直接当文本处理,应使用对应加载器。
  4. metadata 要尽量保留完整,尤其是 source、doc_id、page、version。
  5. 加载后先抽样检查 Document 内容,再继续做切分和向量化。
  6. 数据量较大时,把加载和入库放到队列或异步任务中处理。

文档加载器解决的是 RAG 数据入口问题。入口层越稳定,后面的切分、检索和生成越容易调试。

# 拓展

文档加载器不仅可以读取本地文件,也可以封装任意业务数据源。只要最终输出 Document,后续链路就可以继续复用 LangChain 的分割器、向量库和检索器。

常见扩展方向包括:

  • 数据库 Loader:把表记录、知识条目、工单、FAQ 转成 Document。
  • API Loader:分页调用内部接口,把接口返回内容转成 Document。
  • 对象存储 Loader:读取 OSS、S3、MinIO 中的文件,再交给解析器。
  • 权限感知 Loader:在 metadata 中写入 tenant_id、department、role、acl 等权限字段。
  • 增量 Loader:根据更新时间、版本号或 hash 判断哪些文档需要重新导入。

一个好的 Loader 不只是把内容读出来,还应该保留足够的上下文,让后续能过滤、引用、删除、更新和排错。

# 常见问题

# TextLoader 可以读取所有文件吗?

不可以。TextLoader 适合文本结构文件,例如 .txt、.md、源码、日志。PDF、Word、Excel、PPT 这类文件不是普通文本,应该使用对应的加载器,否则容易出现乱码、内容缺失或结构丢失。

# metadata 为什么不能等检索时再补?

很多 metadata 只有加载时最容易获得,例如文件路径、页码、行号、业务主键、更新时间、权限字段。等到切分、向量化之后再补,往往已经丢失上下文。

# load_and_split 能不能直接用于生产?

可以用于简单场景,但复杂系统更建议拆开。先 load,再清洗,再 split,再入库。这样每一步都有中间结果,便于检查数据质量。

# 面试题

# Document 的核心字段是什么?

核心字段是 page_content 和 metadata。page_content 保存正文,metadata 保存来源、页码、文档 id、权限、版本等信息。

# BaseLoader 的 load 和 lazy_load 有什么区别?

load() 一次性加载所有文档并返回列表,适合数据量较小的场景。lazy_load() 返回迭代器,可以边读取边处理,更适合大文件、目录批量导入和流式数据源。

# 为什么文档加载器要统一输出 Document?

因为后续的分割器、转换器、向量库和检索器都围绕 Document 工作。统一输出 Document,可以屏蔽不同数据源差异,让后续链路保持一致。

# 自定义 Loader 通常要注意什么?

要注意数据分页、异常重试、编码处理、metadata 补齐、权限字段、稳定 doc_id,以及不要在 Loader 中混入向量化和问答逻辑。

# 生产问题排查

问题 常见原因 处理方式
加载后内容为空 文件编码不匹配、路径错误、Loader 类型不对 检查路径和编码,换专用 Loader
文本里有大量空白和导航 网页或通用 Loader 提取噪声 增加清洗步骤,过滤无效段落
检索结果没有来源 metadata 没有 source Loader 输出 Document 时补齐 source
权限过滤失效 metadata 缺少 tenant_id 或 acl 加载阶段写入权限字段,检索阶段强制 filter
更新文档后重复召回 没有稳定 doc_id/chunk_id 设计稳定 id,入库前删除旧版本
大文件导入内存占用高 使用 load 一次性读全量 改用 lazy_load 或异步导入