一、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 加载器,能提取文本和页码 | |
TextLoader | TXT | 纯文本文件加载器 |
Docx2txtLoader | Word (.docx) | Word 文档加载器 |
WebBaseLoader | 网页 | 网页内容加载器 |
DirectoryLoader | 文件夹 | 批量加载文件夹里的所有文档 |
2. 文本分割器(Text Splitters)
核心定义
因为大模型有上下文窗口限制(比如doubao-pro-32k是 32k token),不能直接把整个几万字的文档放进去,所以需要用文本分割器把文档分割成小的、语义完整的文本块(Chunks)。
核心目标:保留语义完整性
这是文本分割最重要的原则 ——绝对不能把一句话、一个段落、一个主题切在中间,否则大模型检索到的是残缺的内容,无法正确回答问题。
最常用的文本分割器:RecursiveCharacterTextSplitter
这是 LangChain 官方最推荐、最通用、最能保留语义完整性的分割器,它的核心逻辑是递归按优先级分割:
- 优先按
\n\n(段落分隔符)分割 - 如果块还是太大,按
\n(换行符)分割 - 如果还是太大,按
。(句子结束符)分割 - 最后按 (空格)分割
这种设计能最大程度保留段落、句子的完整性,不会把语义切散。
文本分割的两个关键参数
| 参数 | 作用 | 推荐值 | 说明 |
|---|---|---|---|
chunk_size | 单个文本块的最大大小(字符数或 token 数) | 1000-2000 字符 / 500-1000 token | 根据大模型的上下文窗口调整,窗口大可以设大一点 |
chunk_overlap | 相邻文本块的重叠大小(字符数或 token 数) | 200-400 字符 / 100-200 token | 让相邻块有重叠,保留上下文连续性,避免语义断裂 |
二、实战:加载 PDF 并进行语义完整的文本分割
下面是一份完全兼容 LangChain 1.0+ 最新版本的代码,实现了:
- 加载本地 PDF 文档
- 用
RecursiveCharacterTextSplitter进行语义完整的分割 - 输出分割结果,验证语义完整性
1. 准备工作
(1)安装依赖
pip install -U langchain langchain-community pypdf python-dotenv tiktokenpypdf:用于加载 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 个技巧
- 必须用
RecursiveCharacterTextSplitter:它是最通用、最能保留语义的分割器,不要用简单的CharacterTextSplitter - 设置合适的
chunk_overlap:推荐设为chunk_size的 10%-20%,让相邻块有重叠,保留上下文 - 优先按 token 数分割:用
tiktoken库按 token 数分割(而不是字符数),能更准确地控制在大模型的上下文窗口内,避免浪费或溢出
🔴 根本原因:PDF 文本提取失败
看你打印出来的结果,每个Document的page_content都是空的:
大概率是这两种情况:
- PDF 是扫描件 / 图片版:不是可复制的文本,而是图片,
PyPDFLoader无法直接提取图片里的文字。 - PDF 有特殊加密 / 字体问题:
PyPDFLoader对部分字体或加密 PDF 的兼容性差,导致提取文本失败。