Skip to content

LangChain教程 - 11 LangChain文档加载器

文档加载器是 LangChain 处理外部知识的基础,它提供了一套标准的接口,用于把各种格式(文本、PDF、Word、Excel 等)的文件转换成 LangChain 能处理的 Document 对象,为后续的文本分割、检索、问答等功能铺路。

我们可以使用内置的文档加载器,或者自己实现也可以,需要实现 BaseLoader 接口。


在 LangChain 中,一个文档长这样:

python
Document(
    page_content="文本内容",
    metadata={"source": "文件路径"}
)

也就是说一个文档包含:

字段说明
page_content文本内容
metadata文档元数据(来源、页码等)

不同的文档加载器可能定义了不同的参数,但是都实现了统一的接口方法:

  • load() :一次性加载全部文档;
  • lazy_load():延迟流式传输文档,对大型数据集很有用,避免内存溢出。

LangChain 中内置了很多的文档加载器,下面就来介绍一下一些常用的文档加载器的使用。

  • CSVLoader
  • JSONLoader
  • PDFLoader
  • TextLoader

11.1 CSVLoader

CSVLoader 是用来读取 CSV 表格文件,并把每一行转换成一个 Document 对象。

1 准备数据

首先准备一下数据,例如 hero_skills.csv

python
英雄姓名,年龄,性别,位置,核心技能,技能描述
赵云,29,男,战士,枪法+冲锋,枪法精通百鸟朝凤枪,冲锋为短距离高速突进,无视地形,擅长近战爆发
花木兰,27,女,战士,重剑+轻剑,轻剑形态灵活突进,重剑形态高伤害格挡,攻防兼备的女战士代表
高渐离,28,男,法师,魔音+冲击,魔音提升法术伤害,冲击范围法术爆发,团战AOE输出核心
不知火舞,21,女,法师,法术+舞蹈,火系法术范围攻击,舞蹈提升机动性,持续输出能力强,远程消耗
铠,30,男,坦克,剑术+格挡,剑术高伤害单体攻击,格挡减免80%伤害,防御反击能力极强,近战坦度高
钟无艳,28,女,坦克,锤击+石化,锤击范围伤害,石化控制敌方1.5秒,坦度+控制兼备,女坦克代表
孙悟空,未知,男,打野,棍术+七十二变,棍术范围击飞,七十二变可伪装,爆发+控制兼备,打野首选
阿轲,22,女,打野,隐身+背刺,隐身接近敌方,背刺造成暴击伤害,收割能力极强,女打野代表
兰陵王,32,男,刺客,隐身+收割,全程隐身侦查,近身爆发收割,秒杀脆皮的男刺客代表
上官婉儿,20,女,刺客,书法+法术,书法触发法术连招,多段位移+远程爆发,操作灵活,适合收割
张飞,35,男,辅助,咆哮+护盾,咆哮提升自身坦度,护盾为友方吸收大量伤害,团战开团核心辅助
蔡文姬,16,女,辅助,治愈+控制,治愈技能为友方回血,控制技能眩晕敌方,续航型女辅助代表

2 CSVLoader的使用

然后编写 python 代码,使用 CSVLoader 加载器来加载文件,转换为 Document 对象:

python
from langchain_community.document_loaders import CSVLoader

loader = CSVLoader(
    file_path="./data/hero_skills.csv",
    encoding="utf-8"
)

# 得到的documents是一个列表,每个元素是一个Document对象,对应csv中的一行数据
documents = loader.load()

for i, doc in enumerate(documents, start=1):
    print(f"第{i}个Document:{doc}")
  • 上面创建 CSVLoader 指定文件路径和编码,然后使用 load() 方法加载文件全部内容,然后打印;
  • enumerate(documents, start=1) 只是在遍历序列时,同时得到“索引”和“元素”,start=1 还是从第0个元素开始遍历,只是 i 从1开始显示而已;如果不想要序号,可以直接写 for i, doc in documents:

执行结果:

python
第1个Document:page_content='name: 赵云
skill: 战士
skill_desc: 枪法+冲锋' metadata={'source': '', 'row': 1, 'age': '29', 'gender': ''}
第2个Document:page_content='name: 花木兰
skill: 战士
skill_desc: 重剑+轻剑' metadata={'source': '', 'row': 2, 'age': '27', 'gender': ''}
第3个Document:page_content='name: 高渐离
skill: 法师
skill_desc: 魔音+冲击' metadata={'source': '', 'row': 3, 'age': '28', 'gender': ''}
第4个Document:page_content='name: 不知火舞
skill: 法师
skill_desc: 法术+舞蹈' metadata={'source': '', 'row': 4, 'age': '21', 'gender': ''}
第5个Document:page_content='name: 铠
skill: 坦克
skill_desc: 剑术+格挡' metadata={'source': '', 'row': 5, 'age': '30', 'gender': ''}
第6个Document:page_content='name: 钟无艳
skill: 坦克
skill_desc: 锤击+石化' metadata={'source': '', 'row': 6, 'age': '28', 'gender': ''}
第7个Document:page_content='name: 孙悟空
skill: 打野
skill_desc: 棍术+七十二变' metadata={'source': '', 'row': 7, 'age': '未知', 'gender': ''}
第8个Document:page_content='name: 阿轲
skill: 打野
skill_desc: 隐身+背刺' metadata={'source': '', 'row': 8, 'age': '22', 'gender': ''}
第9个Document:page_content='name: 兰陵王
skill: 刺客
skill_desc: 隐身+收割' metadata={'source': '', 'row': 9, 'age': '32', 'gender': ''}
第10个Document:page_content='name: 上官婉儿
skill: 刺客
skill_desc: 书法+法术' metadata={'source': '', 'row': 10, 'age': '20', 'gender': ''}
第11个Document:page_content='name: 张飞
skill: 辅助
skill_desc: 咆哮+护盾' metadata={'source': '', 'row': 11, 'age': '35', 'gender': ''}
第12个Document:page_content='name: 蔡文姬
skill: 辅助
skill_desc: 治愈+控制' metadata={'source': '', 'row': 12, 'age': '16', 'gender': ''}

如果要使用延迟加载,可以使用 lazy_load() 方法就可以了:

python
from langchain_community.document_loaders import CSVLoader

loader = CSVLoader(
    file_path="./data/student.csv",
    encoding="utf-8"
)

# 得到的documents是一个列表,每个元素是一个Document对象,对应csv中的一行数据
documents = loader.lazy_load()

for document in documents:
    print(document)

3 其他参数

CSVLoader 还有一些其他常用的参数,可以设置,可以根据需要选择:

python
from langchain_community.document_loaders import CSVLoader

loader = CSVLoader(
    file_path="./data/hero_skills.csv",          # 基础参数:指定文件路径
    encoding="utf-8",                   # 基础参数:指定编码避免中文乱码
    # 核心:csv_args 整合多个解析参数,适配特殊格式的CSV
    csv_args={
        "delimiter": ",",               # 指定列分隔符为逗号
        "skipinitialspace": True,       # 忽略分隔符后的空格(解决 "小明, 20, 北京" 中的空格问题)
        "quotechar": '"',               # 指定引用符为双引号(解析带逗号的字段:"篮球,游泳")
        "doublequote": True,            # 开启双写转义(默认)
      	"escapechar": "\\",             # 指定转义符是反斜杠,此时需要设置doublequote为False
        # 如果没有表头可以自定义表头,如果有表头,使用这个参数,文档中的表头可能会被当为数据了
        "fieldnames": ["name", "age", "gender", "skill", "skill_desc"],
        "strict": True                  # 严格模式:CSV格式错误时直接报错
    },
    # 内容/元数据自定义参数
    source_column="gender",             # 用「gender」替换默认的source元数据
    content_columns=["name", "skill", "skill_desc"],  # 仅加载这3列作为文本内容
    metadata_columns=["age", "gender"]     # 把年龄/性别存入metadata
)

# 得到的documents是一个列表,每个元素是一个Document对象,对应csv中的一行数据
documents = loader.load()

for i, doc in enumerate(documents[1:], start=1): # 跳过原始表头,上面使用了自定义的表头
    print(f"第{i}个Document:{doc}")
  • quotechardoublequoteescapechar 不知道什么意思?解释一下:

首先是因为 CSV 中的字段是使用的 , 分隔的,所以会有一些限制,比如:

  • 分隔符(比如逗号)出现在字段内容里:
姓名,年龄,爱好
小明,20,篮球,游泳  # 问题来了!

于是把含分隔符的字段用引号包裹起来,告诉解析器:引号内的内容是一个完整字段,里面的逗号不是分隔符。这个引号可以选择是单引号还是双引号,对应上面的 quotechar 参数的。

姓名,年龄,爱好
小明,20,"篮球,游泳"  # 用双引号包裹含逗号的字段
  • 新问题来了:如果字段内容本身就包含双引号呢?
姓名,年龄,爱好
小明,20,"爱好是"篮球,游泳""  # 致命错误!

为了解决「字段内容含双引号」的问题,不同系统 / 工具发明了两种常见的转义方式:

  1. 双写双引号(""):把内容里的 " 写成 "",例如:小明,20,"爱好是,""篮球,游泳""",对应上面的 doublequote 参数;
  2. 转义符 + 双引号(\"):用反斜杠 \ 转义内容里的 ",对应上面的 escapechar 参数。

CSV 解析器不知道你用的是哪种方式,所以需要通过 doublequoteescapechar 告诉它,这就是这两个参数的核心作用。

11.2 JSONLoader

JSONLoader 用于将 JSON 数据加载为 Document 类型对象。

1 安装依赖

使用 JSONLoader 之前需要额外安装 jq 依赖:

python
pip install jq
  • jq 是一个跨平台的 JSON 解析工具,LangChain 底层对 JSON 的解析就是基于 jq 工具实现的。

2 准备数据

JSON格式的数据文件有不同的格式,下面准备三个 JSON 数据的文件:

single.json:单行 JSON 文件

json
{
  "name": "李白",
  "age": 27,
  "gender": "男",
  "skills": ["写诗", "喝酒", "剑术"]
}

array.json:数组JSON文件

json
[
  {"name":"赵云","age":29,"gender":"男","skills":["枪法","冲锋"]},
  {"name":"不知火舞","age":21,"gender":"女","skills":["法术","舞蹈"]},
  {"name":"铠","age":30,"gender":"男","skills":["剑术","格挡"]},
  {"name":"上官婉儿","age":20,"gender":"女","skills":["书法","法术"]}
]

multi.json:多行 JSON 文件

json
{"name":"貂蝉","age":24,"gender":"女","skills":["舞蹈","琵琶"]}
{"name":"韩信","age":26,"gender":"男","skills":["枪法","行军"]}
{"name":"王昭君","age":22,"gender":"女","skills":["琵琶","谋略"]}

3 解析单行JSON文件

通过下面的代码,可以读取 single.json 文件的所有内容,转换为 Document 对象。

python
from langchain_community.document_loaders import JSONLoader

# 1. 初始化JSONLoader
loader = JSONLoader(
    file_path="./data/single.json",  # JSON文件路径
    jq_schema=".",                   # jq表达式,"."表示读取整个JSON对象
    text_content=False               # 是否只提取文本内容(False保留原始结构)
)

# 2. 加载并转换为Document对象
document = loader.load()

# 3. 打印结果
print(document)

执行结果如下:

[Document(metadata={'source': '***/data/single.json', 'seq_num': 1}, page_content='{"name": "\\u674e\\u767d", "age": 27, "gender": "\\u7537", "skills": ["\\u5199\\u8bd7", "\\u559d\\u9152", "\\u5251\\u672f"]}')]

如果想直接获取 name 属性,可以修改 jq_schema=".name" ,那么执行结果为:

[Document(metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/single.json', 'seq_num': 1}, page_content='李白')]

如果想获取第一个 skill,可以修改 jq_schema=".skills[0]"

4 解析规则

下面介绍一下创建 JSONLoader 的常用属性:

file_path:指定 JSON 文件路径;

jq_schema(核心中的核心):

  • 作用:通过 jq 表达式指定要提取的 JSON 内容,类似 XPath 对 XML 的作用;
  • 常用表达式示例:
    • .:表示根,提取整个 JSON 对象;
    • .students[]:提取 students 数组的所有元素;
    • .students[0].name:提取 students 数组第一个元素的 name 字段;
    • .students[] | {name, age}:仅提取 students 中每个元素的 name 和 age 字段。
    • .[]:提取整个 JSON 数组内的每个元素;
    • .[].name:提取 JSOIN 数组内每个元素的 name 属性;
    • 不懂?不着急,下面继续演示。

text_content

  • False(默认):保留 JSON 字符串格式(如 {"name":"李白"});
  • True:转为纯文本(如 李白 27 男),适合仅需文本内容的场景。

json_lines

  • False(默认):解析单行完整 JSON;
  • True:解析多行 JSON(每行一个 JSON 对象),适合日志、批量导出数据。

5 解析数组JSON文件

python
# 1. 初始化JSONLoader
from langchain_community.document_loaders import JSONLoader

# 初始化JSONLoader(解析纯数组JSON)
loader = JSONLoader(
    file_path="./data/array.json",
    jq_schema=".",               # jq表达式"."表示读取整个数组
    text_content=False           # 保留JSON格式,不转纯文本
)

# 加载并打印结果
documents = loader.load()
print(f"返回Document数量:{len(documents)}")  # 输出:1
print("Document内容:", documents)
print(documents[0].page_content)

执行结果为:

返回Document数量:1
Document内容: [Document(metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/array.json', 'seq_num': 1}, page_content='[{"name": "\\u8d75\\u4e91", "age": 29, "gender": "\\u7537", "skills": ["\\u67aa\\u6cd5", "\\u51b2\\u950b"]}, {"name": "\\u4e0d\\u77e5\\u706b\\u821e", "age": 21, "gender": "\\u5973", "skills": ["\\u6cd5\\u672f", "\\u821e\\u8e48"]}, {"name": "\\u94e0", "age": 30, "gender": "\\u7537", "skills": ["\\u5251\\u672f", "\\u683c\\u6321"]}, {"name": "\\u4e0a\\u5b98\\u5a49\\u513f", "age": 20, "gender": "\\u5973", "skills": ["\\u4e66\\u6cd5", "\\u6cd5\\u672f"]}]')]
[{"name": "\u8d75\u4e91", "age": 29, "gender": "\u7537", "skills": ["\u67aa\u6cd5", "\u51b2\u950b"]}, {"name": "\u4e0d\u77e5\u706b\u821e", "age": 21, "gender": "\u5973", "skills": ["\u6cd5\u672f", "\u821e\u8e48"]}, {"name": "\u94e0", "age": 30, "gender": "\u7537", "skills": ["\u5251\u672f", "\u683c\u6321"]}, {"name": "\u4e0a\u5b98\u5a49\u513f", "age": 20, "gender": "\u5973", "skills": ["\u4e66\u6cd5", "\u6cd5\u672f"]}]

如果想直接提取数组中所有元素的为一个 Document,可以修改 jq_schema=".[]" ,那么执行结果为:

返回Document数量:4
Document内容: [Document(metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/array.json', 'seq_num': 1}, page_content='{"name": "\\u8d75\\u4e91", "age": 29, "gender": "\\u7537", "skills": ["\\u67aa\\u6cd5", "\\u51b2\\u950b"]}'), Document(metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/array.json', 'seq_num': 2}, page_content='{"name": "\\u4e0d\\u77e5\\u706b\\u821e", "age": 21, "gender": "\\u5973", "skills": ["\\u6cd5\\u672f", "\\u821e\\u8e48"]}'), Document(metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/array.json', 'seq_num': 3}, page_content='{"name": "\\u94e0", "age": 30, "gender": "\\u7537", "skills": ["\\u5251\\u672f", "\\u683c\\u6321"]}'), Document(metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/array.json', 'seq_num': 4}, page_content='{"name": "\\u4e0a\\u5b98\\u5a49\\u513f", "age": 20, "gender": "\\u5973", "skills": ["\\u4e66\\u6cd5", "\\u6cd5\\u672f"]}')]
{"name": "\u8d75\u4e91", "age": 29, "gender": "\u7537", "skills": ["\u67aa\u6cd5", "\u51b2\u950b"]}

如果想直接提取数组中所有元素的 name 属性,可以修改 jq_schema=".[].name" ,那么执行结果为:

page_content='{"name": "\u5b59\u609f\u7a7a", "age": "\u672a\u77e5", "gender": "\u7537", "skills": ["\u68cd\u672f", "\u4e03\u5341\u4e8c\u53d8"]}' metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/nested.json', 'seq_num': 1}
page_content='{"name": "\u59b2\u5df1", "age": 18, "gender": "\u5973", "skills": ["\u9b45\u60d1", "\u6cd5\u672f"]}' metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/nested.json', 'seq_num': 2}

6 解析多行JSON文件

解析多行JSON,需要开启 json_lines=True

python
from langchain_community.document_loaders import JSONLoader

# 1. 初始化JSONLoader
loader = JSONLoader(
    file_path="./data/multi.json",
    jq_schema=".",                   # 每行解析为一个JSON对象
    text_content=False,
    json_lines=True                  # 需要开启多行JSON解析模式,每一行都是独立的JSON
)

documents = loader.load()
for i, doc in enumerate(documents):
    print(f"第{i+1}个Document:{doc}")

执行结果:

第1个Document:page_content='{"name": "\u8c82\u8749", "age": 24, "gender": "\u5973", "skills": ["\u821e\u8e48", "\u7435\u7436"]}' metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/multi.json', 'seq_num': 1}
第2个Document:page_content='{"name": "\u97e9\u4fe1", "age": 26, "gender": "\u7537", "skills": ["\u67aa\u6cd5", "\u884c\u519b"]}' metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/multi.json', 'seq_num': 2}
第3个Document:page_content='{"name": "\u738b\u662d\u541b", "age": 22, "gender": "\u5973", "skills": ["\u7435\u7436", "\u8c0b\u7565"]}' metadata={'source': '/Users/kang/Documents/My/study-project/python/hello-ai/data/multi.json', 'seq_num': 3}

11.3 PDFLoader

LangChain 支持需要的 PDF 加载器,这里介绍一下 PyPDFLoader 加载器,它底层基于 PyPDF 库实现,支持提取 PDF 文本内容、按页码拆分、处理加密 PDF、保留页码元数据等核心功能,是处理文本型 PDF(非扫描件)的首选工具。

PyPDFLoader 加载器依赖 于 PyPDF 库,所以需要安装:

bash
pip install pypdf

1 准备数据

首先准备一个 PDF 文件,确保 PDF 是文本格式,而非扫描件。

2 PyPDFLoader的使用

python
from langchain_community.document_loaders import PyPDFLoader

# 1. 初始化 PyPDFLoader
loader = PyPDFLoader(
    file_path="./data/Jetpack Compose入门到精通.pdf",  # PDF 文件路径(相对/绝对均可)
    mode="page",  # 读取模式,可选page(每页 1 个 Document)和single(全部合并为 1 个 Document)
    password=None  # 无加密时省略,加密 PDF 需传入密码字符串password="123456"
)

# 2. 加载并转换为 Document 对象(默认每页 1 个 Document)
documents = loader.lazy_load()

# 3. 打印加载结果
print("\n===== 每页内容与元数据 =====")
for i, doc in enumerate(documents):
    print(f"\n【第 {i+1} 页】")
    print(f"文本内容(前100字符):{doc.page_content[:100]}")
    print(f"元数据:{doc.metadata}")

11.4 TextLoader

除了上面的三个加载器,LangChain 中还有一个最基础、最通用的文档加载器,专门用于读取纯文本文件(如 .txt.md.log等无结构化文本),将整个文件内容转换为单个 Document 对象。

但这里存在一个核心问题:如果文档体积特别大(比如上万字的技术文档、日志文件),所有内容都集中在一个 Document 对象中,会导致后续的文本处理(如向量嵌入、LLM 问答)出现「上下文过长」「处理效率低」甚至「超出模型 token 限制」的问题。

因此,TextLoader 加载大文本文件时,必须搭配文本分割器使用。在 LangChain 中,RecursiveCharacterTextSplitter(递归字符文本分割器)是官方推荐的默认文本分割器,它的核心优势是「按自然语义分割」—— 优先按大分隔符(如空行、段落)拆分,拆分后仍超长度再按小分隔符(如句号、逗号)拆分,既能保证分段长度可控,又能最大程度保留文本的语义完整性。

1 准备工作

加载文档,首先需要有一个文档,我这里将 python基础语法 作为内容进行加载。

在安装 LangChain 的时候,默认就会安装langchain_text_splitters,如果找不到,可以使用如下的命令进行安装:

pip install langchain_text_splitters

2 TextLoader的使用

举个栗子:

python
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 创建加载器
loader = TextLoader(
    file_path="./data/python基础语法.txt",
    encoding="utf-8",  # 基础参数:指定编码避免中文乱码
)
# 加载并打印结果
docs = loader.load()
print(f"返回Document数量:{len(docs)}")  # 输出:1

# 创建文本分割器
splitter = RecursiveCharacterTextSplitter(
    # 指定每个分段的最大字符数(可根据需求调整)
    chunk_size=500,
    # 指定重叠字符数,也就是每个分段之间的内容可以重叠,确保上下文连贯
    chunk_overlap=50,
    # 指定用于分割的分隔符列表,按优先级顺序排列
    separators=["\n\n", "\n", "。", ".", "!", "!", "?", "?", " "],
    # 用于计算字符长度的函数,这里使用Python的len函数
    length_function=len,
)

split_docs = splitter.split_documents(docs)
print(f"返回Segment数量:{len(split_docs)}")  # 输出:25

for doc in split_docs:
    print("="*20)
    print(doc)
    print("="*20)
  • 首先创建加载器和文本分割器,然后将 Document 传递给文本分割器,将单个 Document 分隔成多个 Document 列表。

有了 Document,我们后面就可以将 Document 转换为向量数据,然后进行保存和检索了。