LangChain 学习笔记 · Day 9 文档切分(TextSplitter)

学习 LangChain 的文档切分策略,掌握 RecursiveCharacterTextSplitter 等常见切分器的使用方法,并完成 PDF/Markdown 等文档的分块 Demo。
20 分钟阅读
LangChainPythonTextSplitterRecursiveCharacterTextSplitterPDFMarkdown

🎯 学习目标

  1. 理解 为什么要进行文档切分
  2. 掌握 LangChain 常见的 切分器(TextSplitter)
  3. 重点学习 RecursiveCharacterTextSplitter 的工作机制
  4. 通过 Demo:把 PDF 切成小块,观察切分效果与参数影响

🔹 为什么要切分?

  • 模型上下文有限:长文档不能直接塞给 LLM,需要拆小块
  • 检索更精准:小块向量化后命中率更高,减少无关噪声
  • 减少割裂:合理的分隔符优先级,能最大程度保留语义边界

🔹 常见切分器对比

Splitter核心思想适用场景优点局限
RecursiveCharacterTextSplitter按分隔符优先级递归切分通用文本(默认首选)保留语义边界,灵活可调极长句可能被拆
CharacterTextSplitter单一分隔符切分简单文本/日志实现简单语义感弱
MarkdownHeaderTextSplitter按 Markdown 标题层级切技术文档结构保真需二次切分控长
HTMLHeaderTextSplitter解析 HTML 标签结构网页文档保留层级结构HTML 噪声需清理
TokenTextSplitter按 token 粒度切严格预算场景与模型上下文对齐可能割裂语义
RecursiveJsonSplitter结构化切分JSON/YAML字段级检索仅适合结构化数据

🔹 RecursiveCharacterTextSplitter 工作机制

  1. 设定分隔符优先级(如:\n\n > \n > 。 > , > 空格 > 空字符
  2. 尝试用最高优先级切分;若仍超长,降级到下一级分隔符继续切
  3. 直到满足 chunk_size 或退化为逐字符切
  4. 最终生成结果时加入 chunk_overlap,减少上下文割裂

🔹 参数含义

  • chunk_size:单个块的最大长度(字符或 token)
  • chunk_overlap:相邻块的重叠部分,保证语义连续性
  • 经验值
    • chunk_size = 800~1200
    • chunk_overlap = 80~200(约 10%~20%)

👉 大块:上下文完整,但可能噪声↑
👉 小块:检索更精准,但上下文容易割裂


🧩 实战代码

1. 基础文本切分

from langchain.text_splitter import RecursiveCharacterTextSplitter

text = "这是一个示例文本。This is an example text.\n```python\ndef add(a, b): return a + b\n```"

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=150,
    separators=["\n```", "\n\n", "\n", "。", "!", "?", ";", ",", ". ", "! ", "? ", ", ", " ", ""],
    add_start_index=True,
    length_function=len
)

docs = splitter.create_documents([text], metadatas=[{"source": "sample"}])
print(len(docs), docs[0].metadata, docs[0].page_content[:80])

2. PDF → 分块

from langchain_community.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

pdf_path = "docs/sample.pdf"
pages = PyPDFLoader(pdf_path).load()

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=150,
    separators=["\n```", "\n\n", "\n", "。", "!", "?", ";", ",", " ", ""],
    add_start_index=True
)

chunks = splitter.split_documents(pages)
print(f"pages={len(pages)}, chunks={len(chunks)}")
print(chunks[0].metadata, chunks[0].page_content[:100])

3. Markdown 标题 + 二次 RCTS

from langchain.text_splitter import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter

md_text = """
# 一、概览
内容 A
## 1.1 背景
内容 B
"""

headers = [("#", "h1"), ("##", "h2")]
md_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers)
sections = md_splitter.split_text(md_text)

rcts = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=120)
chunks = rcts.split_documents(sections)

print(chunks[0].metadata, chunks[0].page_content[:80])

4. Token 粒度切分

from langchain.text_splitter import TokenTextSplitter

tok_splitter = TokenTextSplitter(
    encoding_name="cl100k_base",
    chunk_size=800,
    chunk_overlap=120
)
tok_chunks = tok_splitter.split_text("一大段长文本……")
print(len(tok_chunks), tok_chunks[0][:80])

✅ 今日收获

  • 掌握了 为什么要切分,理解 chunk 与 overlap 的作用
  • 熟悉了 LangChain 提供的主要切分器,尤其是 RecursiveCharacterTextSplitter
  • 能够实际运行 PDF / Markdown / Token 的切分 Demo
  • 为 Day 10 的 向量化存储 做好准备

⏭️ Day 10 展望

  • 学习 Embedding + 向量数据库(Chroma)
  • Demo:把切分后的 chunks 存入向量库,支持相似度检索