从 WordPress 迁移

本页内容

使用 EmDash 管理后台分析 WordPress 站点、准备兼容的集合、导入内容、复制媒体并重写 URL。在导入后的站点通过验证之前,请保持 WordPress 站点可用。

开始之前

请准备以下内容:

  • WordPress 数据库和 wp-content/uploads 的备份。
  • 如果计划复制媒体,需要一个已配置存储的 EmDash 站点。
  • 一个 EmDash 管理员账户。WordPress 导入需要 import:execute 权限。
  • 完整的 WordPress eXtended RSS(WXR)导出文件,或在 WordPress 站点上安装的 EmDash Exporter 插件。

记录当前的固定链接结构、规范来源、重定向、菜单、启用的文章类型、分类法,以及新站点必须保留的插件自有字段。

选择导入方法

方法适用场景包含的数据
WXR 上传你可以通过 Tools → Export 导出导出文件中包含的数据,包括文章、页面、自定义文章类型、分类法词条、可复用区块、作者和附件 URL
EmDash Exporter你控制该 WordPress 站点,并且可以安装导出器需要认证才能访问的内容,以及受支持的站点设置、SEO 字段、分类法和媒体元数据;使用导出器 1.2.0 或更高版本时,还包括评论、菜单、徽标和站点图标

在没有导出器的情况下输入站点 URL,只会探测公开的 WordPress REST API。该探测可以检测到 WordPress,并统计公开的文章、页面和媒体数量,但无法执行直接的 REST 导入。

导出 WXR 文件

在 WordPress 中打开 Tools → Export,选择 All content,然后下载 XML 导出文件。WXR 文件指向 WordPress 来源上的媒体;它不包含附件的字节内容。

连接 EmDash Exporter

请在 WordPress 的 Plugins 页面查看导出器的版本。评论、菜单、徽标和站点图标需要导出器 1.2.0 或更高版本。使用更早的版本时,导入会在没有这些内容的情况下完成,也不会把它们报告为缺失。

使用导出器 1.2.0 或更高版本时,请在 WordPress 中打开 Tools → EmDash Migration 并生成迁移密钥。把密钥粘贴到 EmDash 导入页面中。

无论导出器是哪个版本,你都可以输入 WordPress 站点 URL。检测到导出器后,通过 WordPress 的应用程序密码页面授权 EmDash。在本地 HTTP 开发站点上,由于授权回调需要 HTTPS,请手动输入 WordPress 用户名和应用程序密码。

通过管理后台导入

  1. 在管理后台侧边栏中选择 Import 以打开 Import from WordPress 页面,或直接访问 /_emdash/admin/import/wordpress。

  2. 上传 WXR 文件、粘贴导出器的迁移密钥,或输入 WordPress URL。

  3. 检查分析结果。对于每个 WordPress 文章类型,确认目标集合、必填字段,以及与现有字段的兼容性。

  4. 将 WordPress 作者映射到 EmDash 用户。未映射的来源作者可以用访客 byline 表示,因此署名展示不依赖于是否授予其 EmDash 登录权限。

  5. 对于通过导出器导入的情况,选择是否导入菜单、站点标题和副标题、徽标和网站图标,以及受支持的 SEO 值。这些开关不属于 WXR 路径。

  6. 开始导入。EmDash 会先创建缺失的兼容集合和字段,然后再创建条目。

  7. 如果发现了附件,请运行媒体步骤。EmDash 会下载来源的字节内容,通过已配置的存储适配器存储,并重写匹配的内容 URL。

转换行为

状态

导入器按如下方式映射 WordPress 状态:

WordPressEmDash
publishpublished
draftdraft
pendingdraft
privatedraft
future、trash 或未知状态draft

定时发布的日期和 WordPress 的可见性规则不会根据这些状态重建。发布之前,请检查每一篇导入的草稿。

富文本和可复用区块

Gutenberg 标记会被转换为 Portable Text。经典 HTML 由同一个转换器处理。请在渲染后的 EmDash 站点中检查复杂区块、嵌入内容、短代码、页面构建器标记以及插件定义的区块。

WordPress 在服务器端渲染的区块(例如 Latest Posts 或来自插件的区块)没有保存的标记。它们会被导入为 HTML 区块,其中包含原始的区块注释,例如 <!-- wp:latest-posts {"postsToShow":3} /-->。这些区块在站点上不显示任何内容。请在编辑器中打开该区块的 HTML 标签页,查看它原本是什么,然后重新构建或删除它。

WXR 中的 wp_block 条目会作为 sections 导入。它们不会被导入到普通的内容集合中。

集合和字段

默认的文章类型映射包括 post 映射到 posts,page 映射到 pages。自定义文章类型使用经过清理的集合 slug。准备步骤会添加分析所需的标准标题、Portable Text 内容、摘要和特色图片字段。

类型兼容的现有字段会被复用。类型不匹配会阻止该集合的映射;导入器不会把现有字段强制转换为其他类型。

自定义字段和 SEO 数据

WXR 分析会报告非内部的文章元数据键,并建议字段名称和类型,但 WXR 数据库导入写入的是标准的导入字段,而不是复制每一个任意的元数据键。请把分析结果当作可能需要单独转换的字段清单。

当目标集合具有匹配的字段时,EmDash Exporter 路径可以复制自定义元数据和 Advanced Custom Fields(ACF)的值。对于带有 “Post Type is equal to” 位置规则的 ACF 字段组,分析会为该组中的每个 ACF 字段建议一个字段:文本、数字和真/假字段会获得匹配的类型,而选择、复选框、重复器、组、关系,以及其他可以保存列表或对象的字段则会变成 JSON 字段。通过其他位置规则分配的字段组,其字段仅根据导出器报告的元数据键来建议。如果某个集合的字段是早先的导入根据元数据键创建的,它会保留这些字段,因此其重复器会按每一行和每个子字段各生成一个字段的方式导入。JSON 值保持其 WordPress 形态:关系、文章对象、用户和分类法字段按 WordPress ID 指向 WordPress 的文章、用户和术语,而不是 EmDash 条目;图片、文件和图库字段指向 WordPress 媒体文件,而不是已导入的 EmDash 媒体。存储在多篇文章上的 ACF 组子字段,也可能作为单独的字段出现在该组的 JSON 字段旁边;请保留你的模板所使用的那一份。在模板中使用这些值之前,请先检查它们。

导出器路径还可以在启用相应的导入开关后,创建并填充受支持的特色图片字段,以及 Yoast 或 Rank Math 的 SEO 字段。

作者和 byline

已映射的 WordPress 作者会成为条目的所有者。署名展示使用 byline。如果没有用户映射,EmDash 会根据 WordPress 作者的身份创建或复用访客 byline。

分类法

分类目录和标签会导入到匹配的 EmDash 分类法定义中。导出器路径可以为自定义分类法创建定义。没有匹配 EmDash 定义的 WXR 自定义分类法会被报告为缺失,其分配关系会被跳过。

媒体

媒体去重会比较已下载字节内容的 SHA-1 哈希。具有相同哈希的现有媒体行会被复用。文件名和 WordPress ID 不是去重的依据。

在 EmDash 下载附件期间,来源站点必须保持可访问。来自附件 URL 的重定向在被跟随之前会经过验证。

重试部分完成的导入

管理后台导入的设计会跳过已存在的条目,但其匹配边界很重要:

  • 现有内容按集合、slug 和语言区域匹配,而不是按 WordPress ID 匹配。
  • 如果来源 slug 在两次尝试之间发生了变化,请检查这两个条目并手动解决重复问题。
  • 重新运行媒体步骤时,会按内容哈希复用已经存储的、字节完全相同的文件。
  • 导出器流程发送的是有大小限制的分块。重新启动浏览器流程会重新运行内容页面,并根据被跳过的条目重建关系映射。

管理后台不会保存 WXR 的恢复文件。emdash import wordpress 上的 --resume 标志属于另一个将转换后的 JSON 文件写入磁盘的 CLI 工作流;它不会恢复管理后台的数据库导入。

切换上线前的验证

将导入后的站点与来源站点进行比对:

  • 按文章类型和状态统计条目数量。
  • 打开具有代表性的 Gutenberg、经典编辑器、短代码和页面构建器内容。
  • 确认特色图片和内嵌媒体都能从 EmDash URL 解析。
  • 检查作者、byline、分类目录、标签、自定义分类法和翻译。
  • 如果是通过导出器导入的,测试菜单、评论、站点标识和 SEO 字段。
  • 抓取旧的公开 URL,并为每一个发生变化的路由准备重定向。
  • 验证未登录的访客仍然无法访问草稿。

在新站点于生产环境中成功运行、并且不再需要回滚之前,请保留备份和 WordPress 部署。

故障排除

无法解析 XML 文件

请从 Tools → Export 重新导出文件,并确认下载已完成。不要用会改变编码的工具编辑 XML。

集合被标记为不兼容

现有字段的 slug 相同,但类型不同。请在重试之前更改文章类型的映射,或在 Content Types 下调和该字段。准备步骤会添加缺失的字段,但不会更改现有字段的类型。

机器人防护拦截了导出器

Cloudflare 的机器人防护可能会用质询页面而不是导出器 API 来响应 EmDash 的请求。EmDash 随后会报告机器人防护阻止了连接。请在迁移期间放行导出器的请求:

  • 使用 Super Bot Fight Mode 或其他 Cloudflare WAF 功能时,添加一条操作为 Skip 的自定义规则,并把它放在任何会发出质询的规则之上。选择 All Super Bot Fight Mode rules;如果该区域使用 I’m Under Attack 模式,还要选择 Security Level。下面的表达式可匹配启用和未启用固定链接美化时的导出器 API(包括安装在子目录中的 WordPress),以及 EmDash 下载的媒体文件:

    (http.request.uri.path contains "/wp-json/emdash/v1/")
    or (http.request.uri.query contains "rest_route=%2Femdash%2Fv1%2F")
    or (http.request.uri.path contains "/wp-content/uploads/")
  • Cloudflare 免费套餐上的 Bot Fight Mode 无法通过规则跳过。请在迁移期间将其关闭,并在迁移之后重新开启。

媒体 URL 下载失败

请检查来源附件是否无需 WordPress 会话即可访问。需要认证的媒体、已删除的文件,以及重定向到私有网络地址的媒体都会单独失败,并保留在媒体错误列表中。

适配主题

归档路由使用 getEmDashCollection(),slug 路由使用 getEmDashEntry()。entry.id 是路由标识符,通常就是 slug;entry.data.id 是关系辅助函数使用的已存储内容 ID。

下面的路由会渲染一篇导入的文章:

---
import { decodeSlug, getEmDashEntry } from "emdash";
import { PortableText } from "emdash/ui";

const slug = decodeSlug(Astro.params.slug);
if (!slug) return Astro.rewrite("/404");

const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) return new Response("Could not load the post", { status: 500 });
if (!post) return Astro.rewrite("/404");
---

<article>
  <h1>{post.data.title}</h1>
  <PortableText value={post.data.content} />
</article>

路由和组件的映射请阅读移植 WordPress 主题。端点契约和确切的重试边界请阅读内容导入。