一、LangChain 文档加载器与文本分割器核心概念

这两个模块是 RAG(检索增强生成) 的基石,解决了「如何把非结构化文档(PDF、Word、网页)变成大模型能处理的文本块」的问题,核心目标是保留语义完整性,不能把一句话、一个段落切得支离破碎。

1. 文档加载器(Document Loaders)

核心定义

文档加载器是 LangChain 中负责把各种格式的非结构化文档(PDF、TXT、Word、PPT、网页、Markdown 等)加载成标准化 Document 对象的模块。

标准化 Document 对象

每个加载后的文档都会变成一个 Document 对象,包含两个核心属性:

属性 说明 示例
page_content 文档的文本内容 "这是 PDF 第一页的内容..."
metadata 文档的元数据(来源、页码、作者等) {"source": "test.pdf", "page": 1}

常用文档加载器

加载器名称 适用格式 说明
PyPDFLoader PDF 最常用的 PDF 加载器,能提取文本和页码
TextLoader TXT 纯文本文件加载器
Docx2txtLoader Word (.docx) Word 文档加载器
WebBaseLoader 网页 网页内容加载器
DirectoryLoader 文件夹 批量加载文件夹里的所有文档

2. 文本分割器(Text Splitters)

核心定义

因为大模型有上下文窗口限制(比如 doubao-pro-32k 是 32k token),不能直接把整个几万字的文档放进去,所以需要用文本分割器把文档分割成小的、语义完整的文本块(Chunks)

核心目标:保留语义完整性

这是文本分割最重要的原则 ——绝对不能把一句话、一个段落、一个主题切在中间,否则大模型检索到的是残缺的内容,无法正确回答问题。

最常用的文本分割器:RecursiveCharacterTextSplitter

这是 LangChain 官方最推荐、最通用、最能保留语义完整性的分割器,它的核心逻辑是递归按优先级分割

  1. 优先按 \n\n(段落分隔符)分割
  2. 如果块还是太大,按 \n(换行符)分割
  3. 如果还是太大,按 (句子结束符)分割
  4. 最后按 (空格)分割

这种设计能最大程度保留段落、句子的完整性,不会把语义切散。

文本分割的两个关键参数

参数 作用 推荐值 说明
chunk_size 单个文本块的最大大小(字符数或 token 数) 1000-2000 字符 / 500-1000 token 根据大模型的上下文窗口调整,窗口大可以设大一点
chunk_overlap 相邻文本块的重叠大小(字符数或 token 数) 200-400 字符 / 100-200 token 让相邻块有重叠,保留上下文连续性,避免语义断裂

二、实战:加载 PDF 并进行语义完整的文本分割

下面是一份完全兼容 LangChain 1.0+ 最新版本的代码,实现了:

  1. 加载本地 PDF 文档
  2. RecursiveCharacterTextSplitter 进行语义完整的分割
  3. 输出分割结果,验证语义完整性

1. 准备工作

(1)安装依赖

pip install -U langchain langchain-community pypdf python-dotenv tiktoken
  • pypdf:用于加载 PDF 文档
  • tiktoken:用于按 token 数分割(更准确控制上下文窗口)

2. 完整可运行代码

import os
from dotenv import load_dotenv

# ====================== LangChain 1.0+ 最新导入 ======================
from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

# ====================== 1. 加载环境变量(可选) ======================
load_dotenv()

# ====================== 2. 核心配置 ======================
# PDF 文件路径
PDF_PATH = "test.pdf"  # 替换成你的 PDF 文件路径

# 文本分割参数(语义完整的关键)
CHUNK_SIZE = 1000  # 单个块的最大字符数(推荐 1000-2000)
CHUNK_OVERLAP = 200  # 相邻块的重叠字符数(推荐 200-400,保留上下文)

# ====================== 3. 第一步:加载 PDF 文档 ======================
def load_pdf(pdf_path: str):
    """
    加载本地 PDF 文档
    :param pdf_path: PDF 文件路径
    :return: Document 对象列表(每个元素是 PDF 的一页)
    """
    print(f"📄 正在加载 PDF:{pdf_path}...")
    
    # 初始化 PDF 加载器
    loader = PyPDFLoader(pdf_path)
    
    # 加载文档(返回 Document 对象列表,每个元素对应 PDF 的一页)
    documents = loader.load()
    
    print(f"✅ PDF 加载完成!共 {len(documents)} 页\n")
    return documents

# ====================== 4. 第二步:语义完整的文本分割 ======================
def split_documents(documents):
    """
    用 RecursiveCharacterTextSplitter 进行语义完整的文本分割
    :param documents: 加载后的 Document 对象列表
    :return: 分割后的文本块列表
    """
    print("✂️  正在进行语义完整的文本分割...")
    
    # ✅ 核心:初始化 RecursiveCharacterTextSplitter(最推荐的分割器)
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=CHUNK_SIZE,
        chunk_overlap=CHUNK_OVERLAP,
        # 分割优先级(按顺序,优先保留段落、句子)
        separators=["\n\n", "\n", "。", "!", "?", " ", ""],
        # 可选:按 token 数分割(更准确控制大模型上下文窗口)
        # length_function=len,  # 默认按字符数
        # length_function=lambda text: len(tiktoken.encoding_for_model("gpt-3.5-turbo").encode(text)),  # 按 token 数
    )
    
    # 执行分割
    split_chunks = text_splitter.split_documents(documents)
    
    print(f"✅ 文本分割完成!共生成 {len(split_chunks)} 个文本块\n")
    return split_chunks

# ====================== 5. 第三步:输出分割结果(验证语义完整性) ======================
def print_split_results(split_chunks, num_to_print=3):
    """
    打印分割结果,验证语义完整性
    :param split_chunks: 分割后的文本块列表
    :param num_to_print: 打印前几个块
    """
    print(f"===== 📝 分割结果预览(前 {num_to_print} 个块) =====")
    for i, chunk in enumerate(split_chunks[:num_to_print]):
        print(f"\n【第 {i+1} 个文本块】")
        print(f"📄 来源:{chunk.metadata['source']}(第 {chunk.metadata.get('page', '未知')} 页)")
        print(f"📏 字符数:{len(chunk.page_content)}")
        print(f"📝 内容:\n{chunk.page_content}")
        print("-" * 80)

# ====================== 6. 主程序 ======================
def main():
    print("===== 🚀 LangChain PDF 加载与语义分割 =====")
    
    # 1. 加载 PDF
    documents = load_pdf(PDF_PATH)
    
    # 2. 语义分割
    split_chunks = split_documents(documents)
    
    # 3. 输出结果(验证语义完整性)
    print_split_results(split_chunks)
    
    print("\n🎉 所有步骤完成!")
    print("💡 提示:分割后的 split_chunks 可以直接用于下一步的向量化和 RAG 检索")

if __name__ == "__main__":
    main()

三、代码核心部分详解

1. PDF 加载:PyPDFLoader

loader = PyPDFLoader(pdf_path)
documents = loader.load()
  • PyPDFLoader 会自动提取 PDF 的文本内容和页码
  • 返回的 documents 是一个列表,每个元素对应 PDF 的一页,是一个 Document 对象

2. 语义分割:RecursiveCharacterTextSplitter(核心)

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=CHUNK_SIZE,
    chunk_overlap=CHUNK_OVERLAP,
    # 分割优先级:先按段落,再按换行,再按句子,最后按空格
    separators=["\n\n", "\n", "。", "!", "?", " ", ""],
)
split_chunks = text_splitter.split_documents(documents)
  • separators 参数是保留语义完整性的关键,它定义了分割的优先级,优先按段落、句子分割,最大程度保留语义
  • chunk_overlap 让相邻块有重叠,避免上下文断裂

四、运行效果演示

plaintext

===== 🚀 LangChain PDF 加载与语义分割 =====
📄 正在加载 PDF:test.pdf...
✅ PDF 加载完成!共 5 页

✂️  正在进行语义完整的文本分割...
✅ 文本分割完成!共生成 12 个文本块

===== 📝 分割结果预览(前 3 个块) =====

【第 1 个文本块】
📄 来源:test.pdf(第 1 页)
📏 字符数:987
📝 内容:
这是一份关于人工智能的测试文档。
人工智能(AI)是计算机科学的一个分支,它企图了解智能的实质,并生产出一种新的能以人类智能相似的方式做出反应的智能机器。

该领域的研究包括机器人、语言识别、图像识别、自然语言处理和专家系统等。
人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大,可以设想,未来人工智能带来的科技产品,将会是人类智慧的“容器”。
--------------------------------------------------------------------------------

【第 2 个文本块】
📄 来源:test.pdf(第 1 页)
📏 字符数:956
📝 内容:
人工智能从诞生以来,理论和技术日益成熟,应用领域也不断扩大,可以设想,未来人工智能带来的科技产品,将会是人类智慧的“容器”。

人工智能可以对人的意识、思维的信息过程的模拟。人工智能不是人的智能,但能像人那样思考、也可能超过人的智能。

1956年,在达特茅斯会议上,“人工智能”这一概念被正式提出。
--------------------------------------------------------------------------------

你可以看到,第 1 个块和第 2 个块有重叠的内容(人工智能从诞生以来...),这就是 chunk_overlap 的作用,保留了上下文连续性,同时每个块都是完整的段落和句子,语义完整。

五、保留语义完整性的 3 个技巧

  1. 必须用 RecursiveCharacterTextSplitter:它是最通用、最能保留语义的分割器,不要用简单的 CharacterTextSplitter
  2. 设置合适的 chunk_overlap:推荐设为 chunk_size 的 10%-20%,让相邻块有重叠,保留上下文
  3. 优先按 token 数分割:用 tiktoken 库按 token 数分割(而不是字符数),能更准确地控制在大模型的上下文窗口内,避免浪费或溢出

🔴 根本原因:PDF 文本提取失败

看你打印出来的结果,每个 Documentpage_content 都是空的:

大概率是这两种情况:

  1. PDF 是扫描件 / 图片版:不是可复制的文本,而是图片,PyPDFLoader 无法直接提取图片里的文字。
  2. PDF 有特殊加密 / 字体问题PyPDFLoader 对部分字体或加密 PDF 的兼容性差,导致提取文本失败。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐