博客源码丢了,我从线上站点把它反推了回来
RSIC Lv2

事情是这样的:想给博客改点东西,打开电脑才发现——本地的 Hexo 工程目录不见了。

一开始还比較淡定,想着”GitHub 上应该有吧”。结果一查,事情比想象的有意思。

先确认:源码到底还在不在

GitHub 仓库里只有”成品”

打开 ranshuo-ICer.github.io 这个仓库,里面是这些东西:

1
index.html  archives/  categories/  tags/  css/  js/  font/  image/  images/  pdf/  search.xml

眼熟吗?这就是 hexo generate 出来的 public/ 目录,也就是部署产物,不是源码。

再看提交历史:一共 20 个提交,全部叫 Site updated: 2023-xx-xx xx:xx:xx第一个提交的目录树里只有一个 placeholder 文件。分支也只有 master 一个。

结论很明确:源码从来没有进过这个仓库。当初用 hexo deploy 推上去的,只是生成好的静态页面。

本机也翻遍了

  • 全盘(C/D/E/F/G)扫描 _posts 目录、*hexo* 文件:没有
  • 回收站:没有
  • OneDrive、Documents、Desktop、Downloads、Kuaipan:没有
  • WSL、hexo 命令、npm 安装日志、编辑器”最近打开”:都没有痕迹

说明这个博客当初是在另一台机器(或者另一个环境)上搭的,本机从来没留下过源码。

原始工程文件确实找不回来了。

那就反过来:从成品推出源码

好在站点还在线,而静态站点里其实藏着几乎全部信息。关键是别急着”手抄”,要去找那些机器能读出来的痕迹

1. 主题配置直接内嵌在页面里

Keep 主题(其它主题也类似)会把配置序列化到页面里,方便前端 JS 使用:

1
2
3
4
<script id="hexo-configurations">
KEEP.hexo_config = {"hostname":"...","root":"/","language":"zh-CN","path":"search.xml"}
KEEP.theme_config = {"toc":{...},"style":{"primary_color":"#0066cc",...},...}
</script>

把这段 JSON 拿出来,主题配置基本就齐了:配色、logo/头像/favicon、首屏签名、菜单、本地搜索、PJAX、lazyload、评论(giscus 的 repo / category id 都在里面)。

2. 用”特征”把主题版本钉死

CDN 链接会暴露主题版本:

1
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/hexo-theme-keep@3.6.1/source/font/css/fontawesome.min.css">

但光有版本号不够——npm 上发布的 3.6.1 并不带 giscus 评论,而线上站点有。于是去比对主题仓库的历史:

  • 线上 css/style.css没有 .page-numbers(说明还没加”页码”特性)
  • 页脚没有全站字数
  • 但评论用的是 giscus

结果正好卡在主题仓库的 edbb953(2023-01-18)这个提交上:package.json 里还写着 3.6.1,但已经有 giscus 支持。把这一版导出成 themes/keep 就还原了。

找这类”版本特征”比猜版本号靠谱得多:少一个 CSS 类、多一段模板,都是硬证据。

3. 正文:代码块、图片都有自己的”写法指纹”

正文 HTML 里有不少线索:

  • 代码块是 <figure class="highlight bash"> → 说明原文是 bash ``` 围栏;highlight plaintext 对应”没写语言的围栏”,而 <pre><code> 对应四个空格缩进的代码块——两者渲染结果不一样
  • 图片被主题的 lazyload 过滤器改写过,会出现”两个 alt 属性”:
1
2
3
4
5
<!-- 原文是 ![1680612882454](xxx.png) -->
<img lazyload alt="image" data-src="xxx.png" alt="1680612882454">

<!-- 原文是原生 <img src="xxx.png"> -->
<img lazyload alt="image" data-src="xxx.png">

按这个规律就能分别还原成 Markdown 图片和原生 HTML 图片,连 alt 文本都不会丢。

(有意思的是,重复的 alt 属性在真正的 HTML 解析器里会被丢掉,所以这个判断得在原始文本上做,不能先解析再判断。)

4. 最麻烦的是公式

文章里的数学公式,页面中是 MathJax 渲染后的 SVG,原始 LaTeX 并没有保留。但 SVG 里有两个关键信息:

  • data-mml-node="mfrac" / "msqrt" / "msubsup" / "munderover" / "mtable" → 完整的 MathML 结构
  • 每个字形 <path data-c="1D707"> → 字符编码(十六进制码点)

于是写个脚本按结构递归重建 LaTeX:mfrac\frac{}{}msqrt\sqrt{}msubsup_{}^{}mtable\begin{matrix}…… 最后再把字形码点还原成字符。

踩过的几个坑,记录一下:

  • MathJax 的 msubsup 子节点顺序是 [底, 上标, 下标],不是直觉上的”先下标后上标”
  • 重建出来的 \sum_{n}^{j=0} 如果写成 \sum _{n}(宏后面多一个空格),Markdown 会把 _ 当成强调符号吃掉,公式直接渲染失败
  • 矩阵的行分隔符 \\ 会被 Markdown 转义成 \,得写成 \\\\ 才能活着走到 MathJax
  • 大调色:\log 这种”正体”运算符和斜体的 log 在 SVG 里码点不同,能区分出来

5. 最后用”逐文件对比”验收

重建完不是靠眼睛看,而是把新构建的 public/ 和线上仓库逐文件 diff:

1
2
npx hexo clean && npx hexo generate
# 再和线上克隆逐个文件比对(忽略空白差异)

结果:96 个文件对 96 个文件,一个不多一个不少;其中 67 个逐字节相同或只有空白差异,剩下的差异都能解释(比如页脚年份取的是构建当年、几处字数统计、两个矩阵公式的行间距)。

现在的工程

1
2
3
4
5
6
7
8
9
10
blog-source/
├── _config.yml # 站点配置
├── _config.keep.yml # 主题配置
├── package.json
├── scaffolds/
├── source/
│ ├── _posts/ # 10 篇文章(保留原有子目录结构)
│ ├── tags/ categories/
│ ├── image/ images/ pdf/
└── themes/keep/ # 主题快照

日常还是老几样:

1
2
3
npx hexo server      # 本地预览
npx hexo generate # 生成 public/
npx hexo deploy # 部署

教训

  1. 源码要单独放一个仓库(或者至少本地 + 云端各留一份)。hexo deploy 推上去的永远是 public/ 的产物,产物里没有 Markdown、没有配置、没有主题改动
  2. 顺手搞清楚了:hexo deploy 底层是 git push --force,会覆盖远端历史。想保留历史就 clone 下来、覆盖工作区、普通 push
  3. 静态站点是”可反推”的:只要站点还在线,文章内容、图片、代码块、甚至公式都有办法救回来。但能救回来 ≠ 不用备份,反推一次的成本比备份高太多了

这次算是把博客从”尸体”里捞了回来,也顺手把还原过程写成工具留着了。

 评论