CrawlForge
首页Playground应用场景集成价格文档博客
如何用网页数据构建 RAG 流水线
AI Engineering
返回博客
AI 工程

如何用网页数据构建 RAG 流水线

C
CrawlForge Team
工程团队
2026年4月14日
阅读时长 11 分钟

本页内容

快速解答

一个基于网页数据的生产级 RAG 流水线包含六个阶段:爬取目标站点、提取干净内容、为 embedding 对文本分块、生成向量、将其存入向量数据库,以及在推理时进行查询。CrawlForge 负责前两个阶段(crawl_deep + extract_content),让你的 RAG 系统始终扎根于实时网页数据,而非陈旧的 PDF。

检索增强生成(RAG)的效果,取决于你喂给它的数据质量。大多数 RAG 教程使用静态文档集合——放在某个文件夹里的 PDF 或 markdown 文件。而生产级 RAG 系统需要实时网页数据:每周更新的文档、每月变动的竞品定价、每天发布的研究论文。

本指南将带你构建一个完整的 RAG 流水线,使用 CrawlForge 爬取并提取网页内容,然后将其送入向量数据库以进行检索增强生成。每一步都包含可运行的 TypeScript 代码。

目录

  • 什么是 RAG,为什么要使用网页数据?
  • RAG 流水线架构
  • 步骤 1:爬取目标网站
  • 步骤 2:提取并清洗内容
  • 步骤 3:为 embedding 对文本分块
  • 步骤 4:生成 embedding
  • 步骤 5:存入向量数据库
  • 步骤 6:查询与检索
  • 把所有环节串联起来
  • 性能优化技巧
  • 常见问题

什么是 RAG,为什么要使用网页数据?

检索增强生成是一种让 LLM 的回答扎根于从外部知识库检索到的相关文档的技术。RAG 系统不再仅依赖训练数据(训练数据存在知识截止时间),而是在生成回答之前先获取当前、相关的上下文。

为什么网页数据能让 RAG 更出色:

  • 新鲜度 —— 网页内容实时更新,而训练数据不会
  • 广度 —— 网络覆盖每一个主题、行业和细分领域
  • 针对性 —— 精确抓取与你的领域相关的页面
  • 权威性 —— 从官方文档、研究论文和可信来源中获取

网页数据常见的 RAG 使用场景:

  • 扎根于实时文档的客户支持机器人
  • 跟踪竞争对手变化的竞争情报系统
  • 从学术数据库和新闻中获取信息的研究助手
  • 借助外部行业数据丰富的内部知识库

RAG 流水线架构

一个基于网页数据的 RAG 流水线有六个阶段:

Crawl --> Extract --> Chunk --> Embed --> Store --> Retrieve | | | | | | URLs Clean Text Vectors Vector Context text segments DB + LLM
阶段工具用途
爬取CrawlForge crawl_deep / batch_scrape发现并获取页面
提取CrawlForge extract_content将 HTML 清洗为可读文本
分块自定义逻辑将文本切分为适合 embedding 大小的片段
EmbedOpenAI / Cohere / 本地模型将文本转换为向量表示
存储Pinecone / Weaviate / Qdrant为相似度搜索建立向量索引
检索向量 DB 查询 + LLM找到相关片段,生成回答

步骤 1:爬取目标网站

首先,从你的目标域名发现并获取所有相关页面。CrawlForge 的 crawl_deep 工具会处理分页、链接发现和并行获取。

Typescript
import { CrawlForge } from '@crawlforge/sdk';

const cf = new CrawlForge({ apiKey: process.env.CRAWLFORGE_API_KEY });

// Crawl a documentation site up to 3 levels deep
const crawlResult = await cf.crawlDeep({
  url: 'https://docs.example.com',
  max_depth: 3,
  max_pages: 200,
  include_patterns: ['/docs/', '/guides/', '/api/'],
  exclude_patterns: ['/changelog', '/blog'],
  extract_content: true, // Get clean text during crawl
  concurrency: 10
});

console.log(`Crawled ${crawlResult.pages.length} pages`);
// Crawled 147 pages

如果要抓取一个已知的 URL 列表(比如 sitemap),请改用 batch_scrape:

Typescript
// Scrape a specific list of URLs in parallel
const batchResult = await cf.batchScrape({
  urls: [
    'https://docs.example.com/auth',
    'https://docs.example.com/billing',
    'https://docs.example.com/webhooks',
    'https://docs.example.com/rate-limits',
    // ... up to 50 URLs per batch
  ],
  formats: ['text'],
  maxConcurrency: 10
});

credits 成本: crawl_deep 每次调用花费 5 credits。batch_scrape 每个批次(最多 50 个 URL)花费 5 credits。对于 200 个页面,单次 crawl_deep 调用比多次 batch_scrape 调用更划算。

步骤 2:提取并清洗内容

原始 HTML 包含导航、广告、页脚和样板内容,它们会污染你的 embedding。CrawlForge 的 extract_content 工具使用可读性算法来分离出主要内容。

如果你在爬取阶段使用了 extract_content: true,那么你的内容已经是干净的了。对于单个页面:

Typescript
// Extract clean content from a single page
const page = await cf.extractContent({
  url: 'https://docs.example.com/authentication'
});

console.log(page.content);
// Returns: "Authentication\n\nAll API requests require..."
// No nav bars, no footers, no cookie banners

后处理技巧:

  • 移除重复内容(许多网站会在提取出的文本中重复页眉/页脚)
  • 去除内部导航链接(“Next: Billing” / “Previous: Setup”)
  • 规范化空白字符并移除空行
  • 保留标题——它们为分块提供结构

步骤 3:为 embedding 对文本分块

embedding 模型有 token 上限(通常为 512-8,192 个 token)。长文档必须被切分为更小的、能保留语义含义的片段。

Typescript
interface TextChunk {
  content: string;
  metadata: {
    source: string;
    title: string;
    heading: string;
    chunkIndex: number;
  };
}

function chunkByHeading(
  text: string,
  source: string,
  title: string,
  maxChunkSize: number = 1000 // characters
): TextChunk[] {
  const chunks: TextChunk[] = [];
  // Split on markdown headings (## or ###)
  const sections = text.split(/(?=^#{2,3}\s)/m);

  let chunkIndex = 0;
  for (const section of sections) {
    // Extract heading from section
    const headingMatch = section.match(/^#{2,3}\s+(.+)/);
    const heading = headingMatch ? headingMatch[1].trim() : title;
    const content = section.trim();

    if (content.length <= maxChunkSize) {
      chunks.push({
        content,
        metadata: { source, title, heading, chunkIndex: chunkIndex++ }
      });
    } else {
      // Split large sections by paragraph
      const paragraphs = content.split('\n\n');
      let currentChunk = '';

      for (const para of paragraphs) {
        if ((currentChunk + para).length > maxChunkSize && currentChunk) {
          chunks.push({
            content: currentChunk.trim(),
            metadata: { source, title, heading, chunkIndex: chunkIndex++ }
          });
          currentChunk = para;
        } else {
          currentChunk += (currentChunk ? '\n\n' : '') + para;
        }
      }
      if (currentChunk.trim()) {
        chunks.push({
          content: currentChunk.trim(),
          metadata: { source, title, heading, chunkIndex: chunkIndex++ }
        });
      }
    }
  }

  return chunks;
}

// Usage with crawled pages
const allChunks: TextChunk[] = [];
for (const page of crawlResult.pages) {
  const pageChunks = chunkByHeading(
    page.content,
    page.url,
    page.title || 'Untitled',
    1000
  );
  allChunks.push(...pageChunks);
}

console.log(`Created ${allChunks.length} chunks from ${crawlResult.pages.length} pages`);
// Created 892 chunks from 147 pages

分块策略对比:

策略优点缺点适合
固定大小简单、可预测会在句子中间切断通用内容
基于标题保留结构块大小不均文档
基于句子自然的边界可能太小对话数据
递归式大小均衡 + 保留结构更复杂生产系统

步骤 4:生成 embedding

使用 embedding 模型将每个文本块转换为向量表示。

Typescript
import OpenAI from 'openai';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

interface EmbeddedChunk extends TextChunk {
  embedding: number[];
}

async function embedChunks(
  chunks: TextChunk[],
  batchSize: number = 100
): Promise<EmbeddedChunk[]> {
  const embeddedChunks: EmbeddedChunk[] = [];

  for (let i = 0; i < chunks.length; i += batchSize) {
    const batch = chunks.slice(i, i + batchSize);
    const texts = batch.map(c => c.content);

    const response = await openai.embeddings.create({
      model: 'text-embedding-3-small', // 1536 dimensions, $0.02/1M tokens
      input: texts
    });

    for (let j = 0; j < batch.length; j++) {
      embeddedChunks.push({
        ...batch[j],
        embedding: response.data[j].embedding
      });
    }

    console.log(`Embedded ${Math.min(i + batchSize, chunks.length)}/${chunks.length} chunks`);
  }

  return embeddedChunks;
}

const embeddedChunks = await embedChunks(allChunks);

embedding 模型选项:

模型维度成本质量
OpenAI text-embedding-3-small1,536$0.02/1M tokens不错
OpenAI text-embedding-3-large3,072$0.13/1M tokens更好
Cohere embed-english-v3.01,024$0.10/1M tokens不错
本地(all-MiniLM-L6-v2)384免费够用

步骤 5:存入向量数据库

将带 embedding 的文本块索引到向量数据库中,以进行快速的相似度搜索。

Pinecone 示例

Typescript
import { Pinecone } from '@pinecone-database/pinecone';

const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pinecone.Index('rag-web-data');

// Upsert chunks in batches of 100
async function storeChunks(chunks: EmbeddedChunk[]) {
  const batchSize = 100;

  for (let i = 0; i < chunks.length; i += batchSize) {
    const batch = chunks.slice(i, i + batchSize);
    const vectors = batch.map((chunk, j) => ({
      id: `${chunk.metadata.source}-${chunk.metadata.chunkIndex}`,
      values: chunk.embedding,
      metadata: {
        content: chunk.content,
        source: chunk.metadata.source,
        title: chunk.metadata.title,
        heading: chunk.metadata.heading
      }
    }));

    await index.upsert(vectors);
    console.log(`Stored ${Math.min(i + batchSize, chunks.length)}/${chunks.length} vectors`);
  }
}

await storeChunks(embeddedChunks);

Weaviate 示例

Typescript
import weaviate from 'weaviate-ts-client';

const client = weaviate.client({
  scheme: 'https',
  host: process.env.WEAVIATE_HOST || 'localhost:8080'
});

// Create collection schema
await client.schema.classCreator().withClass({
  class: 'WebDocument',
  vectorizer: 'none', // We provide our own vectors
  properties: [
    { name: 'content', dataType: ['text'] },
    { name: 'source', dataType: ['string'] },
    { name: 'title', dataType: ['string'] },
    { name: 'heading', dataType: ['string'] }
  ]
}).do();

// Store chunks
for (const chunk of embeddedChunks) {
  await client.data.creator()
    .withClassName('WebDocument')
    .withProperties({
      content: chunk.content,
      source: chunk.metadata.source,
      title: chunk.metadata.title,
      heading: chunk.metadata.heading
    })
    .withVector(chunk.embedding)
    .do();
}

步骤 6:查询与检索

现在用用户问题查询向量数据库,检索相关的文本块,并把它们作为上下文传给 LLM。

Typescript
async function ragQuery(question: string): Promise<string> {
  // 1. Embed the question
  const questionEmbedding = await openai.embeddings.create({
    model: 'text-embedding-3-small',
    input: question
  });

  // 2. Query Pinecone for the 5 most relevant chunks
  const queryResult = await index.query({
    vector: questionEmbedding.data[0].embedding,
    topK: 5,
    includeMetadata: true
  });

  // 3. Build context from retrieved chunks
  const context = queryResult.matches
    .map(match => {
      const meta = match.metadata as Record<string, string>;
      return `Source: ${meta.source}\nSection: ${meta.heading}\n\n${meta.content}`;
    })
    .join('\n\n---\n\n');

  // 4. Generate answer with context
  const completion = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      {
        role: 'system',
        content: `Answer the user's question based on the provided context.
Cite sources when possible. If the context does not contain enough
information, say so.`
      },
      {
        role: 'user',
        content: `Context:\n${context}\n\nQuestion: ${question}`
      }
    ]
  });

  return completion.choices[0].message.content || 'No answer generated';
}

// Example usage
const answer = await ragQuery('How does authentication work?');
console.log(answer);

把所有环节串联起来

下面是把整个流水线放进单个编排函数中的完整实现:

Typescript
async function buildRAGPipeline(config: {
  targetUrl: string;
  maxPages: number;
  indexName: string;
}) {
  const cf = new CrawlForge({ apiKey: process.env.CRAWLFORGE_API_KEY });

  // 1. Crawl
  console.log('Crawling...');
  const crawled = await cf.crawlDeep({
    url: config.targetUrl,
    max_depth: 3,
    max_pages: config.maxPages,
    extract_content: true,
    concurrency: 10
  });
  console.log(`Crawled ${crawled.pages.length} pages`);

  // 2. Chunk
  console.log('Chunking...');
  const chunks = crawled.pages.flatMap(page =>
    chunkByHeading(page.content, page.url, page.title || 'Untitled')
  );
  console.log(`Created ${chunks.length} chunks`);

  // 3. Embed
  console.log('Embedding...');
  const embedded = await embedChunks(chunks);

  // 4. Store
  console.log('Storing...');
  await storeChunks(embedded);

  console.log('RAG pipeline complete. Ready for queries.');
}

// Run it
await buildRAGPipeline({
  targetUrl: 'https://docs.example.com',
  maxPages: 200,
  indexName: 'rag-web-data'
});

爬取 200 个页面的 CrawlForge 总 credits 成本: 5 credits(一次带 extract_content: true 的 crawl_deep 调用)。

性能优化技巧

  • 批量 embedding —— 每次 API 调用 embed 100 个文本块,而不是一次一个(速度快 10 倍,成本相同)
  • 对文档使用基于标题的分块,对新闻文章使用基于句子的分块
  • 设置合适的重叠 —— 文本块之间 10-15% 的重叠可防止在边界处丢失上下文
  • 在爬取时过滤 —— 使用 include_patterns 和 exclude_patterns 避免爬取无关页面
  • 积极缓存 —— 将爬取的内容存储在本地,只在内容变化时才重新爬取
  • 监控新鲜度 —— 使用 CrawlForge 的变更跟踪来检测源页面何时更新,然后只对变化的内容重新爬取和重新 embed

常见问题

用 CrawlForge 构建一个 RAG 流水线需要多少 credits?

一次 crawl_deep 调用花费 5 credits,最多可爬取 1,000 个页面。对于一个 200 页的文档站点,CrawlForge 的总成本是 5 credits。免费套餐(1,000 credits)让你可以在付费之前构建 200 个 RAG 流水线。查看定价详情。

我应该为 RAG 使用哪个向量数据库?

Pinecone 上手最简单(全托管,无需基础设施)。Weaviate 通过混合搜索(向量 + 关键词)提供更高的灵活性。Qdrant 是最佳的自托管选项。ChromaDB 非常适合原型开发和本地开发。

我应该多久重新爬取并更新一次 RAG 数据?

这取决于你的源内容变化的频率。文档站点:每周。新闻和研究:每天。产品目录:每小时。使用 CrawlForge 的变更跟踪来检测更新,并只重新处理变化的页面。

我可以将 CrawlForge 与 LangChain 或 LlamaIndex 一起使用吗?

可以。CrawlForge 与这两个框架都能集成。使用 SDK 获取内容,然后把它传给 LangChain 的 document loader 或 LlamaIndex 的 data connector。请参阅我们的 LangChain 集成指南查看示例。


在 10 分钟内构建你的第一个 RAG 流水线。 免费开始,获得 1,000 credits,今天就爬取你的第一个站点。

亲自试一试——无需注册

在 Playground 中运行 CrawlForge 的 27 个抓取与提取工具中的任意一个,然后免费开始,获取 1,000 credits。

1,000 免费 credits • 每月补充 • 无需信用卡

标签

ragai-engineeringvector-databasepineconeweb-scrapingembeddingstutorial

关于作者

C

CrawlForge Team

工程团队

我们正在打造功能最全面的 Web 抓取 MCP server。我们开发的工具帮助开发者为 AI 应用提取、分析和转换 Web 数据。

及时获取最新洞察

将教程、产品更新与 Web 抓取技巧直接发送到你的收件箱。

拒绝垃圾邮件,随时可取消订阅。

付诸实践

在任意 URL 上测试 CrawlForge 的工具——免费,无需注册。

本页内容

Frequently Asked Questions

用网页数据构建一个 RAG 流水线需要多少 credits?+

一次 crawl_deep 调用花费 5 credits,最多可爬取 1,000 个页面。对于一个 200 页的文档站点,CrawlForge 的总成本是 5 credits。1,000 credits 的免费套餐让你可以在付费之前构建 200 个 RAG 流水线。

我应该为 RAG 使用哪个向量数据库?+

Pinecone 上手最简单(全托管,无需基础设施)。Weaviate 通过混合搜索(向量 + 关键词)提供更高的灵活性。Qdrant 是最佳的自托管选项。ChromaDB 非常适合原型开发和本地开发。

我应该多久重新爬取并更新一次 RAG 数据?+

这取决于你的源内容变化的频率。文档站点:每周。新闻和研究:每天。产品目录:每小时。使用 CrawlForge 的变更跟踪来检测更新,并只重新处理变化的页面。

我可以将 CrawlForge 与 LangChain 或 LlamaIndex 一起用于 RAG 吗?+

可以。CrawlForge 与这两个框架都能集成。使用 SDK 获取内容,然后把它传给 LangChain 的 document loader 或 LlamaIndex 的 data connector。这能让你的 RAG 系统始终扎根于实时网页数据,而非陈旧的 PDF。

相关文章

MCP 协议详解:2026 开发者指南
AI Engineering

MCP 协议详解:2026 开发者指南

了解 Model Context Protocol 的工作原理、它为何对 AI 智能体至关重要,以及如何借助架构图和代码构建 MCP server 与客户端。

C
CrawlForge Team
|
4月27日
|
10 分钟
2026 年面向 AI 智能体的最佳网页爬取工具
AI Engineering

2026 年面向 AI 智能体的最佳网页爬取工具

按智能体就绪程度排名的 2026 年面向 AI 智能体的最佳网页爬取工具:MCP 原生工具发现、类型化 schema 与高 token 效率的输出。

C
CrawlForge Team
|
6月9日
|
11 分钟
用本地 LLM 提取网页数据(Ollama + CrawlForge)
AI Engineering

用本地 LLM 提取网页数据(Ollama + CrawlForge)

无需 API key,无需云端,数据不离开你的机器。用 extract_with_llm 配合本地 Ollama 从任何网站提取结构化数据。

C
CrawlForge Team
|
5月24日
|
9 分钟

页脚

CrawlForge

面向 AI Agent 的企业级网页抓取。27 个专业 MCP 工具,专为构建智能系统的现代开发者而设计。

产品

  • 功能
  • Playground
  • 价格
  • 应用场景
  • 集成
  • 替代方案
  • 更新日志

资源

  • 快速上手
  • API 参考
  • 模板
  • 指南
  • 博客
  • 术语表
  • 常见问题
  • 网站地图

开发者

  • MCP 协议
  • Claude Desktop
  • Cursor IDE
  • LangChain
  • LlamaIndex

公司

  • 关于我们
  • 联系我们
  • 隐私政策
  • 服务条款
  • 可接受使用政策
  • Cookie

保持更新

获取新工具和新功能的最新动态。

基于 Next.js 和 MCP 协议构建

© 2025-2026 CrawlForge。保留所有权利。