CrawlForge
首页Playground应用场景集成价格文档博客
用 CrawlForge 实现内容迁移自动化
Use Cases
返回博客
应用场景

用 CrawlForge 实现内容迁移自动化

C
CrawlForge Team
工程团队
2026年4月12日
阅读时长 9 分钟
更新于 2026年4月14日

本页内容

快速解答

当你以编程方式提取内容时,一次原本需要 3 到 6 周的 WordPress 到无头 CMS 的迁移可以压缩到数小时。CrawlForge 的 crawl_deep、extract_content 和 extract_metadata 工具会拉取页面、图片、链接和结构,让你能够转换并重新加载到任何目标平台,无需手动复制粘贴。

将 500 个页面从 WordPress 迁移到无头 CMS 本应只需一个周末。但现实中它要花 3 到 6 周 —— 因为得有人手动复制内容、修正格式、重新链接图片,并逐页核对。内容迁移是 web 开发中最令人头疼的任务,而它几乎可以完全自动化。

CrawlForge 以编程方式提取你整个站点的内容:页面、元数据、图片、链接和文档结构。本指南将向你展示如何构建一个迁移 pipeline,在数小时(而非数周)内将成千上万的页面在任意两个平台之间迁移。

目录

  • 为什么内容迁移如此痛苦
  • 架构概览
  • 步骤 1:盘点你的源站点
  • 步骤 2:提取内容和元数据
  • 步骤 3:保留文档结构
  • 步骤 4:为目标平台进行转换
  • 步骤 5:验证迁移结果
  • credits 成本分析
  • 结果与收益
  • 常见问题

为什么内容迁移如此痛苦

内容迁移失败有三个原因:

  1. 数量:即便是一个小型企业站点也有 200 到 500 个页面。每个页面都需要保留内容、元数据、图片和内部链接
  2. 格式不匹配:源 CMS 和目标 CMS 使用不同的内容模型(WordPress 区块 vs. MDX vs. Contentful 富文本)
  3. 隐藏的复杂性:shortcode、嵌入式媒体、自定义字段、重定向 —— 全都需要处理

手动迁移按分析人员的工时计算,每个页面大约花费 5 至 15 USD。一次 500 页的迁移,按每页 10 USD 计算,仅人力就要 5,000 USD。用 CrawlForge 进行自动化迁移,credits 成本不到 50 USD。

迁移方式成本(500 页)耗时错误率
手动复制粘贴5,000-7,500 USD3-6 周5-10%
半自动(脚本)2,000-3,000 USD1-2 周2-5%
CrawlForge pipeline20-50 USD2-4 小时<1%

架构概览

这个迁移 pipeline 使用五个 CrawlForge 工具:

阶段工具Credits用途
盘点map_site3发现所有页面及其结构
内容提取extract_content2从每个页面拉取干净的内容
元数据采集extract_metadata1保留 SEO 标签和 Open Graph 数据
链接映射extract_links1映射内部链接以便重写
批量处理batch_scrape5高效处理数百个页面

步骤 1:盘点你的源站点

映射你源站点上的每一个页面,包括可能不在导航中的页面。

Typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';

const client = new Client({
  name: 'content-migrator',
  version: '1.0.0',
});

interface SiteInventory {
  pages: Array<{
    url: string;
    path: string;
    depth: number;
    type: 'page' | 'post' | 'category' | 'media' | 'other';
  }>;
  totalPages: number;
  pathGroups: Record<string, number>;
}

async function inventorySite(siteUrl: string): Promise<SiteInventory> {
  const mapResult = await client.callTool({
    name: 'map_site',
    arguments: {
      url: siteUrl,
      max_urls: 2000,
      include_sitemap: true,
      group_by_path: true,
      include_metadata: true,
    },
  });

  const result = JSON.parse(mapResult.content[0].text);

  // Classify pages by URL pattern
  const pages = result.urls.map((url: string) => {
    const path = new URL(url).pathname;
    let type: 'page' | 'post' | 'category' | 'media' | 'other' = 'other';

    if (path.match(/\/\d{4}\/\d{2}\//)) type = 'post';       // WordPress date URLs
    else if (path.match(/\/blog\//)) type = 'post';
    else if (path.match(/\/category\//)) type = 'category';
    else if (path.match(/\.(pdf|doc|jpg|png)/)) type = 'media';
    else type = 'page';

    return { url, path, depth: path.split('/').length - 1, type };
  });

  return {
    pages,
    totalPages: pages.length,
    pathGroups: result.groupedByPath || {},
  };
}

步骤 2:提取内容和元数据

从每个页面提取干净的内容和全部元数据,同时保留标题结构和格式。

Typescript
interface MigratedPage {
  sourceUrl: string;
  slug: string;
  title: string;
  content: string;        // Clean markdown or HTML
  excerpt: string;
  metadata: {
    title: string;
    description: string;
    ogImage: string;
    canonical: string;
    publishedAt: string;
    author: string;
  };
  internalLinks: string[];
  images: string[];
}

async function extractPage(url: string): Promise<MigratedPage> {
  // Extract clean content (readability-optimized)
  const contentResult = await client.callTool({
    name: 'extract_content',
    arguments: { url },
  });

  const content = JSON.parse(contentResult.content[0].text);

  // Extract metadata separately for completeness
  const metaResult = await client.callTool({
    name: 'extract_metadata',
    arguments: { url },
  });

  const meta = JSON.parse(metaResult.content[0].text);

  // Extract links for internal link mapping
  const linksResult = await client.callTool({
    name: 'extract_links',
    arguments: { url },
  });

  const links = JSON.parse(linksResult.content[0].text);
  const internalLinks = (links.internal || []).map(
    (l: { href: string }) => l.href
  );

  // Extract image URLs from content
  const imageRegex = /!\[.*?\]\((.*?)\)/g;
  const images: string[] = [];
  let match;
  while ((match = imageRegex.exec(content.content)) !== null) {
    images.push(match[1]);
  }

  const slug = new URL(url).pathname
    .replace(/^\//,'')
    .replace(/\/$/,'')
    .replace(/\//g, '-');

  return {
    sourceUrl: url,
    slug,
    title: meta.title || content.title || '',
    content: content.content,
    excerpt: meta.description || content.excerpt || '',
    metadata: {
      title: meta.title || '',
      description: meta.description || '',
      ogImage: meta.ogImage || '',
      canonical: meta.canonical || url,
      publishedAt: meta.publishedDate || '',
      author: meta.author || '',
    },
    internalLinks,
    images,
  };
}

步骤 3:保留文档结构

对于大型站点,使用批量处理并构建一个完整的链接映射用于 URL 重写。

Typescript
type UrlMap = Map<string, string>; // old URL -> new slug

async function batchExtract(
  inventory: SiteInventory
): Promise<{ pages: MigratedPage[]; urlMap: UrlMap }> {
  const pages: MigratedPage[] = [];
  const urlMap: UrlMap = new Map();

  // Process in batches of 20
  const batchSize = 20;
  const urls = inventory.pages
    .filter(p => p.type === 'page' || p.type === 'post')
    .map(p => p.url);

  for (let i = 0; i < urls.length; i += batchSize) {
    const batch = urls.slice(i, i + batchSize);
    console.log(
      `Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(urls.length / batchSize)}`
    );

    // Extract each page in the batch
    for (const url of batch) {
      try {
        const page = await extractPage(url);
        pages.push(page);
        urlMap.set(url, page.slug);
      } catch (error) {
        console.error(`Failed to extract ${url}:`, error);
      }
    }
  }

  return { pages, urlMap };
}

步骤 4:为目标平台进行转换

重写内部链接,并将内容转换为与你的目标 CMS 格式相匹配。

Typescript
function transformForTarget(
  page: MigratedPage,
  urlMap: UrlMap,
  targetFormat: 'mdx' | 'contentful' | 'sanity'
): Record<string, unknown> {
  // Rewrite internal links to new URL structure
  let content = page.content;
  for (const [oldUrl, newSlug] of urlMap) {
    content = content.replace(
      new RegExp(oldUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'),
      `/${newSlug}`
    );
  }

  switch (targetFormat) {
    case 'mdx':
      return {
        slug: page.slug,
        title: page.title,
        description: page.metadata.description,
        date: page.metadata.publishedAt || new Date().toISOString(),
        content: content, // Already markdown from extract_content
      };

    case 'contentful':
      return {
        fields: {
          title: { 'en-US': page.title },
          slug: { 'en-US': page.slug },
          body: { 'en-US': content },
          description: { 'en-US': page.metadata.description },
          publishedAt: { 'en-US': page.metadata.publishedAt },
        },
        contentType: 'blogPost',
      };

    case 'sanity':
      return {
        _type: 'post',
        title: page.title,
        slug: { current: page.slug },
        body: content,
        excerpt: page.excerpt,
        publishedAt: page.metadata.publishedAt,
      };

    default:
      return { ...page, content };
  }
}

步骤 5:验证迁移结果

转换之后,核对每个页面是否都正确迁移。

Typescript
interface ValidationResult {
  totalPages: number;
  successCount: number;
  failedCount: number;
  warnings: Array<{
    slug: string;
    issue: string;
  }>;
}

function validateMigration(
  original: SiteInventory,
  migrated: MigratedPage[]
): ValidationResult {
  const warnings: Array<{ slug: string; issue: string }> = [];

  for (const page of migrated) {
    // Check for empty content
    if (!page.content || page.content.trim().length < 50) {
      warnings.push({
        slug: page.slug,
        issue: 'Content appears empty or very short',
      });
    }

    // Check for missing metadata
    if (!page.metadata.title) {
      warnings.push({ slug: page.slug, issue: 'Missing title' });
    }
    if (!page.metadata.description) {
      warnings.push({ slug: page.slug, issue: 'Missing meta description' });
    }

    // Check for broken internal links
    for (const link of page.internalLinks) {
      const isLinkMigrated = migrated.some(
        p => p.sourceUrl === link || link.includes(p.slug)
      );
      if (!isLinkMigrated) {
        warnings.push({
          slug: page.slug,
          issue: `Internal link may be broken: ${link}`,
        });
      }
    }
  }

  const contentPages = original.pages.filter(
    p => p.type === 'page' || p.type === 'post'
  );

  return {
    totalPages: contentPages.length,
    successCount: migrated.length,
    failedCount: contentPages.length - migrated.length,
    warnings,
  };
}

credits 成本分析

以一次 500 页的网站迁移为例:

操作工具Credits数量小计
站点盘点map_site313
内容提取extract_content2500 页1,000
元数据提取extract_metadata1500 页500
链接提取extract_links1500 页500
合计2,003 credits

一次完整的 500 页迁移大约花费 2,000 credits。Hobby 套餐($19/月,5,000 credits)处理它绰绰有余。对于更大的站点(1,000 页以上),Professional 套餐($99/月,50,000 credits)提供了充足的余量。

结果与收益

自动化内容迁移带来:

  • 速度:在 2-4 小时内迁移 500 个页面,而不是 3-6 周
  • 准确性:没有复制粘贴错误、格式损坏或遗漏的页面
  • 完整性:每一个页面、每一个 meta 标签、每一条内部链接都被捕获
  • 成本节省:用 19-99 USD 的工具 credits 取代 5,000 USD 以上的人工

CrawlForge 最适合需要保留 SEO 权重的内容迁移 —— meta 标签、内部链接、canonical URL 和内容结构都会被干净地迁移过去。

常见问题

CrawlForge 能处理 WordPress 的 shortcode 吗?

CrawlForge 的 extract_content 工具处理的是渲染后的 HTML,而不是 WordPress 的原始源码。当 CrawlForge 提取时,shortcode 已经被展开为它们的输出 HTML。你得到的是渲染后的内容,而这正是迁移所需要的。

图片和媒体文件怎么办?

CrawlForge 会从内容中提取图片 URL。你需要一个单独的步骤来下载图片并将其重新托管到目标平台。fetch_url 工具(1 credit)可以下载单个媒体文件。

迁移之后我该如何处理重定向?

步骤 3 中生成的 urlMap 为你提供了一份完整的「旧 URL 到新 slug」的映射。将其导出为你托管平台的重定向映射(Vercel 的 vercel.json、Netlify 的 _redirects,或 nginx 配置)。


这个周末就迁移你的站点。 免费开始,赠送 1,000 credits —— 足以迁移 250 个以上的页面。无需信用卡。

相关资源:

  • CrawlForge 文档
  • 18 个 web scraping 工具概览
  • 使用场景
  • 定价套餐

亲自试一试——无需注册

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

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

标签

content-migrationcmsweb-scrapingautomationwordpressheadless-cmsmcp

关于作者

C

CrawlForge Team

工程团队

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

及时获取最新洞察

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

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

付诸实践

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

本页内容

Frequently Asked Questions

用 CrawlForge 做内容迁移自动化要花多少钱?+

一次完整的 500 页迁移大约花费 2,000 credits(map_site + extract_content + extract_metadata + extract_links)。$19/月、含 5,000 credits 的 Hobby 套餐处理它绰绰有余。对于 1,000 页以上的更大站点,$99/月的 Professional 套餐提供了充足的余量。

迁移过程中 CrawlForge 能处理 WordPress 的 shortcode 吗?+

CrawlForge 的 extract_content 工具处理的是渲染后的 HTML,而不是 WordPress 的原始源码。当 CrawlForge 提取时,shortcode 已经被展开为它们的输出 HTML。你得到的是渲染后的内容,而这正是迁移所需要的。

内容迁移过程中图片和媒体文件怎么办?+

CrawlForge 会从内容中提取图片 URL。你需要一个单独的步骤来下载图片并将其重新托管到目标平台。fetch_url 工具(1 credit)可以下载单个媒体文件。

内容迁移之后我该如何处理重定向?+

迁移过程中生成的 urlMap 为你提供了一份完整的「旧 URL 到新 slug」的映射。将其导出为你托管平台的重定向映射 —— Vercel 的 vercel.json、Netlify 的 _redirects,或 nginx 配置 —— 这样 SEO 权重就能被干净地迁移过去。

相关文章

用 CrawlForge Deep Research 构建调研智能体
Use Cases

用 CrawlForge Deep Research 构建调研智能体

使用 CrawlForge deep_research 构建一个 AI 调研智能体,在几分钟内从数十个来源收集、验证并综合信息。

C
CrawlForge Team
|
4月16日
|
10 分钟
用 CrawlForge 构建 lead enrichment 引擎
Use Cases

用 CrawlForge 构建 lead enrichment 引擎

自动为销售线索补充公司数据、技术栈和联系方式。抓取公开的商业数据来甄别线索并优先安排触达。

C
CrawlForge Team
|
4月14日
|
10 分钟
用 AI agent 实现实时竞争情报
Use Cases

用 AI agent 实现实时竞争情报

用 CrawlForge 和 Claude 构建一个 AI 驱动的竞争情报系统。每周监控竞争对手、追踪变化并生成战略洞察。

C
CrawlForge Team
|
4月8日
|
9 分钟

页脚

CrawlForge

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

产品

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

资源

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

开发者

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

公司

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

保持更新

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

基于 Next.js 和 MCP 协议构建

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