爬网页+分块+向量检索,50行JS搭建专属知识库,彻底解决大模型幻觉问题
爬网页+分块+向量检索,50行JS搭建专属知识库,彻底解决大模型幻觉问题
😵先说说踩过的大坑
做AI问答时,我踩过无数痛点:
- 直接丢整篇文章给LLM,上下文超限直接截断,关键信息丢失
- 大模型凭空编造内容,答非所问,幻觉严重
- 手动写爬虫解析网页,正则写到头秃,换个网站就得重写
- 文档切割不讲究语义,一句话拆成两段,检索完全失效

折腾一周后,我用LangChain一套流程搞定:网页爬虫→标准化文档→语义分块→向量库存储→相似度检索→LLM精准问答。
看完这篇你能学会:
- Cheerio优雅爬取网页正文,不用手写复杂正则
- 中文友好的语义分割,保证chunk语义完整
- 内存向量库快速存储文本向量,本地即可测试
- 相似度打分检索,精准匹配原文内容
- 一套可直接复制运行的完整RAG流水线代码
🧩先搞懂核心概念:RAG预处理全链路
知识库来源五花八门:Word、PDF、B站视频、网页、推文,不能直接丢给大模型。
完整处理链路:各类数据源 → Loader加载标准化Document → 语义分块Chunk → Embedding向量化 → 向量数据库存储 → 检索匹配上下文 → LLM生成答案
1. Document:LangChain标准数据格式
所有文件、网页最终都会转为统一Document对象,包含两个核心字段:
pageContent:正文文本,用于检索和向量化metadata:元数据(来源链接、章节、类型等),溯源、过滤用
不能手动硬拼Document,必须用Loader做适配转换
2. Loader:数据源适配层(180+社区内置)
分两大包:
@langchain/core:官方核心维护@langchain/community:社区维护,包含网页、PDF、Excel等各类加载器
网页专用:CheerioWebBaseLoader,底层封装axios+cheerio,用CSS选择器提取正文,告别正则地狱。
3. 文本分割Splitter:RAG成败关键
文档太大必须切割,核心目标:保留语义完整性
三个核心配置:
separators:分割优先级(中文优先句号、感叹号、问号)chunkSize:单块最大字符长度chunkOverlap:块重叠冗余,解决一句话被拆分导致语义断裂问题
4. AI时代程序员核心价值
不再单纯堆代码,核心能力转变:
- 写出高质量Prompt
- 提供完整、准确上下文Context
- 设计稳定可循环的Agent流程
- 驾驭、部署AI产品,转型AI架构师
🕷️第一步:原生Cheerio爬虫基础demo
先看底层实现,理解Loader背后原理,新建crawl.js
// crawl.js
import axios from 'axios';
import * as cheerio from 'cheerio';
// 目标掘金文章链接
const targetUrl = 'https://juejin.cn/post/7660707431753678854';
async function crawlPage() {
try {
// 1. 请求网页拿到完整html字符串
const { data: html } = await axios.get(targetUrl);
// 2. cheerio在内存虚拟DOM,前端CSS选择器思维解析
const $ = cheerio.load(html);
// 精准提取文章正文段落,过滤导航、广告、侧边栏
const pageContent = $('.main-area p').text();
console.log('提取到的正文内容:', pageContent);
} catch(e) {
console.error('爬取失败:', e);
}
}
crawlPage();
底层逻辑拆解
- axios发送http请求,获取原始HTML文本
- cheerio.load(html) 内存生成DOM树,无需浏览器
- 通过CSS选择器精准筛选需要的内容,比正则简单百倍
🚀第二步:LangChain完整RAG实战(可直接运行)
新建index.js,全套流程:爬取→分块→向量化→检索→问答
前置依赖安装
npm install dotenv cheerio axios langchain @langchain/openai @langchain/community
.env环境变量配置
OPENAI_API_KEY=你的密钥
OPENAI_BASE_URL=https://xxx
MODEL_NAME=gpt-3.5-turbo
EMBEDDINGS_MODEL_NAME=text-embedding-ada-002
完整业务代码
// index.js
import "dotenv/config";
import "cheerio";
import { CheerioWebBaseLoader } from '@langchain/community/document_loaders/web/cheerio'
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters'
import { MemoryVectorStore } from '@langchain/classic/vectorstores/memory'
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
// 1. 初始化大模型与向量嵌入模型
const model = new ChatOpenAI({
temperature: 0, // 关闭随机,保证回答严谨
model: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
configuration: {
baseURL: process.env.OPENAI_BASE_URL,
},
});
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
model: process.env.EMBEDDINGS_MODEL_NAME,
configuration: {
baseURL: process.env.OPENAI_BASE_URL
},
});
// 2. 网页Loader:爬取掘金文章,指定正文选择器
const cheerioLoader = new CheerioWebBaseLoader(
'https://juejin.cn/post/7660707431753678854',
{
selector: '.main-area p' // 只提取文章段落,过滤冗余内容
}
)
const documents = await cheerioLoader.load();
// 3. 中文语义分割器(重点配置)
const textSplitter = new RecursiveCharacterTextSplitter({
chunkSize: 400, // 单块最大400字符
separators: ["。", "!", "?"], // 优先按中文句子分割
chunkOverlap: 100, // 100字符重叠,防止语义切断
})
const splitDocuments = await textSplitter.splitDocuments(documents);
console.log(`文档分割完成,共${splitDocuments.length}个chunks`);
// 4. 内存向量库,本地快速测试
const vectorStore = await MemoryVectorStore.fromDocuments(
splitDocuments,
embeddings
);
console.log("向量存储完成");
// 5. 检索器:每次返回相似度最高3个文本块
const retriever = vectorStore.asRetriever({k: 3});
const question = "fs模块有哪些api";
console.log('='.repeat(80));
console.log('用户问题:', question);
console.log('='.repeat(80));
// 基础检索
const docs = await retriever.invoke(question);
// 带相似度打分检索(距离越小越相似)
const scoredResults = await vectorStore.similaritySearchWithScore(question, 3);
// 打印检索结果与相似度
docs.forEach((doc, i) => {
const scoredResult = scoredResults.find(([scoredDoc]) =>
scoredDoc.pageContent === doc.pageContent
)
const score = scoredResult? scoredResult[1]: null;
const similarity = score != null ? (1 - score).toFixed(4): "N/A"
console.log(`\n[文档 ${i + 1}] 相似度指标: ${similarity} (原始距离分: ${score})`);
console.log(`片段预览: ${doc.pageContent.substring(0, 50)}...`);
console.log(`元数据:${JSON.stringify(doc.metadata)}`);
});
// 6. 拼接检索上下文,传入LLM问答
const context = docs
.map((doc, i) => `[片段${i}]\n ${doc.pageContent}`)
.join("\n\n-----\n\n")
const prompt = `
你是文章辅助阅读助手,**仅能根据下方提供的文章内容回答,禁止编造内容**:
文章参考内容:
${context}
用户问题:${question}
你的回答:
`;
const response = await model.invoke(prompt);
console.log("\n🤖AI回答:");
console.log(response.content);
⚠️实战踩坑提醒(90%开发者都会出错)
坑1:分割不设置chunkOverlap,语义断裂
一段完整说明被切成两个chunk,检索时只能匹配其中一半,答案缺失关键信息。
✅ 解决方案:中文场景重叠值建议80~120字符
坑2:直接爬取整页HTML,无关内容干扰检索
页面导航、广告、评论会混入Document,向量匹配时大量无关文本占用检索名额。
✅ 解决方案:使用selector精准限定正文DOM节点
坑3:separators只写英文逗号,中文句子切割混乱
默认分割符是英文换行、空格,中文一整段不会拆分,直接触发chunkSize硬截断。
✅ 解决方案:优先配置中文句号、感叹号、问号
坑4:temperature设置过高,模型随意编造内容
RAG知识库问答需要严谨,高温度会让模型脱离原文瞎回答。
✅ 解决方案:知识库问答固定temperature=0
坑5:不使用Loader,手动拼接Document
不同文件格式(PDF/Word/网页)文本提取逻辑完全不同,手动维护成本极高。
✅ 解决方案:直接复用社区180+种Loader,切换数据源仅换一行代码
📌整套流程核心总结
- Loader统一标准化:多源数据统一转为Document,一套逻辑适配所有知识库
- 语义分割是核心:用中文标点优先分割+重叠冗余,平衡长度与语义完整
- Cheerio简化爬虫:CSS选择器替代复杂正则,快速提取网页正文
- 向量检索解决幻觉:只拿原文相关片段喂给LLM,杜绝凭空编造
- 低成本落地:MemoryVectorStore无需部署向量数据库,本地快速验证RAG效果
这套流程不仅能爬网页,替换对应Loader即可读取PDF、Markdown、Excel文档,快速搭建企业内部知识库、AI文档问答机器人。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)