mcpField
W
wp-static-clone

wp-static-clone

github.com/jdevalk云与 DevOps10936 次安装19 周前更新
代码仓库

关于 wp-static-clone

将 WordPress 网站转为可部署在静态主机上的 HTML 副本,面向迁移或归档网站的操作者。它以 XML sitemap 收集页面,并提供路径重写、WordPress 运行时清理及部署检查流程。

主要功能

  1. 01从 XML sitemap 获取页面
  2. 02一次抓取并改写跨页链接
  3. 03转换为根相对资源路径
  4. 04清理评论表单等运行时标记
  5. 05自托管 Gravatar 头像
  6. 06提供静态托管部署与验证流程

适用场景

  1. 01将 WordPress 网站迁移到静态托管
  2. 02保存网站页面为静态 HTML
  3. 03检查静态副本的链接和资源

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jdevalk/skills --skill wp-static-clone

安装后在 Claude Code 中输入/wp-static-clone确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

AGENTS.md
README.md
scripts/
insert-banner.py
rewrite-paths.py
selfhost-gravatars.py
strip-wp-runtime.py

详细介绍

wp-static-clone

将在线 WordPress 网站转换为静态 HTML 克隆,可部署到任意静态托管平台。根据网站的 XML 站点地图进行处理。可应对常规 wget 操作容易遗漏的 WordPress 特有问题——Cloudflare 机器人防护、抓取过程中的链接重写、代理分析、转移到 R2 的上传文件、评论表单运行时、Yoast 署名以及 Gravatar 隐私。

操作指南见 AGENTS.md;可复用脚本见 scripts/。 本文件介绍工作流、注意事项和输出结构。

适用场景

遇到以下请求时使用:

  • “抓取这个 WordPress 网站并部署到 [host]”
  • “将 [domain] 冻结为静态 HTML”
  • “从这个站点地图拉取所有页面并转换成静态文件”
  • “将这个 WP 网站迁移到 [host],不需要构建步骤”

整体流程(站点地图 → wget → 根相对路径 → 静态托管)也适用于任何能生成标准 XML 站点地图的 CMS。运行时清理(评论表单、Plausible 代理、Gravatar、Yoast)则是 WordPress 特有的。

工作流

阶段 0 — 确认意图

开始抓取前,确认以下事项:

  • 源 URL(在线网站)。
  • 目标托管平台——Cloudflare Pages、Netlify、Vercel 或通用静态托管平台。这会影响阶段 9。
  • 目标域名是否相同。这决定 og:url、<link rel="canonical"> 和 JSON-LD @id 应保留为绝对 URL(同域名——符合正确的 SEO 行为),还是进行重写(不同域名)。
  • 如何处理分析和表单。 WP 插件无法在静态网站上运行。Plausible 会替换为标准跟踪器(操作指南见 AGENTS.md);联系和搜索表单则要么移除,要么通过 Pages Functions / Formspree / Netlify Forms 接入——具体取决于托管平台。
阶段 1 — 发现 URL 并拉取 XML 站点地图

获取站点地图索引。先尝试 <root>/sitemap_index.xml(Yoast 约定),再尝试 <root>/sitemap.xml。如果索引引用了子站点地图(page-sitemap.xml、post-sitemap.xml 等),则获取每个子站点地图,并将 <loc> 值合并到 urls.txt。跳过图片站点地图条目。

同时获取 XML 站点地图本身和 Yoast XSL 样式表(操作指南见 AGENTS.md)——它们没有链接到 HTML 中,因此稍后 wget -p 不会找到它们。

阶段 2 — 一次性抓取

关键: 在单次 wget 调用中抓取所有 URL,这样 --convert-links 处理时就能看到所有已下载文件,并正确重写跨页面链接。分开运行抓取会导致先抓或后抓的页面残留绝对链接。操作指南见 AGENTS.md。

阶段 3 — 拉取页面资源抓取未覆盖的资源

有些资源不会通过 -p 跟随抓取,因为它们只出现在 og:image、apple-touch-icon、JSON-LD image/logo、msapplication-TileImage 或 <link rel="modulepreload"> 中。检查并获取遗漏的资源。操作指南见 AGENTS.md,涵盖三个资源根目录(uploads、themes、plugins)。

阶段 4 — 将路径转换为根相对路径

wget -k 会生成混合路径:../wp-content/...(相对于目录深度)和不带前缀的 wp-content/...(首页)。两者在本地都可用,但页面一旦移动就会失效。请将所有路径转换为根相对路径 /wp-content/...:

python3 scripts/rewrite-paths.py output/ urls.txt --source-domain example.com

脚本会根据 urls.txt 而不是遍历目录来派生页面 slug 列表——否则,wget 抓取的 category/、feed/、author/、wp-json/ 等归档目录会被错误地归类为页面,其页面间链接也会被错误重写。

脚本默认使用 WordPress 资源根目录(wp-content、wp-includes)。对于非 WP 来源,请通过 --asset-roots 覆盖,例如 Drupal 使用 --asset-roots sites/default/files,sites/default/themes,Ghost 使用 --asset-roots content/images。其余重写逻辑与 CMS 无关。

阶段 5 — 为静态输出添加标识

这样,未来的你(或任何查看源代码的人)都能一眼看出这是静态克隆,而不是在线 WP 安装:

python3 scripts/insert-banner.py output/

在每个页面的 <!DOCTYPE html> 后插入 HTML 注释。可重复执行而不会产生重复内容。然后替换 wp-content/plugins/wordpress-seo/css/main-sitemap.xsl 中的 “Generated by Yoast SEO” 署名——操作指南见 AGENTS.md。

阶段 6 — 替换 WP 运行时钩子

后端移除后会失效的三类仅适用于 WP 的标记:

1. 评论表单、回复链接和失效的 head 标签。 一次处理:

python3 scripts/strip-wp-runtime.py output/

移除 <div id="respond"> 块(评论表单)、区块主题和经典主题变体中的 comment-reply-link 锚点、comment-reply-js 脚本标签及其底层文件,以及失效的 <head> 标签(REST API 发现、RSD、oEmbed alternate、RSS alternate、归档 next 链接)。始终按 class 匹配,不依赖链接文本的语言。

2. Plausible 分析代理。 WP 插件通过 /wp-content/uploads/<hash>/pa-XXX.js 代理脚本,并将事件发回 /wp-json/...。这两个端点都会消失。将这段双脚本代码块替换为标准跟踪代码——配方见 AGENTS.md。

3. Gravatar 头像。 将每个不同的 (hash, size) 组合自行托管,移除发往第三方的 ?s=N&d=mm 请求:

python3 scripts/selfhost-gravatars.py output/

将文件保存到 avatars/ 并重写每个引用。根据响应字节检测扩展名(PNG 兜底,真实头像为 JPEG),并为不同尺寸保留单独文件(?s=40 和 ?s=80 对应不同文件)。

运行脚本后,审查剩余的绝对源域名 URL(配方见 AGENTS.md),并按情况分类处理:作者归档页 → 移除 <a> 包装标签,服务器渲染的 iframe → 移除外层 <p>,Gravity Forms 脚本块 → 提及 gform 时移除,等等。

阶段 7 — 复制 robots.txt

HTML 中没有链接到它;请显式获取。如果部署后的网站地图路径与源路径不同,请调整 Sitemap: 引用。

阶段 8 — 在本地验证

使用 python3 -m http.server 从 output/ 提供服务,然后运行 AGENTS.md 中的验证清单:

  1. urls.txt 中的每个 URL 都能解析到文件(没有遗漏页面)。
  2. 规范 URL / og:url / JSON-LD 允许列表以外,不再有 https://<source-domain>/。
  3. wget --spider 未发现损坏的内部链接。
  4. 在浏览器中抽查首页和一个深层页面。留意 srcset 图片、侧边栏小工具和页眉横幅——遗漏时这些问题会悄无声息地出现。
阶段 9 — 部署

主机专属配方见 AGENTS.md:

  • Cloudflare Pages — _redirects、_headers、“无构建命令、无输出目录”的默认设置。
  • Netlify — 同样的 _redirects / _headers 语法,以及 netlify.toml。
  • Vercel — 使用 redirects / headers 的 vercel.json。
  • 通用 — nginx try_files、Apache Options +MultiViews。

易踩问题

以下是我们遇到的问题。请勿重蹈覆辙。

  1. Cloudflare 的机器人防护会对默认的 Wget/1.x UA 返回 403。 始终设置真实浏览器 UA 和 Accept / Accept-Language 标头(参见操作步骤)。如果在一阵密集请求后看到 403 Forbidden,就是这个原因——暂停请求、更换 UA,然后重试。

  2. 只有在单次 wget 调用中,跨页面链接重写才有效。 wget 的 -k 只会将链接重写为当前运行中看到的本地路径。如果某个页面是在单独一次调用中下载的(例如,为恢复某个 URL 的 403 而单独下载),其中指向其他页面的链接就会保持绝对路径。解决方法:设置好正确的 UA 后,重新完整抓取一次。不要分批零敲碎打。如果要大规模抓取(10K+ 个 URL),且无法在一次运行中完成,请分批抓取,然后重新运行 scripts/rewrite-paths.py,将其作为最终统一处理步骤——此时 -k 的输出就多余了。

  3. 不同主机的默认发布目录。 如果未配置构建命令,Cloudflare Pages 会提供仓库根目录的内容。Netlify 和 Vercel 的默认目录也都是根目录。如果抓取到 output/,要么将文件移到仓库根目录(git mv output/* .),要么配置主机从 output/ 发布。Pages 配置错误时的表现:每个 URL 都返回 404,并带有 R2 风格的标头(access-control-allow-origin: *、cache-control: no-store),而不是带有 Pages 品牌标识的 404。

  4. WordPress Offload Media 插件会将 /wp-content/uploads/ 路由到 R2 / S3 存储桶。即使之后直接访问会返回 404,wget 仍可能成功获取图片(存储桶同步可能是间歇性的或不完整)。请相信本地副本——这正是我们抓取并自行托管的原因。

  5. Sitemap 和 Yoast XSL 不会从 HTML 中链接。 wget -p 找不到它们。请在第 1 阶段显式获取。

  6. 带有 ?ver=... 查询字符串的文件名。 wget 会将其保留为字面文件名;HTML 则使用 %3F 编码。标准服务器(Pages、Netlify、Vercel、python -m http.server)会进行 URL 解码并正确提供文件。除非确实出现问题,否则不要尝试“清理这些文件名”。

  7. og:url、canonical 和 JSON-LD 保持绝对形式。 它们用于标识规范资源;如果重新部署到同一域名,这样写就是正确的。只有更换域名时才需要重写。

  8. sed -i '' 仅适用于 macOS / BSD。 GNU sed 需要使用 sed -i(不带空字符串参数)。AGENTS.md 中的操作步骤会标出 macOS 特有用法;如果有选择,默认使用 Python 脚本——它们具有可移植性。

输出结构

<repo-root>/
  index.html                 ← homepage
  <slug>/index.html          ← one per URL from sitemap
  wp-content/                ← assets (themes, uploads, plugins)
  wp-includes/               ← block library CSS, et al.
  avatars/                   ← self-hosted Gravatars (Phase 6)
  sitemap_index.xml
  page-sitemap.xml           ← + any other child sitemaps
  wp-content/plugins/wordpress-seo/css/main-sitemap.xsl
  robots.txt
  _redirects                 ← optional, host-specific
  _headers                   ← optional, host-specific

推送到 git 主机,并连接到静态主机;不设置构建命令,也不设置构建输出目录——默认设置即可。

评论

支持 Markdown