🔑 让 AI 学会"读"网页:我用 Cheerio 爬了一篇掘金文章,然后切成小块存进了向量库

写在前面:之前我们做 RAG,知识库里的文档都是手动写的 new Document({ pageContent: "......" })。老师说:"你不可能每篇文档都手写吧?知识从哪里来?" 是啊,真实的知识库是各种格式的文件------Word、PDF、网页链接、B 站视频......今天终于学到了关键一步:文档加载器(Loader)。一行代码把一篇掘金文章"爬"下来,再切成语义完整的块,存入向量库。我感觉我离真正的 RAG 又近了一步。


一、知识库不是手写的,是"加载"出来的

1.1 之前的问题

老师说:

"知识库放的知识,知识的来源很多------一个 Word 文档、一个 PDF 文件、一个 Bilibili 视频、一个 URL、一个靠谱的 Twitter......"

之前我们做的 RAG,Document 是手动写的

javascript 复制代码
const documents = [
    new Document({
        pageContent: `光光是一个活泼开朗的小男孩......`,
        metadata: { chapter: 1, character: "光光" },
    }),
];

这显然不现实。一个真正的知识库可能有几千几万篇文档,不可能手写。

解决方案:用 Loader(加载器)。

老师说:

"Langer ------ 知识库 → 向量数据库。各种知识文件有不同的 Loader,输入是文件,输出是 Documents。"

LangChain 内置了 180 多种 Loader------

  • CheerioWebBaseLoader:加载网页内容
  • PDFLoader:加载 PDF
  • DocxLoader:加载 Word
  • YouTubeLoader:加载 YouTube 视频字幕

你只需要告诉 Loader "文件在哪",它自动给你生成标准的 Document 对象。


二、爬虫:用 Cheerio 以"前端思维"爬网页

2.1 传统爬虫 vs Cheerio

先看 crawl.mjs

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

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

async function crawlPage() {
    try {
        const { data: html } = await axios.get(targetUrl);
        const $ = cheerio.load(html);  // html → DOM 树
        const pageContent = $('.main-area p').text(); // CSS 选择器提取
        console.log(pageContent);
    } catch (e) {
        // 错误处理
    }
}

crawlPage();

cheerio 是一个"在 Node.js 里模拟浏览器 DOM"的库。

老师说:

"Cheerio 另辟蹊径------前端思维,CSS 选择器提取需要的内容。"

它的工作方式是这样的:

css 复制代码
HTML 字符串(从网页下载)
  ↓ cheerio.load(html)
虚拟 DOM 树(在内存中)
  ↓ $('.main-area p').text()
提取出指定 CSS 选择器的文本内容

axios 下载 HTML,用 cheerio 解析 DOM,用 CSS 选择器提取内容------这就是"前端思维"的爬虫。

2.2 整个流程

步骤 工具 做了什么
1. 发送请求 axios.get(url) 下载网页 HTML
2. 解析 DOM cheerio.load(html) 在内存构建虚拟 DOM 树
3. 提取内容 $('.main-area p').text() CSS 选择器取指定内容
4. 得到文本 纯文本字符串 可以拿去向量化了

如果没有 cheerio,你就得用正则表达式去抠文本------一个让人想死的操作。


三、Loader:LangChain 的"文档适配器"

3.1 CheerioWebBaseLoader

index.mjs

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();

CheerioWebBaseLoader 把"爬虫 + 解析 + 生成 Document"的流程打包成了一个类。

  • 传入 URL + CSS 选择器。
  • 调用 .load() 方法。
  • 返回的就是标准的 Document[](pageContent + metadata)。

3.2 为什么不用手动写 Loader?

老师说:

"来自社区 @langchain/community,主要由社区维护,我们都可以写 Loader。LangChain @langchain/core 官方维护的。"

Loader 有 180 多种,覆盖了绝大多数知识来源。 如果你需要的格式不在里面,你也可以自己写一个 Loader 提交到社区。


四、文本切割:保持语义完整性的"艺术"

4.1 为什么要切分?

老师说:

"文件太大,要检索的是一定大小、具有一定语义的 Chunk(块)。"

一个文档可能非常长------比如一篇 10000 字的掘金文章。如果你把它整个向量化:

  • 你问"什么是 RAG",它会返回整篇文章。
  • 但这篇文章可能只有一段话是关于 RAG 的,其他全是废话。
  • 不够精准。

所以需要切分(Split)------把长文档切成语义完整的片段。

4.2 怎么切?

老师说:

"切割的意义------保持语义的完整性。"

RecursiveCharacterTextSplitter 是 LangChain 提供的递归文本切分器:

javascript 复制代码
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';

const textSplitter = new RecursiveCharacterTextSplitter({
    chunkSize: 400,       // 每个 Chunk 最大 400 个字符
    separators: ["。","!","?"], // 按句号、感叹号、问号切分
    chunkOverlap: 100,    // 前后重叠 100 个字符
});

const splitDocuments = await textSplitter.splitDocuments(documents);

三个关键参数:

参数 作用 比喻
chunkSize 每块的最大字符数 每段话不能超过 400 字
separators 切分依据的标点符号 按句号切,不能按逗号切
chunkOverlap 前后块的重叠字符数 两段话之间重复 100 字

老师说:

"Separators 按 。!? 切------不会是 。"

句号、感叹号、问号是一句话的结束,语义相对完整。逗号只是停顿,切在逗号上会把一句话劈成两半。

4.3 chunkOverlap 的作用

老师说:

"切断了,Chunk 的最后一句和下一句 Chunk 第一句,他们的语义相关性是最大的。但是因为 chunkSize 切开了,语义遗憾。用 Overlap 用一定的冗余来确保语义的完整性。"

想象一下:

arduino 复制代码
Chunk 1: "今天天气很好,我们去公园散步。小明带了一个风筝,"
Chunk 2: "他跑得很快,风筝飞得很高。"

如果 chunkOverlap = 0,Chunk 1 以"风筝,"结尾,Chunk 2 以"他"开头。
检索"风筝"时,Chunk 2 有"风筝"但没有"小明"------语义断了。

如果 chunkOverlap = 100:
Chunk 1: "今天天气很好,我们去公园散步。小明带了一个风筝,"
Chunk 2: "小明带了一个风筝,他跑得很快,风筝飞得很高。"
                  ↑ 重叠的 100 字
两个 Chunk 都包含"小明带了一个风筝",语义就完整了。

chunkOverlap 就像两块木板拼接时的"重叠部分"------让接缝处更牢固。


五、完整的 RAG 数据准备流程

复制代码
网页 URL / PDF / Word 等
  ↓ Loader(180+ 种)
Document 对象数组
  ↓ TextSplitter(切分成语义完整的 Chunk)
小 Document 数组
  ↓ Embedding 模型
向量
  ↓ 存入
向量数据库
  ↓ 使用时
Retriever 检索器

之前我们手动写 Document,现在有了 Loader,真实的知识库终于可以自动化了。


六、总结:让知识库"活"起来

概念 说明
Loader 把各种格式的文件转为标准 Document
CheerioWebBaseLoader 网页加载器,输入 URL + CSS 选择器
cheerio Node.js 中的虚拟 DOM 解析库
RecursiveCharacterTextSplitter 递归文本切分器
chunkSize 每个块的最大字符数
separators 切分依据的分隔符(句号、感叹号、问号)
chunkOverlap 前后块的重叠字符数,保证语义衔接

Loader 解决了"知识从哪里来"的问题,TextSplitter 解决了"怎么让知识更精准"的问题。两者结合,就是 RAG 知识库的"数据管道"。


写在最后

今天最大的收获,是知道了 Loader 的存在。之前手动写 Document,总觉得 RAG 做着做着就变成了"体力活"。有了 Loader,一个 URL 扔进去,自动爬取、自动切分、自动向量化------这才是工程化的感觉。

而且 cheerio 的"前端思维"爬虫让我特别有亲切感------$('.main-area p').text(),这不就是 jQuery 吗?用熟悉的东西做新的事情,这种感觉太好了。

下次面试官问你:"RAG 的知识库文档怎么准备?"

你可以淡定地说:

"RAG 知识库的文档准备分为两步。第一步用 Loader 加载:LangChain 提供了 180 多种 Loader,比如 CheerioWebBaseLoader 可以加载网页内容,传入 URL 和 CSS 选择器,调用 .load() 方法自动返回标准 Document[]。第二步用 TextSplitter 切分:RecursiveCharacterTextSplitter 按句号、感叹号、问号等分隔符切分,chunkSize 控制每块大小,chunkOverlap 保证语义连续性。这样处理后的 Document 就可以进行 Embedding 并存入向量库了。整个过程从原始文件到可向量化的片段,实现了自动化的数据管道。"

然后看着面试官满意的表情,心里默念:这波,又稳了。


本文所有代码示例均来自课堂学习资料,真实可运行。

相关推荐
颜进强1 小时前
Ollama 从入门到实践:本地模型运行、API 调用
前端·后端·ai编程
颜进强1 小时前
Embedding 基础使用:用 Ollama 和 LangChain.js 生成文本向量
前端·后端·ai编程
颜进强1 小时前
Vector Store 入门:什么是向量数据库,主流产品如何选择
前端·后端·ai编程
方方洛2 小时前
AI 教程系列-TUI 应用开发教程14-技能系统与可扩展性
前端·javascript·typescript
方方洛2 小时前
AI 教程系列-TUI 应用开发教程05-大模型对话界面设计
前端·javascript·typescript
方方洛2 小时前
AI 教程系列-TUI 应用开发教程08-流式响应与动画
前端·javascript·typescript
方方洛2 小时前
AI 教程系列-TUI 应用开发教程04-第一个 TUI 应用
前端·javascript·typescript
cooldream20092 小时前
AI 时代,前端工程师的“新学习路线“—— 从“学框架“到“用 AI“
前端·人工智能·学习
明日清晨2 小时前
printf(“%p\n“,&((struct A *)NULL)->m_float)的原理
linux·服务器·前端