90%RAG新手踩坑点:File、Document、文本分割深度剖析
2026/7/25 0:39:57 网站建设 项目流程

文章目录

    • 前言
    • 一、知识库的素材,来源五花八门
    • 二、File ≠ Document,90%新手踩的第一个误区
      • 2.1 两者根本不是一个东西
      • 2.2 Document不能手动创建
    • 三、Loader:万能转换器,统一所有文件格式
      • 3.1 Loader核心职责
      • 3.2 Loader分两大包
    • 四、逐行拆解网页加载器CheerioWebBaseLoader
      • 4.1 基础概念拆解
      • 4.2 构造函数核心参数
      • 4.3 load()一行代码背后五件事
    • 五、手动复刻Loader逻辑,看懂底层爬虫原理
      • 5.1 请求获取HTML
      • 5.2 cheerio构建内存DOM树
      • 5.3 CSS选择器精准提取正文
    • 六、Splitter:必须切割长文本的两大痛点
    • 七、RecursiveCharacterTextSplitter递归分割器深度拆解
      • 7.1 Recursive递归到底是什么?
      • 7.2 三大核心参数详解
      • 7.3 重叠chunkOverlap存在的意义
    • 八、两种开发方式对比:封装工具 vs 手写底层
      • 8.1 Loader封装写法(工程开发首选)
      • 8.2 axios+cheerio手写爬虫(学习底层专用)
    • 九、完整RAG预处理全链路梳理
    • 十、最后总结

P.S. 挖到宝藏AI教程!全程通俗易懂,风趣幽默,零基础轻松入门,传送门https://blog.csdn.net/qq_34419312

前言

刚上手RAG的时候,我真的卡在前处理半天。大模型生成逻辑我能捋明白,唯独文档加载、文本切割这堆前置步骤,越看越迷糊。

身边不少同行跟我一样,写代码只会复制粘贴Loader,问一句Document和本地PDF有啥区别,当场沉默。还有人写Splitter,chunkOverlap随便填个数字,完全不知道重叠到底干嘛用,纯靠网上复制参数碰运气。

一、知识库的素材,来源五花八门

咱们搭建知识库,素材来源根本不统一:本地Word、PDF文档、网页链接、视频字幕,甚至社交平台短文全都算。

这里有个大坑,千万不能直接把原始二进制文件丢给大模型,也没法直接拿去做向量化运算。

就像你不能直接把一整箱生鲜塞锅里煮,得先分拣、清洗,统一处理成能下锅的食材,RAG预处理干的就是分拣清洗的活儿。

第一步核心目标:把所有乱七八糟的文件格式,统一转换成一套标准数据结构。

二、File ≠ Document,90%新手踩的第一个误区

2.1 两者根本不是一个东西

很多人默认文件就是文档,这是完全错误的认知。

File(文件):磁盘里存储的二进制数据,PDF是二进制流,docx本质是压缩包套XML,视频文件甚至没有纯文本内容,格式千差万别。

Document:LangChain内置的内存结构化对象,固定只有两个字段:

{pageContent:"提取出来的纯文本正文",metadata:{source:"文件/网页地址",title:"文档标题"}}

打个通俗比方:File是没拆封的快递纸箱,Document是拆开整理好、贴好快递单的物品。快递单就是metadata,正文就是pageContent。

2.2 Document不能手动创建

别想着自己手写new Document封装文本,规范流程必须靠Loader加载文件自动生成。

下游所有分割、向量化、检索组件,只认Document这套标准结构,原始文件直接丢进去全报错。

三、Loader:万能转换器,统一所有文件格式

3.1 Loader核心职责

完整链路:各类原始文件 → Loader → 标准Document数组

Loader只干两件事:

  1. 根据文件类型匹配对应加载器:PDF用PDFLoader,网页用CheerioWebBaseLoader,Word用DocxLoader;
  2. 自动提取文本,封装成带元数据的Document对象。

Loader相当于食堂打饭窗口,不管你带米饭、面条、馒头,统一给你装到同款餐盒里,后厨处理起来不用区分食材容器。

3.2 Loader分两大包

  1. @langchain/core:官方核心包,存放BaseDocumentLoader基类、Document基础定义;
  2. @langchain/community:社区维护包,市面上绝大多数小众文件加载器都在这,任何人都能提交代码新增Loader。

四、逐行拆解网页加载器CheerioWebBaseLoader

4.1 基础概念拆解

Cheerio:底层解析HTML的工具库;Web:数据源是网页链接;Base:通用基础实现;Loader:加载器。

它是类,不是普通函数,使用时必须new实例化。

// 实例化,传入网页地址与筛选规则constcheerioLoader=newCheerioWebBaseLoader('目标网页URL',{selector:'.main-area p'})// 调用加载方法,返回Document数组constdocuments=awaitcheerioLoader.load()

为什么设计成类而不是函数?因为要保存状态,网页地址、筛选规则、超时时间存在实例里,不用每次调用load重复传参,省得写一堆重复代码。

4.2 构造函数核心参数

webPath:必填,目标网页链接;

timeout:默认10000毫秒,HTTP请求超时时间;

selector:默认body,CSS选择器,精准提取正文,过滤导航栏、广告、评论;

headers:自定义请求头,用来绕过简单反爬。

4.3 load()一行代码背后五件事

  1. 发起HTTP GET请求,携带超时终止信号;
  2. 获取网页HTML字符串,用cheerio解析成内存DOM树;
  3. 抓取页面title存入元数据;
  4. 通过CSS选择器过滤,提取纯净正文文本;
  5. 封装成Document,以数组形式返回。

所有Loader统一返回Document数组,哪怕只有一条内容,也是[Document]格式,官方为了统一下游代码逻辑,不用做分支判断,细节设计很贴心。

五、手动复刻Loader逻辑,看懂底层爬虫原理

不用LangChain封装,只用axios+cheerio就能完整复现网页加载流程,彻底看透Loader内部逻辑。

5.1 请求获取HTML

const{data:html}=awaitaxios.get(targetUrl)

axios请求返回的data就是完整HTML字符串,解构重命名html,代码可读性更高。

5.2 cheerio构建内存DOM树

import*ascheeriofrom'cheerio'const$=cheerio.load(html)

把纯文本HTML,在内存里生成树形DOM结构,$是操作DOM的查询工具,不是DOM本身。

cheerio最大优势就是不用启动浏览器,命令行就能操作DOM,写爬虫完全不用写复杂正则,早年用正则匹配HTML有多痛苦,写过爬虫的都懂,页面标签顺序一改直接全线崩溃。

5.3 CSS选择器精准提取正文

constpageContent=$('.main-area p').text()

按层级筛选标签,自动剔除全部HTML标签,拼接纯文本。

这套操作逻辑和前端jQuery完全一致,后端爬虫直接复用前端DOM操作思维,学习成本直接减半。

六、Splitter:必须切割长文本的两大痛点

Loader输出的Document动辄几千上万字,直接向量化检索会出现两个致命问题:

  1. 大模型有上下文窗口上限,超长文本根本塞不进去;
  2. 检索精度暴跌,搜索关键词只能匹配整篇文档,精准段落完全没法单独召回。

就像你翻一本厚词典,想查一个成语,总不能把整本词典全抱给别人看,拆分成分页词条,查找效率直接拉满。

Splitter核心目标:拆分出语义完整、长度可控的文本小块Chunk。

七、RecursiveCharacterTextSplitter递归分割器深度拆解

7.1 Recursive递归到底是什么?

核心是分隔符降级策略,优先按语义完整的边界切割,逐级降级兜底:

  1. 优先中文句号「。」切割,完整保留单句语义;
  2. 单句长度超标,降级到感叹号「!」、问号「?」;
  3. 所有句子分隔符都不满足长度要求,最后字符硬切兜底。

很多人以为递归是循环代码,其实是分割规则优先级递归降级,优先保证语义完整,而不是死板卡死字符长度。

7.2 三大核心参数详解

consttextSplitter=newRecursiveCharacterTextSplitter({chunkSize:400,separators:["。","!","?"],chunkOverlap:100,})constsplitDocuments=awaittextSplitter.splitDocuments(documents)

chunkSize:单块最大字符数,不是硬性截断,如果单句刚好超出一点,会完整保留句子,语义优先级高于长度限制。

separators:分隔符优先级列表,顺序不能乱;英文默认段落、换行、空格、字符四级分隔。

chunkOverlap:相邻文本块重叠字符,最容易被忽略的关键参数。

7.3 重叠chunkOverlap存在的意义

无重叠场景缺陷:关键词刚好卡在两块文本中间,检索只能命中前半段,丢失后半段关键信息。

设置重叠后,分割交界区域内容会复制到前后两个Chunk,不管检索命中哪一块,完整上下文都不会丢失。

重叠不是越大越好,一般设置为chunkSize的10%~25%,设太高会出现大量重复文本,浪费向量库存储空间、增加Embedding计费开销,纯纯给自己增加成本。

八、两种开发方式对比:封装工具 vs 手写底层

8.1 Loader封装写法(工程开发首选)

内置网络请求、DOM解析逻辑,仅需配置selector,直接输出标准Document,无缝对接下游分割、向量化流程,开发速度快。

8.2 axios+cheerio手写爬虫(学习底层专用)

所有网络、解析逻辑手动实现,仅输出纯文本字符串,需要自己手动封装metadata才能给到Splitter使用,适合吃透底层原理,不适合线上业务开发。

手写爬虫适合学习拆解原理,线上项目千万别复用,重复造轮子维护成本极高,Loader官方已经封装好成熟稳定的逻辑,没必要重复开发。

九、完整RAG预处理全链路梳理

各类原始素材(PDF/Word/网页/字幕) → Loader加载 → 标准Document对象 → Splitter递归分割 → 带重叠语义Chunk → Embedding向量化 → 向量数据库存储 → 用户提问检索召回Chunk → LLM生成回答

整条链路里,Loader统一数据标准,Splitter平衡文本长度与语义完整度,是RAG效果的基础前提。

十、最后总结

Loader解决多格式数据源不统一的问题,是整个RAG流水线通用化的根基;

Recursive分割器靠分级分隔符+文本重叠,在文本长度和语义完整性之间找到最优平衡点,不是简单粗暴按字数截断。

很多人做RAG只关注大模型生成效果,忽略前处理Loader和Splitter,最后检索效果差找不到问题根源,吃透底层逻辑,调参、排错都会轻松很多。

P.S. 挖到宝藏AI教程!全程通俗易懂,风趣幽默,零基础轻松入门,传送门https://blog.csdn.net/qq_34419312

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询