LangChain框架入门10:一文带你吃透文档加载器

163 阅读7分钟

在上一篇文章中,介绍了实现RAG功能的整个流程,以及LangChain提供了哪些组件来实现RAG功能,想了解RAG实现过程可以参考:LangChain框架入门09:什么是RAG?。

本文将从RAG准备阶段第一个步骤:文档加载,详细介绍LangChain中的文档加载器组件,LangChain内置的文档加载器可以加载各种格式的知识库文档,而无需开发者自己编写,可以非常便捷地实现文档加载功能。

文中所有示例代码:github.com/wzycoding/l…

一、什么是文档加载器

在LangChain中,文档加载器用于将各种格式的文档转换为Document对象,LangChain提供了大量的文档加载器,支持从各种来源加载文档,如文件、数据源、URL等。

常用的LangChain文档加载器如下:

文档加载器作用
CSVLoader从CSV加载文档
JSONLoader从JSON数据加载文档
PyPDFLoader从PDF数据加载文档
UnstructuredHTMLLoader从HTML数据加载文档
UnstructuredMarkdownLoader从Markdown加载文档
UnstructuredExcelLoader从Excel文件加载数据

每一个文档加载器都有自己特定的参数和方法,但它们有一个统一的load()方法来完成文档的加载,load()方法会返回一个Document类的对象列表,因为这些文档加载器都继承自BaseLoader基类,它们的继承关系如下:

image-20250803155229893.pngBaseLoader类中,定义了load()方法,用来加载文档对象,在方法内部又调用了lazy_load()懒加载方法

def load(self) -> List[Document]:
    """Load data into Document objects."""
    return list(self.lazy_load())

lazy_load()方法中,判断了子类是否重写了load()方法,如果重写了,则调用当前类的load()方法,如果没有重写则抛出异常,因此在子类中,要重写load()方法或lazy_load()方法。

def lazy_load(self) -> Iterator[Document]:
    """A lazy loader for Documents."""
    if type(self).load != BaseLoader.load:
        return iter(self.load())
    raise NotImplementedError(
        f"{self.__class__.__name__} does not implement lazy_load()"
    )

如果LangChain提供的文档加载器无法满足业务需求,我们也可以自己实现自定义加载器,通过继承BaseLoader,并实现其中的load()方法,来编写自定义文档加载器的加载逻辑。

二、Document文档类

文档加载器无论从什么来源进行文档加载,最终都是为了将文档信息解析为Document对象,下面一起来看看Document类中重要属性:

Document类中,主要包含两个重要属性:

page_content:表示文档的内容,类型是字符串

metadata :与文档本身无关的元数据信息。可以保存文档 ID、文件名等任意信息,类型是字典

三、UnstructuredMarkdownLoader文档加载器使用

下面以UnstructuredMarkdownLoader为例来介绍文档加载器的用法:

首先需要安装unstructured相关依赖

pip install "unstructured[md]" nltk

执行命令,生成依赖版本快照

pip freeze > requirements.txt

使用UnstructuredMarkdownLoader读取md文件示例如下:

from langchain_community.document_loaders import UnstructuredMarkdownLoader
​
# 1.创建文档加载器,并指定路径
document_load = UnstructuredMarkdownLoader(file_path="LangChain框架入门09:什么是RAG?.md")
​
# 2.加载文档
documents = document_load.load()
​
# 3.打印文档内容
print(f"文档数量:{len(documents)}")
for document in documents:
    print(f"文档内容:{document.page_content}")
    print(f"文档元数据:{document.metadata}")

执行结果如下,默认情况下UnstructuredMarkdownLoader把md文档内容加载成了一个Document对象,并且自动将文件名添加到了Document对象的元数据中。

文档数量:1
文档内容:当我们在跟大语言模型交流时,会发现大语言模型只能回答训练数据范围内的事情,如果你问它一些超出训练数据之外的事情,AI的回答往往就是胡言乱语,或者给出错误的答案。
(文档内容省略...)
文档元数据:{'source': 'LangChain框架入门09:什么是RAG?.md'}

在底层Unstructured包会为不同的文本片段创建不同的“元素”。默认情况下会将这些元素合并在一起,可以通过指定 mode="elements" 来将不同元素进行分离,解析成多个文档。

document_load = UnstructuredMarkdownLoader(file_path="LangChain框架入门09:什么是RAG?.md",
                                           mode="elements")

重新执行代码,可以看到加载的文档数为12,文档的内容也按照不同元素进行了拆分。

文档数量:12
文档内容:当我们在跟大语言模型交流时,会发现大语言模型只能回答训练数据范围内的事情,如果你问它一些超出训练数据之外的事情,AI的回答往往就是胡言乱语,或者给出错误的答案。
文档元数据:{'source': 'LangChain框架入门09:什么是RAG?.md', 'languages': ['zho'], 'filename': 'LangChain框架入门09:什么是RAG?.md', 'filetype': 'text/markdown', 'last_modified': '2025-08-03T15:10:28', 'category': 'UncategorizedText', 'element_id': '026c499e414187c4ffe540d4c89ce4f3'}
文档内容:那么我们不禁会想到:大模型拥有如此强大的能力,是否能让大模型掌握一些特定领域的知识,甚至接入企业内部的知识库数据?来帮助我们实现业务需求。
文档元数据:{'source': 'LangChain框架入门09:什么是RAG?.md', 'languages': ['zho'], 'filename': 'LangChain框架入门09:什么是RAG?.md', 'filetype': 'text/markdown', 'last_modified': '2025-08-03T15:10:28', 'category': 'UncategorizedText', 'element_id': '971d5c14b54653977c847a7802d531a6'}
(省略...)

四、实现自定义文档加载器

在实际开发中,基于文件的不同类型和不同格式,有时通过这些LangChain提供的文档加载器很难满足业务需求,例如需要根据特定规则提取文本片段,这时就需要开发自定义加载器,只需要定义一个自定义文档加载器类,并继承前面提到的BaseLoader类。

假设有如下需求,对chat_record.txt文件进行文档加载,内容如下,要求将不同人物的每一句对话加载成一个文档,并且在文档的元数据中添加人物的名字。

小明:你是哪个学校的?
小刚:我是光明中学的,我叫李小刚
小明:我是第一中学的,我叫赵小明
小刚:你的学号是多少?
小明:我的学号是,202409876
小刚:我的学号是,202407827

自定义文档加载器代码如下:

from typing import List

from langchain_core.document_loaders import BaseLoader
from langchain_core.documents import Document


class ChatRecordLoader(BaseLoader):
    file_path: str

    def __init__(self, file_path: str):
        self.file_path = file_path

    def load(self) -> List[Document]:
        """加载聊天记录文档"""
        documents = []
        with open(self.file_path, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                # 如果包含中文冒号,则进行分割
                if ":" in line:
                    user_name, content = line.split(":", 1)
                    documents.append(
                        Document(
                            page_content=content.strip(),
                            metadata={"user_name": user_name.strip()}
                        )
                    )
                else:
                    # 不符合格式要求直接跳过
                    continue
        return documents


chat_record_loader = ChatRecordLoader(file_path="chat_record.txt")
documents = chat_record_loader.load()
print(f"文档数量:{len(documents)}")
for document in documents:
    print(f"文档内容:{document.page_content}")
    print(f"文档元数据:{document.metadata}")

执行结果如下,chat_record.txt文件被解析成6个文档,并且每个文档的元数据都保存了user_name。

文档数量:6
文档内容:你是哪个学校的?
文档元数据:{'user_name': '小明'}
文档内容:我是光明中学的,我叫李小刚
文档元数据:{'user_name': '小刚'}
文档内容:我是第一中学的,我叫赵小明
文档元数据:{'user_name': '小明'}
文档内容:你的学号是多少?
文档元数据:{'user_name': '小刚'}
文档内容:我的学号是,202409876
文档元数据:{'user_name': '小明'}
文档内容:我的学号是,202407827
文档元数据:{'user_name': '小刚'}

五、总结

本文首先介绍了什么是文档加载器,以及常见文档加载器和它们的类继承关系。随后,以 UnstructuredMarkdownLoader 为例,演示了如何加载 Markdown 文件,并对比了默认模式与 mode="elements" 模式下的不同拆分效果。

最后,通过一个聊天记录文件加载的案例,展示了如何根据业务需求定制自定义文档加载器,实现将聊天记录逐句加载为 Document 对象,并在 metadata 中附加额外信息。

通过本文相信大家已经掌握了文档加载器的用法,LangChain 提供了丰富的内置加载器来快速适配常见文档格式,同时也为开发者预留出了灵活的扩展空间,使得无论是处理结构化数据还是非结构化文本,都能很好的处理文档的加载,在后续的文章中,将会介绍LangChain的文档分割器的用法,敬请期待。