🤖 RAG 文档分割器(Splitter)深度学习笔记 —— 从零搭建知识库文档处理流水线

🤖 RAG 文档分割器(Splitter)深度学习笔记 ------ 从零搭建知识库文档处理流水线

前言 :在 AI 大模型时代,RAG(Retrieval-Augmented Generation,检索增强生成)已经成为构建私有知识库问答系统的核心架构。而 RAG 系统的"地基"是什么?是文档处理 ------如何把五花八门的知识来源(网页、PDF、Word、视频字幕......)变成向量数据库能检索的、具有完整语义的文档块(Chunk)。本文将以一个实际项目为蓝本,从 0 到 1 彻底搞懂文档加载、文本分割、语义保持的全链路,让你写出的 RAG 系统不再是"能跑就行",而是真正语义精准、检索高效


📋 目录

  1. 项目概览与核心依赖
  2. [知识库的数据源:从文件到 Document](#知识库的数据源:从文件到 Document "#2-%E7%9F%A5%E8%AF%86%E5%BA%93%E7%9A%84%E6%95%B0%E6%8D%AE%E6%BA%90%E4%BB%8E%E6%96%87%E4%BB%B6%E5%88%B0-document")
  3. [网页爬虫实战:Cheerio + CSS 选择器精准提取内容](#网页爬虫实战:Cheerio + CSS 选择器精准提取内容 "#3-%E7%BD%91%E9%A1%B5%E7%88%AC%E8%99%AB%E5%AE%9E%E6%88%98cheerio--css-%E9%80%89%E6%8B%A9%E5%99%A8%E7%B2%BE%E5%87%86%E6%8F%90%E5%8F%96%E5%86%85%E5%AE%B9")
  4. [LangChain 文档加载器:180+ Loader 生态](#LangChain 文档加载器:180+ Loader 生态 "#4-langchain-%E6%96%87%E6%A1%A3%E5%8A%A0%E8%BD%BD%E5%99%A8180-loader-%E7%94%9F%E6%80%81")
  5. [文本分割的核心:RecursiveCharacterTextSplitter 深度剖析](#文本分割的核心:RecursiveCharacterTextSplitter 深度剖析 "#5-%E6%96%87%E6%9C%AC%E5%88%86%E5%89%B2%E7%9A%84%E6%A0%B8%E5%BF%83recursivecharactertextsplitter-%E6%B7%B1%E5%BA%A6%E5%89%96%E6%9E%90")
  6. [语义完整性:chunkSize、separators、chunkOverlap 黄金三角](#语义完整性:chunkSize、separators、chunkOverlap 黄金三角 "#6-%E8%AF%AD%E4%B9%89%E5%AE%8C%E6%95%B4%E6%80%A7chunksizeseparatorschunkoverlap-%E9%BB%84%E9%87%91%E4%B8%89%E8%A7%92")
  7. 完整代码解读
  8. [AI 时代程序员的角色转变:从 Coder 到 AI 架构师](#AI 时代程序员的角色转变:从 Coder 到 AI 架构师 "#8-ai-%E6%97%B6%E4%BB%A3%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E8%A7%92%E8%89%B2%E8%BD%AC%E5%8F%98%E4%BB%8E-coder-%E5%88%B0-ai-%E6%9E%B6%E6%9E%84%E5%B8%88")
  9. 总结与展望

1. 项目概览与核心依赖

📦 项目结构

bash 复制代码
rag_splitter/
├── src/
│   ├── crawl.mjs      # 原生爬虫:axios + cheerio 手写版
│   └── index.mjs      # LangChain 封装版:Loader + Splitter
├── .env                # 环境变量(模型配置)
├── package.json
├── pnpm-lock.yaml
└── readme.md

🔧 核心依赖一览

依赖包 版本 用途
@langchain/community ^1.1.29 社区维护的 180+ 文档加载器
@langchain/textsplitters ^1.0.1 LangChain 官方文本分割器
axios ^1.18.1 HTTP 请求库,获取网页 HTML
cheerio ^1.2.0 服务端 jQuery,CSS 选择器解析 HTML
dotenv ^17.4.2 加载 .env 环境变量

💡 小贴士 :LangChain 生态分为两块------@langchain/core(官方核心库)和 @langchain/community(社区贡献库)。社区库拥有 180+ 种 Loader,几乎覆盖了你能想到的所有文件格式,而且你也可以贡献自己的 Loader


2. 知识库的数据源:从文件到 Document

🎯 核心问题

构建知识库时,知识的来源极其丰富:

  • 📄 Word 文档(.docx)
  • 📕 PDF 文件(.pdf)
  • 🎬 B站视频字幕
  • 🌐 网页 URL
  • 🐦 Twitter 推文
  • 📝 Markdown 文件

这些格式各异的内容,无法直接进行向量化 。我们需要一个统一的中间表示------Document 对象

📐 Document:LangChain 的标准文档格式

javascript 复制代码
// LangChain Document 的标准结构
{
  pageContent: "这是文档的文本内容...",  // 页面内容:纯文本
  metadata: {                            // 元数据:来源信息
    source: "https://example.com",       // 来源 URL
    title: "文章标题",                    // 标题
    date: "2026-07-16",                  // 日期
    // ... 任意自定义字段
  }
}

🔑 关键认知:Document 是 RAG 流水线的"通用货币"------Loader 负责把各种格式转成 Document,Splitter 负责把大 Document 切分成小 Document,Embedding 负责把小 Document 向量化存入数据库。

🔄 数据处理流程

scss 复制代码
┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
│ 原始文件  │ -> │  Loader  │ -> │ Splitter │ -> │ 向量存储  │
│ (多格式)  │    │ (加载)   │    │ (分割)   │    │ (检索)   │
└──────────┘    └──────────┘    └──────────┘    └──────────┘
                 两件事要做:
                 ① 选择正确的 Loader
                 ② 合理分块(chunk)

3. 网页爬虫实战:Cheerio + CSS 选择器精准提取内容

🕷️ 传统爬虫的痛点

从网页提取内容,传统做法分两步:

  1. 发送 HTTP 请求 → 拿到 HTML 字符串(axios 轻松搞定 ✅)
  2. 解析 HTML 提取文本 → 用正则表达式?(地狱难度 ❌)

HTML 的嵌套结构让正则表达式力不从心------一个 <div> 嵌套若干层 <p><span>,正则要么漏内容,要么错匹配。

💡 Cheerio 的破局思路

Cheerio 让后端开发者也能用前端思维操作 HTML!

javascript 复制代码
import * as cheerio from 'cheerio';

const $ = cheerio.load(html);       // ① 在内存中构建虚拟 DOM 树
const content = $('.main-area p').text();  // ② 用 CSS 选择器精准提取!

它的底层逻辑是:

css 复制代码
HTML 字符串 → 解析为 DOM 树结构 → CSS Selector 遍历树 → 返回匹配节点

这个过程不需要浏览器环境,纯 Node.js 内存操作,高效轻量。

📝 完整爬虫实现 crawl.mjs

javascript 复制代码
import axios from 'axios';
import * as cheerio from 'cheerio';

const targetUrl = 'https://juejin.cn/post/7660707431753678854';

async function crawlPage() {
    try {
        // Step 1: 发送 HTTP 请求,获取 HTML 字符串
        const { data: html } = await axios.get(targetUrl);
        console.log(html);  // 完整的 HTML 字符串

        // Step 2: cheerio.load() 在内存中构建虚拟 DOM
        //         这是关键------申请一块大的内存空间,构建树状结构
        const $ = cheerio.load(html);

        // Step 3: CSS 选择器精准提取目标内容
        //         就像写前端 jQuery 一样简单!
        const pageContent = $('.main-area p').text();
        console.log(pageContent);
    } catch (err) {
        console.log(err);
    }
}

crawlPage();

🎨 为什么 Cheerio 比正则好?

对比维度 正则表达式 Cheerio
学习曲线 陡峭(各种模式匹配) 平缓(前端都会 CSS 选择器)
可读性 差(/<div[^>]*>(.*?)<\/div>/gs 好(.main-area p 一目了然)
嵌套处理 极易出错 自动处理 DOM 树层级
维护成本 高(HTML 结构一变正则就失效) 低(选择器改一行即可)

🎯 核心价值 :Cheerio 让 JS 开发者用前端思维(CSS 选择器),简单高效地完成指定 URL、指定部分的爬取工作,完全不用碰正则表达式!


4. LangChain 文档加载器:180+ Loader 生态

🏗️ CheerioWebBaseLoader:爬虫 + Document 标准化一步到位

刚才的 crawl.mjs 手动完成了爬取,但产出的只是"裸字符串",没有形成 LangChain 标准的 Document 对象。LangChain 提供了封装好的 CheerioWebBaseLoader,它内置了 Cheerio 解析能力,并且返回标准的 Document 格式:

javascript 复制代码
import { CheerioWebBaseLoader } from '@langchain/community/document_loaders/web/cheerio';

const cheerioLoader = new CheerioWebBaseLoader(
    'https://juejin.cn/post/7660707431753678854',
    {
        selector: '.main-area p',  // 指定 CSS 选择器,只提取文章段落
    }
);

const documents = await cheerioLoader.load();
// 返回的 documents 是 Document[] 数组,每个元素都有 pageContent 和 metadata

🧩 社区 Loader 生态全景

@langchain/community 提供了 180+ 种 Loader,覆盖几乎所有常见场景:

复制代码
📄 文档类:PDFLoader, DocxLoader, CSVLoader, UnstructuredMarkdownLoader
🌐 网页类:CheerioWebBaseLoader, PuppeteerWebBaseLoader, PlaywrightWebBaseLoader
💬 平台类:TwitterPostLoader, YouTubeLoader, GitHubRepoLoader, NotionLoader
🗄️ 数据库类:MongoDBLoader, MySQLoader, PGLoader
📧 邮件类:GmailLoader, OutlookLoader, IMAPLoader

💡 开源贡献:这些 Loader 主要由社区维护,任何人都可以贡献自己的 Loader。遇到特殊格式?自己写一个 Loader,PR 到社区就行!

🔄 两种方案的对比

scss 复制代码
┌─────────────────────────────────────────────────────────┐
│                 crawl.mjs(手动版)                        │
│  axios → cheerio.load() → $('.selector').text()          │
│  产出:纯文本字符串                                        │
│  优点:理解底层原理,灵活可控                                │
├─────────────────────────────────────────────────────────┤
│                index.mjs(LangChain 封装版)                │
│  CheerioWebBaseLoader → .load()                          │
│  产出:标准 Document[] 对象(pageContent + metadata)       │
│  优点:开箱即用,可直接接入 RAG 流水线                        │
└─────────────────────────────────────────────────────────┘

5. 文本分割的核心:RecursiveCharacterTextSplitter 深度剖析

🤔 为什么要分割?

大文档 → 大 Embedding → 语义"稀释" → 检索不准

向量检索的本质是语义匹配。如果把一整篇文章(几千字)塞进一个向量,这个向量就变成了"什么都说了但又什么都没说清"的平均值。用户问一个具体问题,很难和这个"大杂烩"向量产生高相似度。

分割的目标 :把大文档切分成一个个大小适中、语义完整的小块(chunk),让每个 chunk 都能精准命中用户的查询意图。

🎯 分割的核心矛盾

目标 挑战
语义完整:每个 chunk 应该是一个独立、完整的语义单元 语义边界难以自动识别
大小适中:每个 chunk 应该接近预设的 chunkSize 语义边界和大小边界经常冲突
高效检索:chunk 太多检索变慢,chunk 太少覆盖不全 需要在实际场景中调参平衡

🧠 RecursiveCharacterTextSplitter 的递归算法

这是 LangChain 最核心、最常用的分割器。它的精髓在于"递归尝试不同的分隔符":

markdown 复制代码
算法流程:
1. 按优先级最高的分隔符(如 '。')尝试分割
2. 如果分割后的某段仍然超过 chunkSize?
   → 对该段用下一级分隔符(如 '!')再分割
3. 如果还是太大?
   → 再用下一级(如 '?')
4. 终极兜底:
   → 硬切,然后用 chunkOverlap 来弥补语义损失

📊 图解递归分割过程

less 复制代码
原始文本: "段落A。段落B!段落C?段落D。段落E。段落F。"
chunkSize = 400

第1轮 → 用 '。' 分割:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 段落A    │ │ 段落B!..│ │ 段落E    │ │ 段落F    │
│ (200字)  │ │ (500字)  │ │ (350字)  │ │ (380字)  │
│ ✅ 合格  │ │ ❌ 超标! │ │ ✅ 合格  │ │ ✅ 合格  │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
                    │
                    ▼ 递归!用 '!' 再切
              ┌──────────┐ ┌──────────┐
              │ 段落B    │ │ 段落C?..│
              │ (250字)  │ │ (250字)  │
              │ ✅ 合格  │ │ ✅ 合格  │
              └──────────┘ └──────────┘

🔮 设计哲学:优先保证语义完整性(用更大的语义分隔符),大小控制作为约束条件(递归降级到更小的分隔符),最后用 chunkOverlap 兜底。


6. 语义完整性:chunkSize、separators、chunkOverlap 黄金三角

🔺 三角关系

scss 复制代码
                    chunkSize
                    (大小控制)
                       ▲
                      / \
                     /   \
                    /     \
                   /       \
                  /  语义   \
                 /   完整    \
                /    性       \
               /               \
              ▼                 ▼
      separators          chunkOverlap
      (语义分隔)          (重叠补救)

三者协同工作,缺一不可:

1️⃣ separators(分隔符)------ 语义的"骨架"

分隔符是语义最基本的构成符号。默认是 ["\n\n", "\n", " ", ""](换行→空格→字符),但我们针对中文场景优化:

javascript 复制代码
separators: ['。', '!', '?']
  • '。':句号,最自然的语义边界,优先级最高
  • '!':感叹号,次优先级
  • '?':问号,再次优先级

⚠️ 注意 :英文场景通常用 \n\n(段落)和 \n(行)作为主要分隔符。中文没有天然的空格分词,所以用标点符号更合适。

2️⃣ chunkSize(块大小)------ 语义的"容器"

javascript 复制代码
chunkSize: 400  // 每个 chunk 约 400 个字符
  • 太大(如 2000):语义"稀释",检索精度下降
  • 太小(如 100):语义碎片化,上下文丢失
  • 400 是个经验值:适合大多数中文段落,能承载 2-3 个完整句子

3️⃣ chunkOverlap(重叠区)------ 语义的"桥梁"

javascript 复制代码
chunkOverlap: 100  // 相邻两个 chunk 重叠 100 个字符

这是 RAG 切割中最容易被忽视、但又至关重要的参数:

r 复制代码
Chunk 1: [...A B C D E F G H I J K L M N O P Q R S T U...]
                                            └──┬──┘
                                       重叠区域 (100字符)
                                            ┌──┴──┐
Chunk 2:              [...K L M N O P Q R S T U V W X Y Z...]
🎭 为什么需要重叠?

假设一篇技术文章这样写:

"React 18 引入了并发模式,这是一个革命性的特性。它允许 React 在渲染过程中暂停和恢复工作。 这意味着用户交互永远不会被阻塞。"

如果切割点恰好落在第 2 句中间:

arduino 复制代码
Chunk 1: "React 18 引入了并发模式,这是一个革命性的特性。它允许"
Chunk 2: "React 在渲染过程中暂停和恢复工作。这意味着用户交互永远不会被阻塞。"

Chunk 1 末尾的"它允许"------允许什么?❌ 语义断裂!

有 overlap 之后:

arduino 复制代码
Chunk 1: "React 18 引入了并发模式,这是一个革命性的特性。它允许
          React 在渲染过程中暂停和恢复工作。"  ← 完整语义
Chunk 2: "它允许 React 在渲染过程中暂停和恢复工作。这意味着
          用户交互永远不会被阻塞。"            ← 完整语义

🎯 核心认知 :切割必然造成语义损失。chunkOverlap 不是"消除"损失,而是用一定冗余 来确保每个 chunk 的语义完整性。这是一种以空间换质量的策略。


7. 完整代码解读

📄 index.mjs ------ 完整的 RAG 文档处理流水线

javascript 复制代码
import 'dotenv/config'
import 'cheerio'

// ==================== 第1步:加载文档 ====================
import {
    CheerioWebBaseLoader
} from '@langchain/community/document_loaders/web/cheerio'

// 文本分割器
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'

// 创建 Loader:指定目标 URL 和 CSS 选择器
// selector 可以缩小提取范围,只取文章正文
const cheerioLoader = new CheerioWebBaseLoader(
    'https://juejin.cn/post/7660707431753678854',
    {
        selector: '.main-area p',  // 只提取文章段落部分
    }
)

// 加载 → 得到标准 Document 对象数组
const documents = await cheerioLoader.load()
// console.log(documents)

// ==================== 第2步:分割文档 ====================
// 为什么需要分割?
// - 大 document → 分成小 document → 更精细地处理语义
// - 按段落划分?段落太长 → 语义稀释;段落太短 → 语义碎片化
// - 核心目标:语义精确、检索高效

const textSplitter = new RecursiveCharacterTextSplitter({
    chunkSize: 400,                  // 每个 chunk 的目标大小(字符数)
    separators: ['。', '!', '?'],  // 中文语义分隔符(句号、感叹号、问号)
    chunkOverlap: 100,               // 相邻 chunk 的重叠字符数
})

// 执行分割
const splitDocuments = await textSplitter.splitDocuments(documents)

// 输出结果
console.log(splitDocuments)
// 预期:10 个左右的 chunk
// ✅ 每个 chunk 都有完整语义
// ✅ 每个 chunk 大小都接近 chunkSize (400)

📊 运行结果分析

执行 node src/index.mjs 后,你会看到类似这样的输出(以掘金文章为例):

bash 复制代码
[
  Document {
    pageContent: "前言:在AI大模型时代...我们开始吧。",
    metadata: { source: "https://juejin.cn/post/...", ... }
  },
  Document {
    pageContent: "1. 项目背景...核心问题。",
    metadata: { source: "https://juejin.cn/post/...", ... }
  },
  // ... 共约 10 个 chunk
]

每个 chunk:

  • 📏 大小 ≈ 350~400 字符(接近 chunkSize)
  • 🧩 语义独立完整(每个 chunk 都是一个完整的小段落)
  • 🔗 重叠确保过渡句的语义不丢失

8. AI 时代程序员的角色转变:从 Coder 到 AI 架构师

本项目不仅仅是一次技术实践,更折射出 AI 时代程序员的核心竞争力转变:

🚫 不再是:纯 Coding

写代码这件事,AI 做得越来越好。死磕语法、拼写速度,已经不是核心竞争力。

✅ 而是:AI 架构师

能力维度 传统程序员 AI 架构师
核心技能 写代码(Coding) 问好问题(Prompt Engineering)
工作方式 手写每一行逻辑 提供丰富准确的上下文(Context)
产出物 功能模块 驾驭(Harness)并部署(FDE)Agent 产品
系统设计 单体应用 设计长时间稳定运行的 Agent Loop
技术栈 单一语言/框架 多模态工具链编排

🎯 五个关键能力

  1. 问出好的问题(Prompt)------精准描述需求,让 AI 给出高质量产出
  2. 提供丰富准确的上下文(Context)------RAG 的核心,喂给 AI 的信息质量决定输出质量
  3. 驾驭(Harness)Agent 产品------不是手写代码,而是编排多个 AI Agent 协同工作
  4. 部署(FDE)Agent 产品------让 AI 应用真正跑在生产环境
  5. 设计长时间稳定运行的 Loop------多轮对话、自动重试、自我纠错的闭环系统

🤖 Vibe Coding:这不再是一个玩笑,而是一种新的编程范式------你描述你想要什么(vibe),AI 帮你实现。但前提是,你得清楚地知道"什么是一个好的 RAG 系统",这正是本文的价值所在。


9. 总结与展望

📝 本文核心要点回顾

scss 复制代码
知识来源(多格式)
     │
     ▼
  Loader (180+ 种,选正确的)
     │  输入:文件/URL
     │  输出:Document[] (pageContent + metadata)
     │
     ▼
  Splitter (文本分割)
     │  chunkSize:大小控制 (400)
     │  separators:语义分隔 (。!?)
     │  chunkOverlap:重叠补救 (100)
     │
     ▼
  向量化 & 存入向量数据库 (后续步骤)

🔑 三个核心认知

  1. Document 是通用货币:Loader 把各种格式统一为 Document,Splitter 在 Document 上操作,Embedding 把 Document 转成向量------整个 RAG 流水线围绕 Document 运转。

  2. 分割是艺术不是科学 :没有绝对"正确"的 chunkSize/overlap,要根据你的文档类型 (技术文章 vs 法律合同 vs 聊天记录)、检索场景 (精确匹配 vs 概括总结)、模型限制(Token 上限)来调参。

  3. chunkOverlap 是语义的保险丝:它不消除切割损伤,但确保每个 chunk 都能"自圆其说"。这就是"用一定的冗余来确保语义的完整性"。

🚀 下一步学习方向

  • 📊 Embedding 向量化:把 chunk 转成向量(OpenAI Embedding / 本地模型)
  • 🗄️ 向量数据库:Pinecone / Chroma / Milvus / PGVector
  • 🔍 检索优化:语义检索 + 关键词检索混合(Hybrid Search)
  • 🧠 Rerank 重排序:对召回结果二次精排,提升 Top-K 质量
  • 🎯 Agent 集成:把 RAG 作为 Tool 喂给 Agent,实现更智能的问答

📖 参考资源

相关推荐
哈__1 小时前
MCP 协议落地实践:国产化数据库 AI 一站式 SQL 诊断调优方案
数据库·人工智能·sql
2501_911067661 小时前
市政标杆视角:标准化建设+精品工程,叁仟智慧灯杆打造杭州市政新基建样板
人工智能
集之互动1 小时前
破解出海传播壁垒 集之互动AI TVC双维适配助力品牌全球化精准传播
人工智能
ss2731 小时前
Vue 完整版本发展史时间线(2013–2026)|版本迭代里程碑+核心特性对照表
前端·javascript·vue.js
长风2301 小时前
Day 17: 突破 AOB 框架霸权 —— 插件界面重构与大屏呈现
人工智能·安全
用户938515635071 小时前
知识库预处理实战:从URL加载到语义分块的全链路解析
javascript·人工智能·全栈
何时梦醒1 小时前
🧠 大模型知识蒸馏:小模型如何"偷师"大模型?
人工智能
萧青山1 小时前
AI+HI人机协同范式:从ElementsClaw到政务智能体的分层进化论
人工智能·机器学习·政务·ai+hi人机协同
武子康1 小时前
HunyuanVideo 全家族选型:原版 13B / I2V / Avatar / Foley / 1.5 8.3B 怎么分工
人工智能·llm·aigc