Day 6:文档加载

你必须要认识到真实世界的文档加载难度,不仅要会加载文档,更要理解文档如何成为 Agent 的“记忆”或“知识库”,支撑工具调用、RAG、长期记忆等关键能力

LangChain 的文档加载就是:把不同格式的文件,变成统一的 Document 对象

DocumentDocumentDocument 文档有两个字段:

Document(
    page_content="文本内容……",
    metadata={"source": "..."} 
)
  • page_content

    • 存储文件的全部文本内容
    • 类型:字符串(str)
    • 包含文件中的所有文本,保留原始格式
  • metadata

    • 存储文件的元数据信息
    • 类型:字典(dict)
    • 默认包含:
      • source: 文件路径
    • 可以包含其他自定义元数据

1、文档加载的本质

LangChain v1.0+ 的文档处理链路是:

文件 → Document 对象 → 文本切片 → 检索 & RAG → Agent

可以粗浅地理解为:

Document 是 LangChain 世界的“人民币”,所有组件靠它传递信息

2、文档加载器的核心模块

2.1、Document_loaders 文档加载器

在LangChain中,Document Loaders 是构建AI应用的"第一道工序"。它们负责将各种格式的文档转换为统一的Document对象,让后续的文本处理、向量化、检索等工作变得简单高效,没有它,我们就像没有刀的厨师,再好的食材也做不出美味佳肴。

2.2、常用加载器详解与示例:
1、TXT 文件加载器:
  • **用途:**加载 .txt 纯文本文件到一个Document对象的page_content中

  • 代码示例

    from langchain_community.document_loaders import TextLoader
    
    # 加载文档
    loader = TextLoader("文件路径", encoder = "指定文件编码格式")
    
    # 执行文件加载操作
    # 调用load()方法读取文件内容
    # 返回一个包含 Document 对象的列表
    documents = loader.load()
    
2、PDF 文件加载器:PyPDFLoader
  • **用途:**加载PDF文件(每页转换为一个DocumentDocumentDocument)

  • 代码示例:

    from langchain_community.document_loaders import PyPDFLoader
    
    loader = PyPDFLoader("文件路径")
    documents = loader.load()
    
    # 查看第一页的内容和元数据
    first_page = documents[0]
    print(f"Page content: {first_page.page_content[:200]}...")  # 预览前200个字符
    print(f"Metadata: {first_page.metadata}")  # 通常包含 'source' 和 'page'
    
3、MarkDown 文件加载器:UnstructuredMarkdownLoader
  • 用途:专门加载Markdown文件(.md)

  • 参数解析:

    • **file_path:**Markdown 文件的路径
    • **mode:**关键参数,决定文档结构
      • “single” (默认):将整个 Markdown 文件作为一个完整的 Document 加载
      • “elements”:将文件解析为结构化的元素(如 TitleNarrativeTextListItem),每个元素成为一个独立的 Document 对象,并包含丰富的元数据(如 category
    • strategy:解析策略
      • 可选 "hi_res"(高精度,保留更多结构)
      • "fast"(快速模式)
  • 代码示例:

    from langchain_community.document_loaders import UnstructuredMarkdownLoader
    
    # 模式1: 作为单个文档加载
    loader_single = UnstructuredMarkdownLoader("文件路径")
    data_single = loader_single.load()
    
    # 模式2: 作为元素加载 (保留文档结构)
    loader_elements = UnstructuredMarkdownLoader(
        file_path='文件路径',
        mode='elements',
        strategy='fast'  # 选择快速解析策略
    )
    data_elements = loader_elements.load()
    
    # 查看元素的类型
    if data_elements:
        print(f"Element category: {data_elements[0].metadata['category']}")
        print(f"Element content: {data_elements[0].page_content}")
    
4、HTML文件加载器:UnstructuredHTMLLoader
  • 用途:加载HTML文件,提取文本内容

  • **参数解析:**file_path

  • 代码示例:

    from langchain_community>document_loaders import UnstructuredHTMLLoader
    
    loader = UnstructuredHTMLLoader("文件路径")
    data = loader.load()
    
    # 查看加载的文档内容
    html_doc = data[0]
    print(f"HTML content: {html_doc.page_content}")
    print(f"Source: {html_doc.metadata['source']}")
    
5、Office文档加载器:UnstructuredWordDocunmentLoader
  • 用途:加载Word文档(.docx)

  • 参数解析:

    • file_path: 文档路径
    • model: 处理模式,决定文档结构保留方式
      • single:将整个文档作为一个 Document 进行处理
      • elements:按文档元素进行分割,返回包含标题、段落、列表等结构化元素的 Document 列表
  • 示例代码:

    from langchain_community.document_loaders import UnstructuredWordDocumentLoader
    
    # 使用 elements 模式保留文档结构
    loader = UnstructuredWordDocumentLoader("./example.docx", mode="elements")
    documents = loader.load()
    
    print(f"加载了 {len(documents)} 个文档元素")
    for i, doc in enumerate(documents[:3]):  # 查看前3个元素
        print(f"元素 {i+1}:")
        print(f"  类型: {doc.metadata.get('category', 'Unknown')}")
        print(f"  内容: {doc.page_content[:100]}...")
        print(f"  元数据: {doc.metadata}")
        print("---")
    
6、UnstructuredExcelLoader Excel文档加载器
  • **用途:**加载Excel文件

  • 参数解析:

    • file_path: Excel 文件路径 (.xlsx)
    • mode: 处理模式(同 Word 加载器)
  • 代码示例:

    from langchain_community.document_loaders import UnstructuredExcelLoader
    
    loader = UnstructuredExcelLoader("./data.xlsx")
    documents = loader.load()
    
    # Excel 加载器的特殊处理
    print(f"加载了 {len(documents)} 个工作表或单元格区域")
    for doc in documents:
        print(f"内容: {doc.page_content[:200]}...")
        print(f"元数据: {doc.metadata}")
        print("---")
    
7、PPT 文档加载器:UnstructuredPowerPointLoader
  • 用途:加载PPT文档(.pptx)

  • 核心参数:

    • file_path: PowerPoint 文件路径 (.pptx)
    • mode: 处理模式
  • 示例代码:

    from langchain_community.document_loaders import UnstructuredPowerPointLoader
    
    loader = UnstructuredPowerPointLoader("./presentation.pptx", mode="elements")
    documents = loader.load()
    
    print(f"PPT 包含 {len(documents)} 个幻灯片元素")
    for doc in documents:
        slide_num = doc.metadata.get('page_number', 'Unknown')
        element_type = doc.metadata.get('category', 'Unknown')
        print(f"幻灯片 {slide_num} - {element_type}: {doc.page_content[:100]}...")
    
8、目录加载器:DirectoryLoader
  • **用途:**加载整个目录中的所有文档(自动识别文件类型)

  • 参数解析:

    • path: 目录路径
    • glob: 文件匹配模式
    • loader_clsloader_map: 指定文件类型对应的加载器
    • recursive: 是否递归搜索子目录
    • silent_errors:是否忽略加载错误的文件
    • show_progress: 是否显示进度条
    • use_multithreading: 是否使用多线程
  • 特点:支持递归加载,可以指定文件类型,懒加载优化性能

  • 示例代码:

    from langchain_community.document_loaders import (
        DirectoryLoader, 
        TextLoader, 
        PyPDFLoader,
        UnstructuredWordDocumentLoader
    )
    
    # 方法1:使用 loader_cls(单一文件类型)
    loader1 = DirectoryLoader(
        "./documents/",
        glob="*.txt",  # 只加载txt文件
        loader_cls=TextLoader,
        loader_kwargs={"encoding": "utf-8"},
        show_progress=True,
        recursive=False  # 不搜索子目录
    )
    
    # 方法2:使用 loader_map(多种文件类型)
    loader_map = {
        '.txt': TextLoader,
        '.pdf': PyPDFLoader,
        '.docx': lambda path: UnstructuredWordDocumentLoader(path, mode="elements")
    }
    
    loader2 = DirectoryLoader(
        "./documents/",
        glob="**/*.*",  # 递归匹配所有文件
        loader_map=loader_map,
        recursive=True,  # 递归搜索子目录
        use_multithreading=True,
        show_progress=True
    )
    
    documents = loader2.load()
    print(f"总共加载了 {len(documents)} 个文档")
    
    # 统计不同文件类型的文档数量
    from collections import Counter
    file_types = [doc.metadata.get('source', '').split('.')[-1] for doc in documents]
    print(f"文件类型分布: {Counter(file_types)}")
    
9、CSV文档加载器: CSVLoader
  • **功能:**从CSV加载文档

  • 核心参数:

    • file_path: CSV 文件路径
    • source_column: 指定作为来源的列名
    • csv_args: 自定义 CSV 读取参数
  • 代码示例:

    from langchain_community.document_loaders import CSVLoader
    
    loader = CSVLoader(
        file_path="./data.csv",
        source_column="id",  # 指定元数据中的来源列
        encoding="utf-8",
        csv_args={
            'delimiter': ',',
            'quotechar': '"'
        }
    )
    documents = loader.load()
    
    print(f"CSV 包含 {len(documents)} 行数据")
    for doc in documents[:2]:  # 查看前2行
        print(f"内容: {doc.page_content}")
        print(f"元数据: {doc.metadata}")
        print("---")
    
10、JSONLoader:
  • 用途:从JSON数据加载文档

  • 核心参数:

    • file_path: JSON 文件路径
    • jq_schema: jq 查询语法,用于提取文本内容
    • text_key: 指定包含文本内容的字段
    • content_key: 同 text_key
  • 代码示例:

    from langchain_community.document_loaders import JSONLoader
    
    # 示例1:简单数组结构
    loader1 = JSONLoader(
        file_path='./data.json',
        jq_schema='.[]',  # 遍历数组中的每个对象
        text_key='content'
    )
    
    # 示例2:复杂嵌套结构
    loader2 = JSONLoader(
        file_path='./articles.json',
        jq_schema='.articles[].body',  # 提取 articles 数组中每个对象的 body 字段
        content_key='body'
    )
    
    documents = loader2.load()
    print(f"从 JSON 中提取了 {len(documents)} 个文档")
    for doc in documents[:2]:
        print(f"内容: {doc.page_content[:100]}...")
        print(f"元数据: {doc.metadata}")
    
11、WebBaseLoader
  • 功能:

  • 核心参数:

    • web_pathsurl: 网页URL

    • bs_kwargs: BeautifulSoup 解析参数

    • requests_kwargs: 请求参数

  • 代码示例:

    from langchain_community.document_loaders import WebBaseLoader
    
    # 加载单个网页
    loader1 = WebBaseLoader("https://example.com")
    
    # 加载多个网页
    urls = [
        "https://example.com/page1",
        "https://example.com/page2"
    ]
    loader2 = WebBaseLoader(urls)
    
    # 自定义请求参数
    loader3 = WebBaseLoader(
        "https://example.com",
        requests_kwargs={
            'headers': {'User-Agent': 'Mozilla/5.0'},
            'timeout': 10
        },
        bs_kwargs={'features': 'lxml'}
    )
    
    documents = loader3.load()
    print(f"从网页加载了 {len(documents)} 个文档")
    for doc in documents:
        print(f"网页标题: {doc.metadata.get('title', 'N/A')}")
        print(f"内容预览: {doc.page_content[:200]}...")
        print(f"来源: {doc.metadata.get('source', 'N/A')}")
        print("---")
    

3、错误处理(仅供参考)

  • 单文件类型版本
# 导入必要的库和模块
from langchain_community.document_loaders import DirectoryLoader  # 导入目录加载器,用于批量加载文档
from langchain_community.document_loaders import TextLoader       # 导入文本加载器,用于加载txt文件
import os        # 导入操作系统接口模块,用于文件路径操作
import glob      # 导入glob模块,用于文件模式匹配

def safe_load_document(directory, glob_pattern="*.*",loader_cls=TextLoader):
    """
    安全加载文档的函数,能够处理文件加载过程中的常见错误
    
    参数:
        directory (str): 要加载的文档目录路径
        glob_pattern (str): 文件匹配模式,默认为所有文件
        loader_cls (class): 文档加载器类,默认为TextLoader
    
    返回:
        list: 包含成功加载的所有Document对象的列表
    """
    documents = []  # 初始化一个空列表,用于存储成功加载的文档
    error_files = []  # 记录加载失败的文件

    # 检查目录是否存在
    if not os.path.exists(directory):
        print(f"错误: 目录 {directory} 不存在")
        return documents, error_files
    
    # 使用glob模式匹配目录中符合条件的所有文件路径
    # os.path.join() 用于安全地拼接目录路径和文件模式
    try:
        for file_path in glob.glob(os.path.join(directory, glob_pattern)):
            try:
                # 检查文件是否为空
                if os.path.getsize(file_path) == 0:
                    print(f"警告: 文件 {file_path} 为空,跳过")
                    error_files.append(file_path)
                    continue
                    
                loader = loader_cls(file_path, encoding="utf-8")
                loaded_docs = loader.load()
                
                # 检查是否成功加载到内容
                if loaded_docs and len(loaded_docs) > 0:
                    documents.extend(loaded_docs)
                    print(f"成功: 加载文件 {file_path},获得 {len(loaded_docs)} 个文档")
                else:
                    print(f"警告: 文件 {file_path} 没有加载到内容")
                    error_files.append(file_path)
                    
            except Exception as e:
                print(f"错误: 加载文件 {file_path} 失败 - {str(e)}")
                error_files.append(file_path)
                continue
                
    except Exception as e:
        print(f"错误: 遍历目录时发生异常 - {str(e)}")
    
    return documents, error_files  # 返回成功和失败的文件信息

# 使用示例:调用安全加载函数
# 加载指定目录下所有.txt文件
documents, errors = safe_load_documents("./documents/", glob_pattern="*.txt")

# 打印加载结果统计信息
print(f"成功加载 {len(documents)} 个文档,{len(errors)} 个文件加载失败")
  • 多文件类型的版本
from langchain_community.document_loaders import PyPDFLoader, UnstructuredWordDocumentLoader

def safe_load_multiple_types(directory):
    """
    支持多种文件类型的安全加载函数
    """
    documents = []
    
    # 定义文件类型与对应加载器的映射
    loader_map = {
        '.txt': (TextLoader, {'encoding': 'utf-8'}),
        '.pdf': (PyPDFLoader, {}),
        '.docx': (UnstructuredWordDocumentLoader, {'mode': 'elements'})
    }
    
    for file_path in glob.glob(os.path.join(directory, "*.*")):
        # 获取文件扩展名
        file_ext = os.path.splitext(file_path)[1].lower()
        
        # 检查是否有对应的加载器
        if file_ext in loader_map:
            loader_class, loader_kwargs = loader_map[file_ext]
            try:
                loader = loader_class(file_path, **loader_kwargs)
                documents.extend(loader.load())
                print(f"成功加载: {file_path}")
            except Exception as e:
                print(f"加载失败: {file_path} - {e}")
        else:
            print(f"跳过不支持的文件类型: {file_path}")
    
    return documents

4、懒加载机制:

懒加载(Lazy Loading)是一种程序设计优化技术,核心思想是延迟初始化对象,直到真正需要使用时才创建

DirectoryLoader的懒加载实现
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.document_loaders import PyPDFLoader
import time

# 创建DirectoryLoader实例(默认启用懒加载)
loader = DirectoryLoader(
    "./large_documents/",  # 包含大量PDF文件的目录
    glob="*.pdf",          # 匹配所有PDF文件
    loader_cls=PyPDFLoader, # 指定PDF加载器
    show_progress=True     # 显示加载进度条
)

print("创建加载器完成,但还没有真正读取任何文件...")
print(f"加载器类型: {type(loader)}")
print("---")




# 方法1:一次性加载所有文档(触发实际读取)
print("开始使用load()方法加载文档...")
start_time = time.time()
documents = loader.load()  # 这时才真正开始读取所有文件
end_time = time.time()

print(f"共加载了 {len(documents)} 个文档")
print(f"加载耗时: {end_time - start_time:.2f} 秒")
print("---")





# 方法2:使用懒加载迭代器(推荐处理大文件)
print("开始使用lazy_load()方法逐个处理文档...")
start_time = time.time()

processed_count = 0
for doc in loader.lazy_load():  # 返回生成器,按需加载
    # 模拟文档处理
    processed_count += 1
    print(f"正在处理第 {processed_count} 个文档: {doc.metadata['source']}")
    
    # 这里可以添加实际的文档处理逻辑
    # 例如:文本分析、向量化、存储到数据库等
    
    # 每处理10个文档显示一次进度
    if processed_count % 10 == 0:
        print(f"已处理 {processed_count} 个文档...")

end_time = time.time()
print(f"懒加载处理完成,总共处理 {processed_count} 个文档")
print(f"处理耗时: {end_time - start_time:.2f} 秒")
lazy_load() 方法深度解析

lazy_load() 是 DirectoryLoader 类中的一个方法,它返回一个生成器(Generator),能够按需逐个加载和处理文档,而不是一次性加载所有文件到内存中

  • load() 方法将会立即加载所有文档到内存
  • lazy_load() 方法只在迭代时加载当前文件, 生成一个文档后立即暂停,等待下一次请求
性能对比结果分析

我这里处理1000个PDF文件,性能对比结果如下:

指标一次性加载懒加载优势
初始响应时间20秒0.5秒40倍提升
内存占用峰值2GB50MB40倍降低
首个结果时间20秒后立即实时响应
错误处理全部失败部分失败容错性更好
用户体验长时间等待即时反馈体验更佳

5、 文档加载性能优化技巧

from langchain_community.document_loaders import DirectoryLoader, TextLoader
import os
import glob

class OptimizedDocumentLoader:
    """优化文档加载器"""
    
    def __init__(self, base_directory):
        self.base_directory = base_directory
    
    def multi_threaded_loading(self, file_pattern="*.txt", recursive=True):
        """
        多线程加载文档
        
        参数:
            file_pattern: 文件匹配模式
            recursive: 是否递归搜索子目录
        """
        loader = DirectoryLoader(
            self.base_directory,
            glob=file_pattern,
            loader_cls=TextLoader,
            loader_kwargs={"encoding": "utf-8", "autodetect_encoding": True},
            use_multithreading=True,      # 启用多线程加速
            silent_errors=True,           # 静默处理错误文件
            recursive=recursive,          # 递归搜索
            show_progress=True           # 显示进度条
        )
        
        return loader.load()
    
    def batch_loading(self, file_pattern="*.txt", batch_size=100, recursive=True):
        """
        分批加载文档,适用于超大目录
        
        参数:
            file_pattern: 文件匹配模式
            batch_size: 每批处理的文件数量
            recursive: 是否递归搜索
        """
        all_documents = []
        
        # 构建搜索模式
        if recursive:
            search_pattern = os.path.join(self.base_directory, "**", file_pattern)
        else:
            search_pattern = os.path.join(self.base_directory, file_pattern)
        
        # 获取所有文件
        file_list = glob.glob(search_pattern, recursive=recursive)
        total_files = len(file_list)
        
        print(f"找到 {total_files} 个文件,开始分批加载(每批 {batch_size} 个文件)...")
        
        for batch_index, i in enumerate(range(0, total_files, batch_size)):
            batch_files = file_list[i:i + batch_size]
            batch_documents = []
            
            for file_path in batch_files:
                try:
                    loader = TextLoader(file_path, encoding="utf-8")
                    documents = loader.load()
                    batch_documents.extend(documents)
                except Exception as e:
                    print(f" 加载失败: {os.path.basename(file_path)} - {str(e)}")
                    continue
            
            all_documents.extend(batch_documents)
            
            # 进度报告
            processed_files = min(i + batch_size, total_files)
            progress = (processed_files / total_files) * 100
            print(f" 批次 {batch_index + 1}: 处理 {len(batch_files)} 个文件 → 获得 {len(batch_documents)} 个文档 "
                  f"({progress:.1f}%)")
            
            # 内存优化:清理批处理数据
            del batch_documents
            
        print(f" 加载完成: 总共 {len(all_documents)} 个文档")
        return all_documents
    
    def lazy_batch_processing(self, file_pattern="*.txt", recursive=True):
        """
        懒加载 + 分批处理(内存最优方案)
        """
        loader = DirectoryLoader(
            self.base_directory,
            glob=file_pattern,
            loader_cls=TextLoader,
            loader_kwargs={"encoding": "utf-8"},
            recursive=recursive,
            use_multithreading=True
        )
        
        # 返回生成器,实现真正的流式处理
        return loader.lazy_load()

# 使用示例
def demo_optimized_loading():
    """演示优化后的加载方法"""
    
    loader = OptimizedDocumentLoader("./documents/")
    
    print("=== 方法1: 多线程加载 ===")
    documents1 = loader.multi_threaded_loading("*.txt")
    print(f"多线程加载结果: {len(documents1)} 个文档")
    
    print("\n=== 方法2: 分批加载 ===")
    documents2 = loader.batch_loading("*.txt", batch_size=50)
    print(f"分批加载结果: {len(documents2)} 个文档")
    
    print("\n=== 方法3: 懒加载流式处理 ===")
    processed_count = 0
    for document in loader.lazy_batch_processing("*.txt"):
        # 在这里处理每个文档
        processed_count += 1
        if processed_count % 10 == 0:
            print(f"已流式处理 {processed_count} 个文档...")
    
    print(f"流式处理完成: {processed_count} 个文档")

# 运行演示
if __name__ == "__main__":
    demo_optimized_loading()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值