G11 运维两小件:给爬虫的 robots.txt,给迷路读者的 404 页

自研三连(G08–G10)写完插件,这一期叶扬处理两个建站起就欠着的小尾巴:G04 做 SEO 时测过本站的 robots.txt 是 404——没有 robots 文件时绝大多数爬虫默认"全站可抓",所以没出事故,但 sitemap 的门牌号已经挂出去了,总不能还让 Googlebot 每次来都撞一扇没挂牌子的大门;另一边,输错地址的读者此前看到的是 GitHub Pages 默认的白地 404,和博客毫无关系。两个文件,一个管机器、一个管人,都不用写 Python、不用动配置,放进 static/ 就行。

先看现场

一、robots.txt 是什么,不是什么

robots.txt 是爬虫协议(2022 年正式成为标准 RFC 9309),放在站点根目录,用极简的"分组声明"语法告诉合规爬虫:哪些路径欢迎、哪些别碰。本站最终的文件只有七行:

# robots.txt — https://yeyangchen2009.github.io
# 全站欢迎合规爬虫收录,无任何屏蔽
User-agent: *
Allow: /

# G04 起由 sitemap_gen.py 每次构建自动刷新
Sitemap: https://yeyangchen2009.github.io/sitemap.xml

逐个字段说:

三个常见误解顺手掰正:

  1. robots.txt 不是安全措施。它只是"君子协定",恶意爬虫完全无视;真正要保护的内容得靠鉴权或 noindex,不能指望它;
  2. Disallow 挡不住已经被别处链接的 URL 被索引——协议只管"抓不抓",不管"收不收"。要消失得用 noindex 或移除工具;
  3. 文件本身必须在根路径/robots.txt),放子目录无效;协议规定爬虫只认这一个入口,且返回非 200 时行为各异(404 通常视为"无限制"但不优雅)。

二、为什么放进 static/ 就能到达站点根

G03 的 favicon、G04 的两个搜索引擎验证文件都是这么部署的,这一期把原理从源码里翻出来。构建脚本 Gmeek.py 的初始化逻辑(约 77–88 行):

if os.path.exists(self.static_dir):
    for item in os.listdir(self.static_dir):
        src = os.path.join(self.static_dir, item)
        dst = os.path.join(self.root_dir, item)
        if os.path.isfile(src):
            shutil.copy(src, dst)     # 文件:原样拷到 docs/ 根
        elif os.path.isdir(src):
            shutil.copytree(src, dst) # 目录:整目录递归复制

结合上面第 70–75 行——每次构建先把整个 docs/ 目录删掉重建,再逐字节原样复制 static/:没有文件名白名单、没有内容处理。这解释了三件事:

三、自定义 404:GitHub Pages 的规则

GitHub Pages 对自定义 404 有官方文档,规则比想象的简单,也有一个著名的坑:

还有一个 SEO 细节:错误路径返回 404 时搜索引擎知道这页无效;但 /404.html 直接访问是 200,万一被谁链了就可能被索引。所以 <head> 里主动写了:

<meta name="robots" content="noindex">

四、404 页怎么写才像"自己人"

框架模板不管 404,这页就是一个完全独立的静态 HTML,设计时叶扬定了四条规矩:

1. 资源一律绝对路径,视觉自成体系。 404 可能在 /a/b/c 任意深度触发,相对路径会全部错位,所以 favicon、首页链接、postList.json 全部 / 开头。CSS 不引 Primer(那是文章页才加载的),自己用同名 CSS 变量写了 30 行,色值直接对齐 GitHub Dark 色板(#0d1117 / #58a6ff 等)。

2. 主题与主站三态一致。 G08 讲过 Gmeek 把主题存在 localStoragemeek_theme 键里(light/dark/auto),同域页面共享。404 页开头读这个键,复刻主站的判定:dark 直接暗色,light 亮色,auto 跟 prefers-color-scheme

var t = localStorage.getItem('meek_theme') || 'auto';
var dark = t === 'dark' ||
    (t !== 'light' && window.matchMedia &&
     window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.setAttribute('data-color-mode', dark ? 'dark' : 'light');

这样读者在主站选了暗色,手滑进 404 不会被白页闪瞎。注意这是独立页的折中方案——它不监听主题切换按钮(页面上压根没有),但进入瞬间的状态永远和主站一致。

3. 迷路时给内容,不只给按钮。 这页也 fetch 了 postList.json,取最新 5 篇文章(排序逻辑与 G08/G10 同款:日期倒序、编号兜底),渲染成"或者,从最新文章里挑一篇"。读者输错地址时,被接住的最好方式是直接给他可看的东西。fetch 失败整块隐藏——降级成只有两个按钮的简洁版,绝不报错。

4. 零外部依赖。 不加载任何第三方 CSS/JS/字体,系统字体栈兜底。404 是"出错时刻",页面必须在网络最差的时候也能秒开。

页面结构就是一张居中卡片:渐变数字 404、一句叶扬式调侃("这页大概是坐时光机走丢了")、两个按钮(🏠 首页 / 🗂 归档——归档入口正好用上 G10 成果)、最新文章列表、页脚签名。

五、验证清单

检查 期望 实测
GET /robots.txt 200,text/plain,无 BOM、LF 行尾 ✅ 首字节 23 20 72# r),0 个 CR
Googlebot UA 访问 同样 200 可读
GET /404.html 200,含 noindex
不存在的根路径 404 状态码 + 自定义页内容
/post/999.html/a/b/c 404 + 自定义页(深层路径生效)
sitemap / RSS 条数不变(19 URLs),不含 404/robots
最新文章列表 #17、#15、#14、#13、#12

行尾和 BOM 值得多说一句:Windows 上 git 默认可能在工作区把 LF 转成 CRLF(提交时能看到那两条 warning),但仓库 blob 存的是 LF,Action checkout 后复制到线上的仍是干净 LF——叶扬专门 curl 下来 tr -cd '\r' | wc -c 数过,为 0。robots 解析器对 CRLF 其实也兼容,但干净永远是好习惯。

小结

两个文件,零配置,补齐了站点的两副面孔:对机器,robots.txt 显式表达收录态度、钉上 sitemap 入口;对人,404 页用同色系、同主题、最新文章把迷路读者接住。原理上就记住一句:static/ 是"原样到达站点根"的传送门,且每次构建全量重刷

顺带记录一个现场:本文发布后普通文章达到 16 篇,越过了 config 里 onePageListNum:15 的单页上限,首页底部第一次出现"下一页"分页条(框架自动生成 page2.html,最早的搭建篇被请去了第二页)。纯框架行为,无需任何操作——数字分页的美化留给 G13。

下一期 G12 做个体验小补丁:让正文中的外链自动在新标签页打开(target="_blank"rel="noopener"),内链保持当前页跳转——读者点开参考资料时不再被"逐出"博客。

参考

✍️ 原创文章,转载请注明出处~叶扬谢谢你来过 ❤️