# Document 组件与文档加载器的使用
Document 是 LangChain 中的核心组件,它定义了一个文档对象的结构,包含文本内容和相关元数据。Document 也是文档加载器、文档分割器、向量数据库、检索器之间交互传递的状态数据。
在较新的 LangChain 版本中,Document 主要承担基础记录功能:
Document = page_content 页面内容 + metadata 元数据
在 RAG 应用中,通常不会手动录入知识数据,而是读取特定来源的数据,例如本地 Markdown 文件、HTML 网页、PDF 文档、DOC 文档、URL 链接等。读取完成后,再将原始文档切割成特定大小的文档片段,最后存储到向量数据库中。
因此,一个完整的 RAG 应用外部通常会有一条额外的数据处理链路,专门处理:
读取数据 -> 切割数据 -> 存储数据
这条链路往往比较耗时。例如上传一个较大的文档,需要执行加载、切割、文本嵌入、写入向量库等操作,实际系统中通常会使用队列或异步任务处理。

在这条架构链路中,文档加载器的作用是从各式各样的数据源中提取相应信息,并转换成标准 Document,从而屏蔽不同类型文件的读取差异。
# BaseLoader 的统一方法
LangChain 中所有文档加载器的基类是 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)
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)
2
3
4
5
6
7
不优先推荐把加载和切分直接合在一起:
# 不优先推荐:步骤耦合,不方便检查加载结果和切分结果
chunks = loader.load_and_split(text_splitter)
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)
2
3
4
5
6
7
8
9
10
11
输出结果中,documents 是一个 Document 列表。第一个 Document 的 page_content 保存文本文件内容,metadata 中会包含类似下面的信息:
{"source": "./电商产品数据.txt"}
这说明 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",
},
)
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:封装检索策略
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 | 通用文件加载 |
明确文件类型时,优先选择对应的专用加载器;无法确定文件类型或需要快速兜底时,再使用通用文件加载器。
# 使用建议
- 小文本文件可以直接使用
TextLoader。 - 多文件批量导入时,优先考虑懒加载或目录加载器。
- PDF、Word、Excel、PPT 不要直接当文本处理,应使用对应加载器。
- metadata 要尽量保留完整,尤其是
source、doc_id、page、version。 - 加载后先抽样检查 Document 内容,再继续做切分和向量化。
- 数据量较大时,把加载和入库放到队列或异步任务中处理。
文档加载器解决的是 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 或异步导入 |