这次整理博客的起点有点狼狈:网站还能打开,GitHub 仓库也在,但原来的 Hexo 本地工程找不到了。

仓库里留下的不是 Markdown,而是一堆已经生成好的 HTML、CSS 和 JavaScript。换句话说,房子还在,图纸没了。文章在网页上都能看,可我没法正常修改,也没法继续用 Hexo 写新文章。

好在静态网页并不是完全没用。只要文章发布过,正文、标题、日期、标签、分类这些信息大多还藏在 HTML 里。这次折腾下来,我把原来的文章重新整理成了一个能安装、能生成、能本地预览,也能继续发布的 Hexo 工程。

先别动远端

刚开始最应该做的事不是重装 Hexo,而是把还活着的静态站点完整留一份。

我把项目拆成了两个目录:

1
2
3
BLOG/
├── hexo-old-web/ # 从 GitHub 拉下来的旧网页
└── hexo-source/ # 重新恢复的 Hexo 工程

hexo-old-web 就当现场备份,不在里面直接改东西。后面的解析和转换全部输出到 hexo-source。这样即使恢复脚本写坏了,也不会把仅剩的一份网页覆盖掉。

当时如果直接在原仓库里一边移动文件、一边试着重建,后面很难分清哪些是旧文件,哪些是新生成的文件。把输入和输出隔开,确实省了不少麻烦。

HTML 还能反推回 Markdown

恢复方法参考了 Hexo-Phantom-Res。大致思路并不神秘:先用 BeautifulSoup 找到 Butterfly 页面里的文章正文,再用 markdownify 转回 Markdown。

真正麻烦的是网页里不只有正文。代码块已经被主题拆成了很多标签,图片可能经过懒加载处理,文章末尾还有版权、分享和上一篇下一篇等组件。直接把整块 HTML 转成 Markdown,结果会很乱。

所以恢复脚本先做了一遍清理:

  • 从页面的 meta 和文章头部取回标题、时间、分类、标签与封面;
  • 把 Butterfly 生成的代码表格重新拼成 Markdown 代码块;
  • 去掉版权、分享、翻页等不属于文章的内容;
  • 把懒加载图片地址换回普通图片地址;
  • 最后补上 Hexo 需要的 front matter。

实际执行的是:

1
2
3
4
python .\hexo-old-web\tools\restore_hexo_source.py `
--source .\hexo-old-web `
--output .\hexo-source `
--force

脚本跑完以后,文章数量对上了,但这还远远不算结束。我又逐篇检查了标题层级、代码块、表格、图片和公式。机器转换最容易在这些地方出问题,尤其是代码块,一旦换行丢了,看起来还在,复制出来却根本不能跑。

最后保留了 20 篇文章,原来那篇电路与模电的笔记按计划删掉了。

页面恢复了,配置还得重新猜

文章可以从 HTML 里扒出来,主题配置却没办法原样恢复。网页只能告诉我“最后显示成什么样”,不会告诉我当时 _config.butterfly.yml 具体写了什么。

这部分只能对着旧站一点点补:背景图、头像、菜单、页脚、搜索、标签和分类。后来又加回了 404 页面、图片灯箱、Mermaid、KaTeX,以及浏览器原生的 loading="lazy"

一言也是这样。旧站首页原本会从一言接口取一句话,恢复后只剩静态效果。我重新接了:

1
https://v1.hitokoto.cn/?c=b

拿返回结果里的 hitokotofrom 做打字动画:先显示句子,删掉,再显示出处,然后继续循环。

还有一个挺隐蔽的问题是关于页。文件存在,Hexo 也能正常生成,点进去却是空的。查完才发现 source/about/index.md 只有 front matter,正文一个字都没有。Hexo 对此不会报错,它只会忠实地生成一个空页面。

现在关于页只留了两句话:

一个热爱人工智能的人
追逐梦想,无限进步。

顺手做成了自适应的极简页面,关于页不再显示侧栏,手机上也不会横向溢出。

我把整个源码推上去了

恢复过程中最危险的错误出在发布。

我当时说“提交推送”,结果操作时把整个本地仓库都当成了要发布的内容。hexo-sourcehexo-old-web 这些目录一起进了远端 main。但这个仓库的 main 原本只用来放 GitHub Pages 静态网页,根本不该出现整套源码和备份。

发现以后,先把远端回滚到了上一个站点版本,本地文件保持不动。然后进入 hexo-source,重新用 Hexo 发布:

1
npm run deploy

这个命令会先清理并生成 public,再由 hexo-deployer-git 把生成后的网页推到 main。它发布的是 HTML、CSS、JavaScript 和图片,不是整个 hexo-source

这两种操作看起来都叫“推送”,实际完全不是一回事:

1
2
3
git add .
git commit
git push

上面推的是当前 Git 仓库里被跟踪的内容。

1
npm run deploy

这个发布的是 Hexo 新生成的静态站点。对我现在的仓库结构来说,以后更新网页应该用后者。

源码当然也应该进 Git,只是不能再和 Pages 成品混在同一个发布分支里。比较合适的做法是给源码单独建一个分支,或者干脆放进另一个私有仓库。

GitHub Pages 还卡了一下

重新发布后,Actions 又报了这个错误:

1
Deployment request failed due to in progress deployment

一开始看着像新版本发布失败,其实 Hexo 已经生成成功,GitHub Pages 的 artifact 也上传了。问题是前一个提交还处于部署中,GitHub 不允许同一个站点同时开始第二次部署。

这和之前连续回滚、发布有关。等旧任务结束,再重新运行失败的 workflow 就可以。如果旧部署一直不结束,就去 Pages 的部署记录里把它取消。

日志里那些 punycode、Node.js deprecated 警告也很显眼,但它们不是这次失败的原因。看 Actions 日志还是得先找真正的 Error,否则很容易被旁边的警告带跑。

现在怎么写、怎么发

恢复后的日常流程其实很短。

新文章放进:

1
hexo-source/source/_posts/

写完先构建:

1
npm run build

本地看一下:

1
npm run server

确认没问题再发布:

1
npm run deploy

标签、分类和搜索不需要另外维护。Hexo 每次生成站点时都会重新读取所有文章,标签页、分类页和 search.xml 也会跟着更新。

这次能把博客救回来,主要还是因为发布后的 HTML 还在。但 HTML 只能救回已经公开过的内容,没发布的草稿、原始配置和没被引用的本地图片,丢了就真的很难找。

所以折腾完以后,我最先想做的不是再加插件,而是把 source、两份配置文件、package.jsonpackage-lock.json 和自定义资源好好备份起来。至于 node_modulespublic.deploy_gitdb.json,这些本来就能重新生成,没必要一起存。

这篇文章本身就是恢复完成后的第一篇新文章。能重新在 _posts 里写 Markdown,然后看着它出现在首页、标签和搜索里,说明这次博客算是真的救回来了。