Part 7: Documents(文档处理)
文章目录
-
- [Part 7: Documents(文档处理)](#Part 7: Documents(文档处理))
-
- [7.1 Document 对象](#7.1 Document 对象)
-
- [Document 类结构](#Document 类结构)
- [创建和操作 Demo](#创建和操作 Demo)
- [7.2 Document Loaders(文档加载器)](#7.2 Document Loaders(文档加载器))
-
- 概念和分类
- TextLoader(所有参数)
- [PyPDFLoader(所有参数 + 按页加载)](#PyPDFLoader(所有参数 + 按页加载))
- Docx2txtLoader
- UnstructuredMarkdownLoader
- WebBaseLoader(爬取网页)
- CSVLoader
- JSONLoader
- DirectoryLoader(递归加载目录)
- RecursiveUrlLoader(递归爬取网站)
- [自定义加载器 Demo](#自定义加载器 Demo)
- [7.3 Text Splitters(文本分割器)](#7.3 Text Splitters(文本分割器))
-
- 为什么需要分割
- [RecursiveCharacterTextSplitter(所有参数 + 中文优化配置)](#RecursiveCharacterTextSplitter(所有参数 + 中文优化配置))
- CharacterTextSplitter
- TokenTextSplitter
- MarkdownHeaderTextSplitter(按标题分割)
- HTMLHeaderTextSplitter
- RecursiveJsonSplitter
- 分割策略对比表格
- [分割质量评估 Demo](#分割质量评估 Demo)
- [7.4 文档处理最佳实践](#7.4 文档处理最佳实践)
7.1 Document 对象
Document 是 LangChain 文档处理体系中最基础的数据结构。无论是从 PDF 加载内容、从网页抓取数据,还是对文本进行分割,最终都会以 Document 对象的形式在各个组件之间传递。
Document 类结构
#mermaid-svg-aXh9nqI4sN0N7ilO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aXh9nqI4sN0N7ilO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aXh9nqI4sN0N7ilO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aXh9nqI4sN0N7ilO .error-icon{fill:#552222;}#mermaid-svg-aXh9nqI4sN0N7ilO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aXh9nqI4sN0N7ilO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aXh9nqI4sN0N7ilO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aXh9nqI4sN0N7ilO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aXh9nqI4sN0N7ilO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aXh9nqI4sN0N7ilO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aXh9nqI4sN0N7ilO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aXh9nqI4sN0N7ilO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aXh9nqI4sN0N7ilO .marker.cross{stroke:#333333;}#mermaid-svg-aXh9nqI4sN0N7ilO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aXh9nqI4sN0N7ilO p{margin:0;}#mermaid-svg-aXh9nqI4sN0N7ilO g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-aXh9nqI4sN0N7ilO g.classGroup text .title{font-weight:bolder;}#mermaid-svg-aXh9nqI4sN0N7ilO .cluster-label text{fill:#333;}#mermaid-svg-aXh9nqI4sN0N7ilO .cluster-label span{color:#333;}#mermaid-svg-aXh9nqI4sN0N7ilO .cluster-label span p{background-color:transparent;}#mermaid-svg-aXh9nqI4sN0N7ilO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aXh9nqI4sN0N7ilO .cluster text{fill:#333;}#mermaid-svg-aXh9nqI4sN0N7ilO .cluster span{color:#333;}#mermaid-svg-aXh9nqI4sN0N7ilO .nodeLabel,#mermaid-svg-aXh9nqI4sN0N7ilO .edgeLabel{color:#131300;}#mermaid-svg-aXh9nqI4sN0N7ilO .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-aXh9nqI4sN0N7ilO .label text{fill:#131300;}#mermaid-svg-aXh9nqI4sN0N7ilO .labelBkg{background:#ECECFF;}#mermaid-svg-aXh9nqI4sN0N7ilO .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-aXh9nqI4sN0N7ilO .classTitle{font-weight:bolder;}#mermaid-svg-aXh9nqI4sN0N7ilO .node rect,#mermaid-svg-aXh9nqI4sN0N7ilO .node circle,#mermaid-svg-aXh9nqI4sN0N7ilO .node ellipse,#mermaid-svg-aXh9nqI4sN0N7ilO .node polygon,#mermaid-svg-aXh9nqI4sN0N7ilO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aXh9nqI4sN0N7ilO .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO g.clickable{cursor:pointer;}#mermaid-svg-aXh9nqI4sN0N7ilO g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-aXh9nqI4sN0N7ilO g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-aXh9nqI4sN0N7ilO .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-aXh9nqI4sN0N7ilO .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-aXh9nqI4sN0N7ilO .dashed-line{stroke-dasharray:3;}#mermaid-svg-aXh9nqI4sN0N7ilO .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-aXh9nqI4sN0N7ilO #compositionStart,#mermaid-svg-aXh9nqI4sN0N7ilO .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO #compositionEnd,#mermaid-svg-aXh9nqI4sN0N7ilO .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO #dependencyStart,#mermaid-svg-aXh9nqI4sN0N7ilO .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO #dependencyStart,#mermaid-svg-aXh9nqI4sN0N7ilO .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO #extensionStart,#mermaid-svg-aXh9nqI4sN0N7ilO .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO #extensionEnd,#mermaid-svg-aXh9nqI4sN0N7ilO .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO #aggregationStart,#mermaid-svg-aXh9nqI4sN0N7ilO .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO #aggregationEnd,#mermaid-svg-aXh9nqI4sN0N7ilO .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO #lollipopStart,#mermaid-svg-aXh9nqI4sN0N7ilO .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO #lollipopEnd,#mermaid-svg-aXh9nqI4sN0N7ilO .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-aXh9nqI4sN0N7ilO .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-aXh9nqI4sN0N7ilO .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aXh9nqI4sN0N7ilO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aXh9nqI4sN0N7ilO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aXh9nqI4sN0N7ilO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 包含
Document
+str page_content 文档正文内容
+dict metadata 元数据信息
+str type 文档类型标识
+to_json() : dict 转换为JSON
+copy() : Document 创建副本
Metadata
+str source 来源路径
+int page 页码
+str author 作者
+datetime created_at 创建时间
+str title 标题
+str language 语言
+dict 自定义字段 任意附加信息
Document 对象包含两个核心字段:
| 字段 | 类型 | 说明 | 示例 |
|---|---|---|---|
page_content |
str |
文档的正文内容 | "这是一段文本..." |
metadata |
dict |
文档的元数据信息 | {"source": "file.pdf", "page": 1} |
创建和操作 Demo
python
"""
Document 对象的基本操作 Demo
"""
from langchain_core.documents import Document
# ==========================================
# 1. 创建 Document 对象
# ==========================================
# 最基本的创建方式
doc = Document(
page_content="LangChain 是一个用于开发大语言模型应用的框架。",
metadata={"source": "intro.txt", "author": "张三"}
)
print(f"内容: {doc.page_content}")
print(f"元数据: {doc.metadata}")
# ==========================================
# 2. 批量创建文档
# ==========================================
documents = [
Document(
page_content="第一章:Python 基础语法",
metadata={"chapter": 1, "title": "Python 入门"}
),
Document(
page_content="第二章:Python 数据结构",
metadata={"chapter": 2, "title": "Python 入门"}
),
Document(
page_content="第三章:Python 函数编程",
metadata={"chapter": 3, "title": "Python 入门"}
),
]
for doc in documents:
print(f"[第{doc.metadata['chapter']}章] {doc.page_content}")
# ==========================================
# 3. 文档的复制与修改
# ==========================================
# 使用 copy() 方法创建副本(避免修改原始文档)
original = Document(
page_content="原始内容",
metadata={"source": "original.txt"}
)
# 创建副本并修改
copied = original.copy()
copied.page_content = "修改后的内容"
copied.metadata["modified"] = True
print(f"原始: {original.page_content}") # 输出: 原始内容
print(f"副本: {copied.page_content}") # 输出: 修改后的内容
# ==========================================
# 4. 文档的序列化与反序列化
# ==========================================
import json
doc = Document(
page_content="序列化测试内容",
metadata={"source": "test.txt", "page": 1, "tags": ["AI", "NLP"]}
)
# 转换为字典
doc_dict = {
"page_content": doc.page_content,
"metadata": doc.metadata
}
print(json.dumps(doc_dict, ensure_ascii=False, indent=2))
# ==========================================
# 5. 文档过滤操作
# ==========================================
documents = [
Document(page_content="苹果公司发布新产品", metadata={"category": "科技", "lang": "zh"}),
Document(page_content="Apple releases new product", metadata={"category": "tech", "lang": "en"}),
Document(page_content="量子计算取得突破", metadata={"category": "科技", "lang": "zh"}),
Document(page_content="Climate change report", metadata={"category": "science", "lang": "en"}),
]
# 按语言过滤
zh_docs = [doc for doc in documents if doc.metadata.get("lang") == "zh"]
print(f"中文文档数量: {len(zh_docs)}")
# 按类别过滤
tech_docs = [doc for doc in documents if doc.metadata.get("category") in ("科技", "tech")]
print(f"科技文档数量: {len(tech_docs)}")
# ==========================================
# 6. 文档内容统计
# ==========================================
for doc in documents:
char_count = len(doc.page_content)
word_count = len(doc.page_content.split())
print(f"文档: {doc.page_content[:20]}... | 字符数: {char_count} | 词数: {word_count}")
7.2 Document Loaders(文档加载器)
文档加载器负责将各种格式的数据源转换为 Document 对象列表。LangChain 提供了上百种加载器,覆盖了几乎所有常见的数据格式。
概念和分类
#mermaid-svg-4TbUheiQ1lgcKaMS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4TbUheiQ1lgcKaMS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4TbUheiQ1lgcKaMS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4TbUheiQ1lgcKaMS .error-icon{fill:#552222;}#mermaid-svg-4TbUheiQ1lgcKaMS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4TbUheiQ1lgcKaMS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4TbUheiQ1lgcKaMS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4TbUheiQ1lgcKaMS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4TbUheiQ1lgcKaMS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4TbUheiQ1lgcKaMS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4TbUheiQ1lgcKaMS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4TbUheiQ1lgcKaMS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4TbUheiQ1lgcKaMS .marker.cross{stroke:#333333;}#mermaid-svg-4TbUheiQ1lgcKaMS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4TbUheiQ1lgcKaMS p{margin:0;}#mermaid-svg-4TbUheiQ1lgcKaMS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4TbUheiQ1lgcKaMS .cluster-label text{fill:#333;}#mermaid-svg-4TbUheiQ1lgcKaMS .cluster-label span{color:#333;}#mermaid-svg-4TbUheiQ1lgcKaMS .cluster-label span p{background-color:transparent;}#mermaid-svg-4TbUheiQ1lgcKaMS .label text,#mermaid-svg-4TbUheiQ1lgcKaMS span{fill:#333;color:#333;}#mermaid-svg-4TbUheiQ1lgcKaMS .node rect,#mermaid-svg-4TbUheiQ1lgcKaMS .node circle,#mermaid-svg-4TbUheiQ1lgcKaMS .node ellipse,#mermaid-svg-4TbUheiQ1lgcKaMS .node polygon,#mermaid-svg-4TbUheiQ1lgcKaMS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4TbUheiQ1lgcKaMS .rough-node .label text,#mermaid-svg-4TbUheiQ1lgcKaMS .node .label text,#mermaid-svg-4TbUheiQ1lgcKaMS .image-shape .label,#mermaid-svg-4TbUheiQ1lgcKaMS .icon-shape .label{text-anchor:middle;}#mermaid-svg-4TbUheiQ1lgcKaMS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4TbUheiQ1lgcKaMS .rough-node .label,#mermaid-svg-4TbUheiQ1lgcKaMS .node .label,#mermaid-svg-4TbUheiQ1lgcKaMS .image-shape .label,#mermaid-svg-4TbUheiQ1lgcKaMS .icon-shape .label{text-align:center;}#mermaid-svg-4TbUheiQ1lgcKaMS .node.clickable{cursor:pointer;}#mermaid-svg-4TbUheiQ1lgcKaMS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4TbUheiQ1lgcKaMS .arrowheadPath{fill:#333333;}#mermaid-svg-4TbUheiQ1lgcKaMS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4TbUheiQ1lgcKaMS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4TbUheiQ1lgcKaMS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4TbUheiQ1lgcKaMS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4TbUheiQ1lgcKaMS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4TbUheiQ1lgcKaMS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4TbUheiQ1lgcKaMS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4TbUheiQ1lgcKaMS .cluster text{fill:#333;}#mermaid-svg-4TbUheiQ1lgcKaMS .cluster span{color:#333;}#mermaid-svg-4TbUheiQ1lgcKaMS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4TbUheiQ1lgcKaMS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4TbUheiQ1lgcKaMS rect.text{fill:none;stroke-width:0;}#mermaid-svg-4TbUheiQ1lgcKaMS .icon-shape,#mermaid-svg-4TbUheiQ1lgcKaMS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4TbUheiQ1lgcKaMS .icon-shape p,#mermaid-svg-4TbUheiQ1lgcKaMS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4TbUheiQ1lgcKaMS .icon-shape .label rect,#mermaid-svg-4TbUheiQ1lgcKaMS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4TbUheiQ1lgcKaMS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4TbUheiQ1lgcKaMS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4TbUheiQ1lgcKaMS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Document Loaders 文档加载器
文本类
文档类
网页类
Office 类
数据库类
云存储类
自定义加载器
TextLoader
CSVLoader
JSONLoader
UnstructuredMarkdownLoader
PyPDFLoader
PyMuPDFLoader
UnstructuredFileLoader
WebBaseLoader
RecursiveUrlLoader
AsyncWebBaseLoader
SitemapLoader
Docx2txtLoader
UnstructuredExcelLoader
UnstructuredPowerPointLoader
SQLDatabaseLoader
MongoDBLoader
RedisLoader
S3FileLoader
GCSFileLoader
AzureBlobStorageLoader
TextLoader(所有参数)
TextLoader 是最基础的加载器,用于加载纯文本文件。
python
"""
TextLoader 完整参数 Demo
"""
from langchain_community.document_loaders import TextLoader
# ==========================================
# 基本用法
# ==========================================
# 首先创建一个示例文本文件
with open("/data/user/work/sample.txt", "w", encoding="utf-8") as f:
f.write("第一行:LangChain 是一个强大的框架。\n")
f.write("第二行:它支持多种大语言模型。\n")
f.write("第三行:可以构建复杂的 AI 应用。\n")
# 创建加载器
loader = TextLoader(
file_path="/data/user/work/sample.txt", # 文件路径(必需)
encoding="utf-8", # 文件编码,默认为 "utf-8"
autodetect_encoding=False # 是否自动检测编码,默认为 False
)
# 加载文档
documents = loader.load()
print(f"文档数量: {len(documents)}")
print(f"内容: {documents[0].page_content}")
print(f"元数据: {documents[0].metadata}")
# 输出: {'source': '/data/user/work/sample.txt'}
# ==========================================
# 自动检测编码
# ==========================================
loader_auto = TextLoader(
file_path="/data/user/work/sample.txt",
autodetect_encoding=True # 自动检测文件编码(适用于编码不确定的文件)
)
docs = loader_auto.load()
# ==========================================
# 惰性加载(适用于大文件)
# ==========================================
# lazy_load() 返回一个迭代器,不会一次性加载所有内容到内存
for doc in loader.lazy_load():
print(f"惰性加载: {doc.page_content[:30]}...")
PyPDFLoader(所有参数 + 按页加载)
python
"""
PyPDFLoader 完整参数 Demo
安装: pip install pypdf
"""
from langchain_community.document_loaders import PyPDFLoader
# ==========================================
# 基本用法(按页加载)
# ==========================================
loader = PyPDFLoader(
file_path="example.pdf", # PDF 文件路径(必需)
password="", # PDF 密码,默认为空(用于加密 PDF)
headers={}, # 请求头(用于 URL 路径)
extract_images=False, # 是否提取图片,默认为 False
mode="single", # 加载模式: "single"(每页一个文档)/ "page"(同 single)
# "elements"(按元素分割,需要额外依赖)
)
# 加载文档 ------ 每一页会生成一个独立的 Document
documents = loader.load()
for i, doc in enumerate(documents):
print(f"第 {i+1} 页:")
print(f" 内容: {doc.page_content[:100]}...")
print(f" 元数据: {doc.metadata}")
# metadata 包含: source, page(页码)
# ==========================================
# 指定页码范围加载
# ==========================================
# 使用 lazy_load 获取特定页面
all_docs = []
for doc in loader.lazy_load():
page_num = doc.metadata.get("page", 0)
# 只加载第 1-5 页
if 1 <= page_num <= 5:
all_docs.append(doc)
print(f"加载了 {len(all_docs)} 页")
# ==========================================
# 获取 PDF 总页数
# ==========================================
documents = loader.load()
total_pages = len(documents)
print(f"PDF 总页数: {total_pages}")
Docx2txtLoader
python
"""
Docx2txtLoader Demo
安装: pip install docx2txt
"""
from langchain_community.document_loaders import Docx2txtLoader
# 加载 Word 文档
loader = Docx2txtLoader("document.docx")
documents = loader.load()
print(f"文档数量: {len(documents)}")
print(f"内容: {documents[0].page_content[:200]}...")
print(f"元数据: {documents[0].metadata}")
# 输出: {'source': 'document.docx'}
UnstructuredMarkdownLoader
python
"""
UnstructuredMarkdownLoader Demo
安装: pip install unstructured
"""
from langchain_community.document_loaders import UnstructuredMarkdownLoader
# 加载 Markdown 文件
loader = UnstructuredMarkdownLoader(
file_path="readme.md", # Markdown 文件路径
mode="single", # 加载模式: "single" / "elements"
)
documents = loader.load()
print(f"内容: {documents[0].page_content[:300]}...")
# ==========================================
# elements 模式(按元素分割)
# ==========================================
loader_elements = UnstructuredMarkdownLoader(
file_path="readme.md",
mode="elements" # 按标题、段落、列表等元素分别创建文档
)
docs_elements = loader_elements.load()
for doc in docs_elements:
print(f"类型: {doc.metadata.get('category')} | 内容: {doc.page_content[:50]}...")
WebBaseLoader(爬取网页)
python
"""
WebBaseLoader 完整参数 Demo
安装: pip install beautifulsoup4
"""
from langchain_community.document_loaders import WebBaseLoader
# ==========================================
# 基本用法
# ==========================================
loader = WebBaseLoader(
web_path="https://python.langchain.com/docs/get_started/introduction",
# web_path 可以是单个 URL 字符串,也可以是 URL 列表
# web_path=["https://example.com/page1", "https://example.com/page2"],
bs_kwargs={
"parse_only": { # 只解析特定的 HTML 标签
"id": ["main-content"], # 按 id 选择
# "class": ["post-content"], # 按 class 选择
# "name": ["article"], # 按 name 选择
}
},
# BeautifulSoup 解析器,默认为 "html.parser"
# 可选: "lxml"(更快), "html5lib"(更宽容)
bs_parser="html.parser",
# 请求头
headers={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
},
# 是否验证 SSL 证书
verify_ssl=True,
# 请求超时时间(秒)
requests_per_second=2,
# 继续出错时是否继续
continue_on_failure=False,
# 请求之间的间隔(秒),用于限速
# requests_kwargs={"timeout": 10},
)
# 加载网页内容
documents = loader.load()
print(f"文档数量: {len(documents)}")
print(f"内容长度: {len(documents[0].page_content)} 字符")
print(f"内容预览: {documents[0].page_content[:200]}...")
print(f"元数据: {documents[0].metadata}")
# metadata 包含: source(URL)
# ==========================================
# 批量加载多个网页
# ==========================================
urls = [
"https://python.langchain.com/docs/get_started/introduction",
"https://python.langchain.com/docs/get_started/quickstart",
]
loader_multi = WebBaseLoader(web_path=urls)
docs_multi = loader_multi.load()
print(f"共加载 {len(docs_multi)} 个网页")
# ==========================================
# 异步加载(更快)
# ==========================================
import asyncio
async def load_web_async():
loader = WebBaseLoader(web_path=urls)
docs = await loader.aload() # 异步加载
return docs
# 在 Jupyter Notebook 中可以直接 await
# 在普通脚本中需要 asyncio.run()
# docs = asyncio.run(load_web_async())
CSVLoader
python
"""
CSVLoader 完整参数 Demo
"""
from langchain_community.document_loaders import CSVLoader
# ==========================================
# 基本用法
# ==========================================
# 首先创建示例 CSV 文件
import csv
with open("/data/user/work/sample.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerow(["姓名", "年龄", "城市", "职业"])
writer.writerow(["张三", "28", "北京", "工程师"])
writer.writerow(["李四", "32", "上海", "设计师"])
writer.writerow(["王五", "25", "深圳", "产品经理"])
loader = CSVLoader(
file_path="/data/user/work/sample.csv", # CSV 文件路径(必需)
encoding="utf-8", # 文件编码,默认为 "utf-8"
source_column=None, # 指定某一列作为 source 元数据,默认为 None
csv_args={
"delimiter": ",", # 分隔符,默认为 ","
"quotechar": '"', # 引用字符,默认为 '"'
"fieldnames": None, # 字段名列表,默认为 None(使用第一行)
},
metadata_columns=[], # 要包含在元数据中的列名列表
)
documents = loader.load()
for doc in documents:
print(f"内容: {doc.page_content}")
print(f"元数据: {doc.metadata}")
print("---")
# ==========================================
# 指定 source_column
# ==========================================
loader_with_source = CSVLoader(
file_path="/data/user/work/sample.csv",
source_column="姓名", # 将"姓名"列的值作为 source 元数据
)
docs = loader_with_source.load()
print(f"source 元数据: {docs[0].metadata['source']}") # 输出: 张三
# ==========================================
# 包含元数据列
# ==========================================
loader_meta = CSVLoader(
file_path="/data/user/work/sample.csv",
metadata_columns=["姓名", "城市"], # 这些列会从内容中移除,放入元数据
)
docs_meta = loader_meta.load()
for doc in docs_meta:
print(f"内容: {doc.page_content}")
print(f"元数据: {doc.metadata}")
# metadata: {'source': '...', 'row': 0, '姓名': '张三', '城市': '北京'}
JSONLoader
python
"""
JSONLoader 完整参数 Demo
安装: pip install jq (可选,用于 jq_schema)
"""
import json
from langchain_community.document_loaders import JSONLoader
# ==========================================
# 首先创建示例 JSON 文件
# ==========================================
sample_data = {
"employees": [
{
"name": "张三",
"age": 28,
"department": "工程部",
"skills": ["Python", "JavaScript", "Docker"]
},
{
"name": "李四",
"age": 32,
"department": "设计部",
"skills": ["Figma", "Sketch", "Photoshop"]
},
{
"name": "王五",
"age": 25,
"department": "产品部",
"skills": ["Axure", "Jira", "Confluence"]
}
]
}
with open("/data/user/work/sample.json", "w", encoding="utf-8") as f:
json.dump(sample_data, f, ensure_ascii=False, indent=2)
# ==========================================
# 基本用法:使用 jq_schema 提取特定字段
# ==========================================
loader = JSONLoader(
file_path="/data/user/work/sample.json", # JSON 文件路径(必需)
jq_schema=".employees[]", # jq 过滤表达式,提取 employees 数组中的每个元素
# jq_schema 说明:
# ".employees[]" → 提取数组每个元素
# ".employees[].name" → 只提取 name 字段
# ".data.messages[]" → 嵌套路径
# "." → 整个 JSON 根对象
content_key="name", # 从匹配结果中提取哪个字段作为 page_content
# content_key="." → 使用整个匹配对象作为内容(默认)
metadata_key=None, # 从匹配结果中提取哪个字段作为 metadata
# metadata_key="metadata" → 使用对象的 "metadata" 字段作为元数据
text_content=True, # 是否将内容序列化为字符串,默认为 True
)
documents = loader.load()
for doc in documents:
print(f"内容: {doc.page_content}")
print(f"元数据: {doc.metadata}")
print("---")
# ==========================================
# 提取完整对象作为内容
# ==========================================
loader_full = JSONLoader(
file_path="/data/user/work/sample.json",
jq_schema=".employees[]",
content_key=".", # 使用整个员工对象作为内容
)
docs_full = loader_full.load()
for doc in docs_full:
print(f"内容: {doc.page_content}")
# ==========================================
# 使用 metadata_key 提取元数据
# ==========================================
# 创建带元数据的 JSON
data_with_meta = {
"documents": [
{"text": "文档一内容", "metadata": {"source": "doc1", "page": 1}},
{"text": "文档二内容", "metadata": {"source": "doc2", "page": 2}},
]
}
with open("/data/user/work/sample_meta.json", "w", encoding="utf-8") as f:
json.dump(data_with_meta, f, ensure_ascii=False, indent=2)
loader_meta = JSONLoader(
file_path="/data/user/work/sample_meta.json",
jq_schema=".documents[]",
content_key="text",
metadata_key="metadata", # 使用 metadata 字段作为文档元数据
)
docs_meta = loader_meta.load()
for doc in docs_meta:
print(f"内容: {doc.page_content} | 元数据: {doc.metadata}")
DirectoryLoader(递归加载目录)
python
"""
DirectoryLoader 完整参数 Demo
"""
from langchain_community.document_loaders import (
DirectoryLoader,
TextLoader,
PyPDFLoader,
)
from langchain_community.document_loaders.csv_loader import CSVLoader
# ==========================================
# 基本用法:加载目录下所有文件
# ==========================================
loader = DirectoryLoader(
path="/data/user/work", # 目录路径(必需)
glob="**/*.txt", # 文件匹配模式,默认为 "**/*"
# glob 支持的模式:
# "*.txt" → 当前目录下的 .txt 文件
# "**/*.txt" → 递归匹配所有 .txt 文件
# "**/*.pdf" → 递归匹配所有 .pdf 文件
# "**/[!.]*" → 排除隐藏文件
loader_cls=TextLoader, # 使用的加载器类(必需)
loader_kwargs={ # 传递给加载器的额外参数
"encoding": "utf-8",
},
silent_errors=False, # 是否忽略加载错误,默认为 False
use_multithreading=False, # 是否使用多线程加载,默认为 False
max_concurrency=4, # 最大并发数(多线程时)
show_progress=True, # 是否显示进度条,默认为 False
)
documents = loader.load()
print(f"共加载 {len(documents)} 个文档")
# ==========================================
# 加载多种文件类型
# ==========================================
# 方式一:分别加载不同类型
txt_loader = DirectoryLoader(
path="/data/user/work",
glob="**/*.txt",
loader_cls=TextLoader,
loader_kwargs={"encoding": "utf-8"}
)
csv_loader = DirectoryLoader(
path="/data/user/work",
glob="**/*.csv",
loader_cls=CSVLoader,
)
# 合并所有文档
all_docs = txt_loader.load() + csv_loader.load()
print(f"共加载 {len(all_docs)} 个文档")
# ==========================================
# 递归加载子目录
# ==========================================
loader_recursive = DirectoryLoader(
path="/data/user/work",
glob="**/*.txt", # ** 表示递归匹配所有子目录
loader_cls=TextLoader,
silent_errors=True, # 忽略无法加载的文件
show_progress=True,
)
docs = loader_recursive.load()
# ==========================================
# 使用自定义加载器映射
# ==========================================
from langchain_community.document_loaders import DirectoryLoader
# 通过文件扩展名自动选择加载器
import os
def get_loader(file_path):
"""根据文件扩展名返回对应的加载器"""
ext = os.path.splitext(file_path)[1].lower()
if ext == ".txt":
return TextLoader(file_path, encoding="utf-8")
elif ext == ".csv":
return CSVLoader(file_path)
elif ext == ".pdf":
return PyPDFLoader(file_path)
else:
return TextLoader(file_path, encoding="utf-8")
# 手动遍历目录
def load_directory(path):
documents = []
for root, dirs, files in os.walk(path):
for file in files:
file_path = os.path.join(root, file)
try:
loader = get_loader(file_path)
docs = loader.load()
documents.extend(docs)
except Exception as e:
print(f"加载失败: {file_path} - {e}")
return documents
docs = load_directory("/data/user/work")
RecursiveUrlLoader(递归爬取网站)
python
"""
RecursiveUrlLoader 完整参数 Demo
安装: pip install beautifulsoup4
"""
from langchain_community.document_loaders import RecursiveUrlLoader
# ==========================================
# 基本用法
# ==========================================
from bs4 import BeautifulSoup
# 定义提取正文的函数
def extract_content(html):
"""从 HTML 中提取正文内容"""
soup = BeautifulSoup(html, "html.parser")
# 移除 script 和 style 标签
for tag in soup(["script", "style", "nav", "footer", "header"]):
tag.decompose()
return soup.get_text(separator="\n", strip=True)
loader = RecursiveUrlLoader(
url="https://docs.python.org/3/tutorial/", # 起始 URL(必需)
# 最大递归深度,默认为 2
max_depth=2,
# 是否使用超链接提取器,默认为 True
extractor=None,
# extractor 参数可以自定义提取逻辑
# 防止爬取外部链接
prevent_outside=True, # 默认为 True,只爬取同一域名下的链接
# 使用自定义的 HTML 解析函数
extractor_kwargs={
"extract": extract_content, # 自定义内容提取函数
},
# 请求之间的间隔(秒),防止被封
timeout=10, # 请求超时时间
# 限制最大文档数
# max_documents=50,
# 是否继续出错时继续
continue_on_failure=True,
# 请求头
headers={
"User-Agent": "Mozilla/5.0 (compatible; LangChain Tutorial)"
},
)
documents = loader.load()
print(f"共爬取 {len(documents)} 个页面")
for doc in documents[:3]:
print(f"来源: {doc.metadata.get('source', 'unknown')}")
print(f"内容长度: {len(doc.page_content)} 字符")
print("---")
自定义加载器 Demo
python
"""
自定义 Document Loader Demo
"""
from typing import List, Iterator, Optional
from langchain_core.documents import Document
from langchain_community.document_loaders.base import BaseLoader
class MyCustomLoader(BaseLoader):
"""
自定义文档加载器
继承 BaseLoader 并实现 lazy_load 方法即可创建自定义加载器。
"""
def __init__(
self,
data_source: str,
encoding: str = "utf-8",
metadata: Optional[dict] = None,
):
"""
参数:
data_source: 数据源标识(可以是文件路径、URL、数据库连接等)
encoding: 文本编码
metadata: 附加到所有文档的元数据
"""
self.data_source = data_source
self.encoding = encoding
self.metadata = metadata or {}
def lazy_load(self) -> Iterator[Document]:
"""
惰性加载文档(必须实现)
返回一个 Document 对象的迭代器。
这是 BaseLoader 要求实现的唯一方法。
"""
# 示例:从自定义数据源读取数据
# 这里模拟从 API 获取数据
raw_data = self._fetch_data()
for item in raw_data:
doc = Document(
page_content=item["content"],
metadata={
**self.metadata,
"source": self.data_source,
"id": item.get("id"),
"timestamp": item.get("timestamp"),
}
)
yield doc
def _fetch_data(self) -> list:
"""模拟从数据源获取数据"""
return [
{"id": "1", "content": "第一条数据内容", "timestamp": "2026-01-01"},
{"id": "2", "content": "第二条数据内容", "timestamp": "2026-01-02"},
{"id": "3", "content": "第三条数据内容", "timestamp": "2026-01-03"},
]
# ==========================================
# 使用自定义加载器
# ==========================================
loader = MyCustomLoader(
data_source="my_api_endpoint",
metadata={"project": "tutorial", "version": "1.0"}
)
# 惰性加载
for doc in loader.lazy_load():
print(f"ID: {doc.metadata['id']} | 内容: {doc.page_content}")
# 一次性加载
documents = loader.load()
print(f"\n共加载 {len(documents)} 个文档")
# ==========================================
# 自定义文件加载器示例
# ==========================================
class LogFileLoader(BaseLoader):
"""日志文件加载器 ------ 按日志条目分割"""
def __init__(self, file_path: str, pattern: str = r"^\[\d{4}-\d{2}-\d{2}"):
self.file_path = file_path
self.pattern = pattern
def lazy_load(self) -> Iterator[Document]:
import re
with open(self.file_path, "r", encoding="utf-8") as f:
content = f.read()
# 按日期分割日志条目
entries = re.split(r"(?=\[\d{4}-\d{2}-\d{2})", content)
entries = [e.strip() for e in entries if e.strip()]
for i, entry in enumerate(entries):
yield Document(
page_content=entry,
metadata={
"source": self.file_path,
"entry_index": i,
"entry_type": "log",
}
)
7.3 Text Splitters(文本分割器)
为什么需要分割
大语言模型有上下文窗口限制(Token 限制),无法一次性处理超长文本。文本分割器将长文档切分成较小的块(Chunk),以便:
- 适配模型的输入限制
- 提高检索精度(更小的块意味着更精确的语义匹配)
- 降低 API 调用成本
#mermaid-svg-6yb8zShncVVhTOF5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6yb8zShncVVhTOF5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6yb8zShncVVhTOF5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6yb8zShncVVhTOF5 .error-icon{fill:#552222;}#mermaid-svg-6yb8zShncVVhTOF5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6yb8zShncVVhTOF5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6yb8zShncVVhTOF5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6yb8zShncVVhTOF5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6yb8zShncVVhTOF5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6yb8zShncVVhTOF5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6yb8zShncVVhTOF5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6yb8zShncVVhTOF5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6yb8zShncVVhTOF5 .marker.cross{stroke:#333333;}#mermaid-svg-6yb8zShncVVhTOF5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6yb8zShncVVhTOF5 p{margin:0;}#mermaid-svg-6yb8zShncVVhTOF5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6yb8zShncVVhTOF5 .cluster-label text{fill:#333;}#mermaid-svg-6yb8zShncVVhTOF5 .cluster-label span{color:#333;}#mermaid-svg-6yb8zShncVVhTOF5 .cluster-label span p{background-color:transparent;}#mermaid-svg-6yb8zShncVVhTOF5 .label text,#mermaid-svg-6yb8zShncVVhTOF5 span{fill:#333;color:#333;}#mermaid-svg-6yb8zShncVVhTOF5 .node rect,#mermaid-svg-6yb8zShncVVhTOF5 .node circle,#mermaid-svg-6yb8zShncVVhTOF5 .node ellipse,#mermaid-svg-6yb8zShncVVhTOF5 .node polygon,#mermaid-svg-6yb8zShncVVhTOF5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6yb8zShncVVhTOF5 .rough-node .label text,#mermaid-svg-6yb8zShncVVhTOF5 .node .label text,#mermaid-svg-6yb8zShncVVhTOF5 .image-shape .label,#mermaid-svg-6yb8zShncVVhTOF5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-6yb8zShncVVhTOF5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6yb8zShncVVhTOF5 .rough-node .label,#mermaid-svg-6yb8zShncVVhTOF5 .node .label,#mermaid-svg-6yb8zShncVVhTOF5 .image-shape .label,#mermaid-svg-6yb8zShncVVhTOF5 .icon-shape .label{text-align:center;}#mermaid-svg-6yb8zShncVVhTOF5 .node.clickable{cursor:pointer;}#mermaid-svg-6yb8zShncVVhTOF5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6yb8zShncVVhTOF5 .arrowheadPath{fill:#333333;}#mermaid-svg-6yb8zShncVVhTOF5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6yb8zShncVVhTOF5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6yb8zShncVVhTOF5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6yb8zShncVVhTOF5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6yb8zShncVVhTOF5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6yb8zShncVVhTOF5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6yb8zShncVVhTOF5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6yb8zShncVVhTOF5 .cluster text{fill:#333;}#mermaid-svg-6yb8zShncVVhTOF5 .cluster span{color:#333;}#mermaid-svg-6yb8zShncVVhTOF5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6yb8zShncVVhTOF5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6yb8zShncVVhTOF5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-6yb8zShncVVhTOF5 .icon-shape,#mermaid-svg-6yb8zShncVVhTOF5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6yb8zShncVVhTOF5 .icon-shape p,#mermaid-svg-6yb8zShncVVhTOF5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6yb8zShncVVhTOF5 .icon-shape .label rect,#mermaid-svg-6yb8zShncVVhTOF5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6yb8zShncVVhTOF5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6yb8zShncVVhTOF5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6yb8zShncVVhTOF5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始长文档
10000+ 字符
文本分割器
Chunk 1
~500 字符
Chunk 2
~500 字符
Chunk 3
~500 字符
Chunk N
~500 字符
嵌入向量
向量数据库
RecursiveCharacterTextSplitter(所有参数 + 中文优化配置)
这是最常用的文本分割器,它按照字符列表的优先级递归地分割文本。
python
"""
RecursiveCharacterTextSplitter 完整参数 Demo
"""
from langchain_text_splitters import RecursiveCharacterTextSplitter
# ==========================================
# 基本用法
# ==========================================
text_splitter = RecursiveCharacterTextSplitter(
# --- 核心参数 ---
chunk_size=1000, # 每个块的最大字符数(默认 4000)
# 建议值: 500-2000(取决于应用场景)
chunk_overlap=200, # 块之间的重叠字符数(默认 200)
# 重叠确保语义不会在边界处断裂
# 通常设为 chunk_size 的 10%-20%
length_function=len, # 计算长度的函数(默认 len)
# 可替换为 token 计数函数
# --- 分隔符参数 ---
separators=None, # 分隔符优先级列表(默认如下)
# 默认值: ["\n\n", "\n", " ", ""]
# 按优先级从高到低尝试分割
# --- 其他参数 ---
keep_separator=True, # 是否在分割后保留分隔符(默认 True)
strip_whitespace=True, # 是否去除每个块首尾的空白(默认 True)
is_separator_regex=False, # 分隔符是否为正则表达式(默认 False)
)
# ==========================================
# 中文优化配置(重要!)
# ==========================================
# 中文文本没有空格分隔,需要特殊处理
chinese_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 中文每个字符都有意义,chunk_size 可以小一些
chunk_overlap=50, # 中文重叠可以少一些
# 中文友好的分隔符列表
separators=[
"\n\n", # 段落分隔(最高优先级)
"\n", # 行分隔
"。", # 中文句号
"!", # 中文感叹号
"?", # 中文问号
";", # 中文分号
",", # 中文逗号
"、", # 中文顿号
" ", # 空格
"", # 字符级分割(最低优先级)
],
length_function=len,
keep_separator=True,
strip_whitespace=True,
)
# ==========================================
# 分割中文文本 Demo
# ==========================================
chinese_text = """
人工智能(Artificial Intelligence,简称AI)是计算机科学的一个分支,致力于开发能够模拟人类智能的系统。
自1956年达特茅斯会议以来,人工智能经历了多次发展浪潮。
机器学习是人工智能的核心技术之一。它通过数据驱动的方式,让计算机自动从数据中学习规律和模式。
常见的机器学习算法包括线性回归、决策树、支持向量机和神经网络。
深度学习是机器学习的一个子领域,使用多层神经网络来学习数据的层次化表示。
卷积神经网络(CNN)在图像识别领域取得了突破性成果,而循环神经网络(RNN)则在自然语言处理方面表现出色。
大语言模型(LLM)是近年来最重要的AI突破之一。以GPT、BERT为代表的预训练语言模型,
通过在海量文本数据上进行预训练,获得了强大的语言理解和生成能力。
Transformer架构是大语言模型的基础。它引入了自注意力机制(Self-Attention),
能够有效捕捉文本中的长距离依赖关系,极大地提升了模型性能。
"""
chunks = chinese_splitter.split_text(chinese_text)
print(f"分割为 {len(chunks)} 个块")
for i, chunk in enumerate(chunks):
print(f"\n--- 块 {i+1} (长度: {len(chunk)}) ---")
print(chunk)
# ==========================================
# 分割 Document 对象列表
# ==========================================
from langchain_core.documents import Document
documents = [
Document(page_content="第一段很长的文本..." * 100, metadata={"source": "doc1.txt"}),
Document(page_content="第二段很长的文本..." * 100, metadata={"source": "doc2.txt"}),
]
split_docs = chinese_splitter.split_documents(documents)
print(f"\n原始文档数: {len(documents)}")
print(f"分割后文档数: {len(split_docs)}")
for doc in split_docs[:3]:
print(f"长度: {len(doc.page_content)} | 来源: {doc.metadata['source']}")
# ==========================================
# 使用 Token 计数作为长度函数
# ==========================================
# 方式一:使用 tiktoken(OpenAI 的 tokenizer)
# pip install tiktoken
try:
import tiktoken
def token_length(text: str) -> int:
"""使用 tiktoken 计算文本的 token 数"""
encoding = tiktoken.encoding_for_model("gpt-4")
return len(encoding.encode(text))
token_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 500 tokens
chunk_overlap=50, # 50 tokens 重叠
length_function=token_length, # 使用 token 计数
separators=["\n\n", "\n", " ", ""],
)
chunks = token_splitter.split_text(chinese_text)
print(f"\n按 Token 分割: {len(chunks)} 个块")
except ImportError:
print("tiktoken 未安装,跳过 Token 分割 Demo")
# ==========================================
# 从元数据创建分割器
# ==========================================
splitter_from_meta = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
encoding_name="cl100k_base", # tiktoken 编码名称
chunk_size=500,
chunk_overlap=50,
)
CharacterTextSplitter
python
"""
CharacterTextSplitter Demo
"""
from langchain_text_splitters import CharacterTextSplitter
# CharacterTextSplitter 按单个分隔符分割文本
# 适合结构明确的文本(如以换行符分隔的条目)
splitter = CharacterTextSplitter(
separator="\n", # 分隔符(默认为 "\n\n")
chunk_size=1000, # 每个块的最大字符数
chunk_overlap=200, # 块之间的重叠字符数
length_function=len, # 长度计算函数
keep_separator=True, # 是否保留分隔符
strip_whitespace=True, # 是否去除空白
is_separator_regex=False, # 分隔符是否为正则
)
text = """第一行内容
第二行内容
第三行内容
第四行内容
第五行内容"""
chunks = splitter.split_text(text)
print(f"分割为 {len(chunks)} 个块")
for chunk in chunks:
print(f" -> {chunk.strip()}")
# ==========================================
# 使用正则分隔符
# ==========================================
regex_splitter = CharacterTextSplitter(
separator=r"\d+\.", # 正则:匹配数字+点号(如 "1.", "2.")
chunk_size=500,
chunk_overlap=0,
is_separator_regex=True, # 启用正则模式
)
numbered_text = """1. 第一个要点内容
2. 第二个要点内容
3. 第三个要点内容
4. 第四个要点内容"""
chunks = regex_splitter.split_text(numbered_text)
for chunk in chunks:
print(f" -> {chunk.strip()}")
TokenTextSplitter
python
"""
TokenTextSplitter Demo
安装: pip install tiktoken
"""
from langchain_text_splitters import TokenTextSplitter
# TokenTextSplitter 按 Token 数量分割
# 确保每个块不超过模型的 Token 限制
splitter = TokenTextSplitter(
chunk_size=100, # 每个块的最大 Token 数
chunk_overlap=20, # 块之间的 Token 重叠数
# 指定使用的 tokenizer 编码
encoding_name="cl100k_base", # 默认值,适用于 GPT-4/GPT-3.5
# 其他可选值:
# "p50k_base" → 旧版 GPT 模型
# "r50k_base" → 最早的 GPT 模型
# "o200k_base" → GPT-4o 模型
# 也可以直接传入 tokenizer
# tokenizer=None, # 自定义 tokenizer 对象
# 是否将分隔符计入 Token 数
disallowed_special=set(), # 不允许的特殊 token 集合
)
text = "这是一段用于测试 Token 分割的文本。" * 50
try:
chunks = splitter.split_text(text)
print(f"分割为 {len(chunks)} 个块")
for i, chunk in enumerate(chunks):
print(f"块 {i+1}: {chunk[:50]}...")
except Exception as e:
print(f"需要安装 tiktoken: pip install tiktoken")
MarkdownHeaderTextSplitter(按标题分割)
python
"""
MarkdownHeaderTextSplitter Demo
"""
from langchain_text_splitters import MarkdownHeaderTextSplitter
# MarkdownHeaderTextSplitter 按 Markdown 标题层级分割文档
# 每个块的元数据中会包含其所属的标题层级信息
# 定义要分割的标题层级
headers_to_split_on = [
("#", "h1"), # 一级标题
("##", "h2"), # 二级标题
("###", "h3"), # 三级标题
("####", "h4"), # 四级标题
]
splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers_to_split_on,
# 是否在分割时返回每一行
return_each_line=False, # 默认 False
# 是否去除分割后的空白
strip_headers=False, # 默认 False
)
markdown_text = """
# LangChain 教程
## 概述
LangChain 是一个用于开发大语言模型应用的框架。
### 安装
使用 pip 安装 LangChain:
pip install langchain
### 快速开始
以下是一个简单的示例代码。
## 核心概念
### 模型(Models)
LangChain 支持多种大语言模型。
### 提示(Prompts)
提示模板用于管理提示词。
### 链(Chains)
链用于组合多个组件。
## 进阶主题
### RAG
检索增强生成技术。
"""
chunks = splitter.split_text(markdown_text)
print(f"分割为 {len(chunks)} 个块\n")
for i, chunk in enumerate(chunks):
print(f"--- 块 {i+1} ---")
print(f"元数据(标题层级): {chunk.metadata}")
print(f"内容: {chunk.page_content[:100]}...")
print()
# ==========================================
# 结合 RecursiveCharacterTextSplitter 使用
# ==========================================
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 先按标题分割,再对过长的块进行二次分割
markdown_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=[("#", "h1"), ("##", "h2"), ("###", "h3")]
)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
)
# 第一步:按标题分割
header_chunks = markdown_splitter.split_text(markdown_text)
# 第二步:对每个块进行字符级分割
final_chunks = text_splitter.split_documents(header_chunks)
print(f"最终分割为 {len(final_chunks)} 个块")
HTMLHeaderTextSplitter
python
"""
HTMLHeaderTextSplitter Demo
"""
from langchain_text_splitters import HTMLHeaderTextSplitter
# HTMLHeaderTextSplitter 按 HTML 标题标签分割网页内容
headers_to_split_on = [
("h1", "h1"), # <h1> 标签
("h2", "h2"), # <h2> 标签
("h3", "h3"), # <h3> 标签
("h4", "h4"), # <h4> 标签
]
splitter = HTMLHeaderTextSplitter(
headers_to_split_on=headers_to_split_on,
# 是否返回每个元素
return_each_element=False, # 默认 False
)
html_text = """
<html>
<body>
<h1>公司介绍</h1>
<p>我们是一家专注于人工智能技术的公司。</p>
<h2>产品线</h2>
<h3>智能客服</h3>
<p>基于大语言模型的智能客服系统。</p>
<h3>数据分析</h3>
<p>自动化的数据分析平台。</p>
<h2>联系方式</h2>
<p>邮箱: contact@example.com</p>
</body>
</html>
"""
chunks = splitter.split_text(html_text)
for chunk in chunks:
print(f"元数据: {chunk.metadata}")
print(f"内容: {chunk.page_content.strip()[:80]}...")
print()
RecursiveJsonSplitter
python
"""
RecursiveJsonSplitter Demo
"""
import json
from langchain_text_splitters import RecursiveJsonSplitter
# RecursiveJsonSplitter 按照 JSON 结构递归分割
splitter = RecursiveJsonSplitter(
max_chunk_size=300, # 每个块的最大字符数
)
json_data = {
"公司": {
"名称": "示例科技",
"成立年份": 2020,
"部门": {
"技术部": {
"人数": 50,
"负责人": "张三",
"团队": ["前端组", "后端组", "AI组"]
},
"市场部": {
"人数": 20,
"负责人": "李四",
"团队": ["品牌组", "推广组"]
}
}
},
"产品": [
{"名称": "产品A", "价格": 99, "描述": "基础版"},
{"名称": "产品B", "价格": 199, "描述": "专业版"},
]
}
# 分割 JSON 数据
chunks = splitter.split_json(json_data, min_chunk_size=50)
print(f"分割为 {len(chunks)} 个块")
for i, chunk in enumerate(chunks):
print(f"\n--- 块 {i+1} ---")
print(json.dumps(chunk, ensure_ascii=False, indent=2))
# ==========================================
# 将 JSON 分割为 Document 对象
# ==========================================
docs = splitter.create_documents(texts=[json_data])
for doc in docs:
print(f"内容长度: {len(doc.page_content)}")
print(f"内容: {doc.page_content[:100]}...")
分割策略对比表格
| 分割器 | 分割依据 | 适用场景 | 中文支持 | 保留结构 |
|---|---|---|---|---|
RecursiveCharacterTextSplitter |
多级分隔符递归 | 通用场景,推荐首选 | 需配置分隔符 | 部分 |
CharacterTextSplitter |
单一分隔符 | 结构明确的文本 | 需配置分隔符 | 部分 |
TokenTextSplitter |
Token 数量 | 需精确控制 Token | 一般 | 否 |
MarkdownHeaderTextSplitter |
Markdown 标题 | Markdown 文档 | 好 | 好 |
HTMLHeaderTextSplitter |
HTML 标题标签 | HTML 网页 | 好 | 好 |
RecursiveJsonSplitter |
JSON 结构层级 | JSON 数据 | 好 | 好 |
分割质量评估 Demo
python
"""
文本分割质量评估 Demo
"""
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
def evaluate_split_quality(chunks, min_size=100, max_size=2000, target_size=500):
"""
评估文本分割质量
参数:
chunks: 分割后的文本块列表
min_size: 最小可接受大小
max_size: 最大可接受大小
target_size: 目标大小
"""
print("=" * 60)
print("分割质量评估报告")
print("=" * 60)
sizes = [len(chunk) for chunk in chunks]
avg_size = sum(sizes) / len(sizes) if sizes else 0
# 1. 基本信息
print(f"\n基本信息:")
print(f" 总块数: {len(chunks)}")
print(f" 平均块大小: {avg_size:.0f} 字符")
print(f" 最小块: {min(sizes)} 字符")
print(f" 最大块: {max(sizes)} 字符")
print(f" 标准差: {(sum((s - avg_size)**2 for s in sizes) / len(sizes))**0.5:.0f}")
# 2. 质量指标
print(f"\n质量指标:")
# 过小块比例
too_small = sum(1 for s in sizes if s < min_size)
print(f" 过小块 (<{min_size}): {too_small}/{len(chunks)} ({too_small/len(chunks)*100:.1f}%)")
# 过大块比例
too_large = sum(1 for s in sizes if s > max_size)
print(f" 过大块 (>{max_size}): {too_large}/{len(chunks)} ({too_large/len(chunks)*100:.1f}%)")
# 块大小分布
print(f"\n块大小分布:")
ranges = [(0, 200), (200, 500), (500, 1000), (1000, 2000), (2000, float('inf'))]
for low, high in ranges:
count = sum(1 for s in sizes if low <= s < high)
label = f"{low}-{high}" if high != float('inf') else f"{low}+"
bar = "#" * int(count / len(chunks) * 50) if chunks else ""
print(f" {label:>10}: {count:>3} ({count/len(chunks)*100:>5.1f}%) {bar}")
# 3. 建议
print(f"\n优化建议:")
if too_small > len(chunks) * 0.1:
print(f" [!] 过小块比例较高,建议增大 chunk_size 或减小 chunk_overlap")
if too_large > 0:
print(f" [!] 存在过大块,建议减小 chunk_size")
if avg_size < target_size * 0.5:
print(f" [!] 平均块大小远小于目标,建议增大 chunk_size")
if avg_size > target_size * 1.5:
print(f" [!] 平均块大小远大于目标,建议减小 chunk_size")
# 测试评估
text = "这是一段测试文本。" * 200
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", " ", ""],
)
chunks = splitter.split_text(text)
evaluate_split_quality(chunks, target_size=500)
7.4 文档处理最佳实践
python
"""
文档处理最佳实践总结
"""
# ==========================================
# 1. 选择合适的加载器
# ==========================================
"""
- 纯文本 → TextLoader
- PDF → PyPDFLoader(简单)/ PyMuPDFLoader(高级)
- Word → Docx2txtLoader
- Markdown → UnstructuredMarkdownLoader
- 网页 → WebBaseLoader
- CSV → CSVLoader
- JSON → JSONLoader
- 多种文件混合 → DirectoryLoader + 自定义映射
"""
# ==========================================
# 2. 中文文本分割推荐配置
# ==========================================
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 通用中文分割器
CHINESE_SPLITTER = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", "、", " ", ""],
length_function=len,
keep_separator=True,
)
# 精细中文分割器(用于需要高精度检索的场景)
CHINESE_FINE_SPLITTER = RecursiveCharacterTextSplitter(
chunk_size=200,
chunk_overlap=30,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""],
)
# 粗粒度中文分割器(用于需要更多上下文的场景)
CHINESE_COARSE_SPLITTER = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=100,
separators=["\n\n", "\n", "。", " ", ""],
)
# ==========================================
# 3. 完整的文档处理流水线
# ==========================================
def process_documents(file_path: str) -> list:
"""
完整的文档处理流水线
流程: 加载 → 清洗 → 分割 → 返回
"""
from langchain_community.document_loaders import TextLoader
import re
# 第一步:加载文档
loader = TextLoader(file_path, encoding="utf-8")
documents = loader.load()
# 第二步:清洗文档内容
for doc in documents:
# 去除多余空白
doc.page_content = re.sub(r'\n{3,}', '\n\n', doc.page_content)
# 去除行首行尾空白
doc.page_content = doc.page_content.strip()
# 第三步:分割文档
splitter = CHINESE_SPLITTER
chunks = splitter.split_documents(documents)
# 第四步:添加额外元数据
for i, chunk in enumerate(chunks):
chunk.metadata["chunk_index"] = i
chunk.metadata["chunk_total"] = len(chunks)
return chunks
# ==========================================
# 4. Markdown 文档处理最佳实践
# ==========================================
def process_markdown(file_path: str) -> list:
"""Markdown 文档处理流水线"""
from langchain_community.document_loaders import UnstructuredMarkdownLoader
from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
# 加载
loader = UnstructuredMarkdownLoader(file_path)
docs = loader.load()
# 先按标题分割(保留结构信息)
md_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=[("#", "h1"), ("##", "h2"), ("###", "h3")]
)
header_chunks = md_splitter.split_text(docs[0].page_content)
# 再对过长的块进行字符级分割
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
final_chunks = text_splitter.split_documents(header_chunks)
return final_chunks
# ==========================================
# 5. Chunk 大小选择指南
# =========================================="""
| 场景 | 推荐 chunk_size | 推荐 chunk_overlap | 说明 |
|------|----------------|-------------------|------|
| 问答系统 | 300-500 | 50-100 | 需要精确匹配 |
| 文档摘要 | 1000-2000 | 100-200 | 需要更多上下文 |
| 语义搜索 | 500-1000 | 50-100 | 平衡精度和上下文 |
| RAG 应用 | 500-1000 | 100-200 | 最常用配置 |
| 代码搜索 | 200-500 | 50 | 代码片段较短 |
| 法律文档 | 1000-2000 | 200 | 需要完整段落 |
"""