跳到主要内容
BLKBLKTECH

资源 / resources

SEO Skills 工具箱:说明书、规则注册表与上线前检查清单

SEO 审计 Skill 的完整可复制资源:目录结构、SKILL.md 说明书全文、30 条规则注册表、特性开关与白名单模板、两套 CI 配置,以及自动化覆盖不到的人工检查清单。

作者 BLKTECH 编辑部更新 2026年8月1日16 分钟难度 实战免费Node.jsBunAI AgentGitHub Actions

这套资源包含一份跨平台通用的 SKILL.md 说明书、30 条带 error/warn 分级与特性开关的规则注册表、白名单与阈值配置模板、两套 CI 配置,以及一份自动化覆盖不到的人工检查清单。检测脚本的实现分散在系列六篇正文中,本页只收录可直接复制的成品件。

这份资源是什么

「SEO Skills 工具箱」系列六篇正文讲了怎么从零建一套 SEO 审计体系。这一页把其中可直接复制的成品件集中起来,省去在六篇文章里翻找。

分工很清楚:

内容 在哪
说明书、规则表、配置、CI、检查清单 本页,完整给出
各项检查的实现代码与原理 系列正文,本页只给索引

不在这里重复贴检测脚本,是因为那些代码需要配着「为什么这么写」才有意义——脱离上下文抄过去,遇到第一个误报就不知道该改哪里了。


一、完整目录结构

项目根目录/
├── seo/
│   ├── rules.js            # 规则注册表(本页第三节,完整)
│   ├── config.js           # 特性开关 + 白名单(本页第四节,完整)
│   ├── runner.js           # 执行器(系列⑥ 第二节)
│   ├── load.js             # 产物加载与归一化(系列⑥ 第三节)
│   └── checks/
│       ├── index.js        # 各检查函数汇总导出
│       ├── page.js         # scope=page  的检查
│       ├── site.js         # scope=site  的检查
│       ├── file.js         # scope=file  的检查
│       └── cross.js        # scope=cross 的检查
├── references/
│   ├── thresholds.json     # 长度阈值(本页第五节)
│   ├── schema-rules.json   # JSON-LD 必填字段表(系列③ 第六节)
│   └── consistency-map.json# 标记与页面的对照表(系列③ 第六节)
├── .claude/skills/seo-audit/
│   └── SKILL.md            # 说明书(本页第二节,完整)
└── .audit/                 # 产出目录,加进 .gitignore
    └── seo.json

.audit/ 一定要加进 .gitignore。它是每次构建的产物,提交进仓库只会制造无意义的 diff。


二、SKILL.md 说明书(完整,可直接复制)

这份说明书是跨平台通用的。放进 .claude/skills/seo-audit/SKILL.md 即为 Claude Code 的 Skill;内容原样搬进 .cursor/rules/seo-audit.mdcAGENTS.md 的一节同样能用——系列①有各平台的落地位置对照。

---
name: seo-audit
description: 解读 SEO 审计结果,定位根因并给出修复方案。当用户运行 validate
  或 seo:audit 后出现失败、询问 SEO 检查报错的含义、需要修复审计问题,或要求
  做上线前 SEO 检查时使用。
---

## 职责边界

**只解释,不检测。** 检测由 `seo/runner.js` 完成,本 Skill 读取它的输出。

- ✅ 读 `.audit/seo.json`,归纳根因,给修复方案
- ❌ 不自己扫描 HTML 寻找问题
- ❌ 不重新实现任何一条已有规则

原因:检测要求 100% 覆盖与结果稳定,这是脚本的强项;解释要求理解代码
结构与业务意图,这才是模型的位置。

## 执行步骤

1. 检查 `.audit/seo.json` 是否存在,以及是否晚于最近一次构建。
   缺失或过期时先执行 `npm run validate`
2. 读取 JSON。`summary` 给总量,`findings` 给明细。
3.`id` 分组,再判断哪些分组属于同一个根因。
4. 对每个根因定位到**源码位置**(不是产物位置)。
5. 按下方「输出格式」生成报告。
6. 除非用户明确要求,不要直接改代码。

## 根因定位规则

判断依据是**受影响页面的分布**,不是问题类型:

| 受影响范围 | 通常的根因位置 |
|---|---|
| 全部页面 | 布局文件(BaseLayout 等) |
| 某个栏目的全部页面 | 该栏目的页面模板或 content 配置 |
| 若干页面,无明显共性 | 逐页的 Markdown / frontmatter |
| 只在动态路由页出现 | 路由参数处理逻辑 |
| sitemap / robots 相关 | 构建配置(astro.config.mjs 等) |

同一个 `id` 出现 3 次和出现 60 次,根因位置完全不同。**先看分布,再找文件。**

## 误报处理

以下情况先标记为「需确认」,不要直接当作缺陷:

- 目标域名在 `seo/config.js` 的 ALLOWLIST 中
- 外链返回 401 / 403 / 429(可能是反爬或限流,浏览器能打开即为误报)
- 页面内容需 JS 渲染后才出现(静态审计覆盖不到,应在说明中注明)

确认为稳定误报的,追加进 ALLOWLIST 并写明**日期与原因**

## 输出格式

按根因分组。每组给出:

1. **一句话问题描述** + 影响页面数
2. **根因位置**:文件路径与行号
3. **具体改法**:能给 diff 就给 diff
4. **为什么是这些页面**:解释受影响范围的成因
5. **验证方式**:改完跑什么命令、期望看到什么

`warn` 级别默认只列出计数,不展开修复方案,除非用户问起。

不要逐条罗列 findings——JSON 里已经有了,人要的是分组后的结论。

## 拿不准时

宁可输出「影响这 3 个页面,需要确认是否共用同一模板」,
也不要猜一个具体文件路径。**猜错路径的报告比不给路径的报告更糟**——
人照着改一次发现不对,之后就不会再信任这个 Skill 了。

三、规则注册表(30 条,完整)

// seo/rules.js
export const RULES = [
  // ── HTML 基础 ──
  { id: 'html-lang',         level: 'error', scope: 'page',  desc: '中文路径 lang="zh",其余 lang="en"', when: 'i18n' },
  { id: 'h1-exactly-one',    level: 'error', scope: 'page',  desc: '每页恰好一个 <h1>' },
  { id: 'title-present',     level: 'error', scope: 'page',  desc: 'title 存在且非空' },
  { id: 'title-duplicate',   level: 'warn',  scope: 'site',  desc: 'title 跨页面重复' },
  { id: 'title-width',       level: 'warn',  scope: 'page',  desc: 'title 宽度在 30–60 半角当量内' },
  { id: 'desc-exactly-one',  level: 'error', scope: 'page',  desc: 'meta description 恰好一个且非空' },
  { id: 'desc-duplicate',    level: 'warn',  scope: 'site',  desc: 'description 跨页面重复' },
  { id: 'desc-width',        level: 'warn',  scope: 'page',  desc: 'description 宽度在 70–155 半角当量内' },
  { id: 'img-alt',           level: 'error', scope: 'page',  desc: '每个 <img> 有非空 alt' },

  // ── 索引指令 ──
  { id: 'canonical-one',     level: 'error', scope: 'page',  desc: 'canonical 恰好一个' },
  { id: 'canonical-self',    level: 'error', scope: 'page',  desc: 'canonical 等于当前正式 URL' },
  { id: 'canonical-noquery', level: 'error', scope: 'page',  desc: 'canonical 不带查询参数' },
  { id: 'canonical-target',  level: 'error', scope: 'cross', desc: 'canonical 目标页面存在' },
  { id: 'robots-meta-one',   level: 'error', scope: 'page',  desc: 'robots meta 恰好一个且无冲突指令' },
  { id: 'noindex-sitemap',   level: 'error', scope: 'cross', desc: 'noindex 页面不得进入 sitemap' },

  // ── 多语言(单语站关闭)──
  { id: 'hreflang-set',      level: 'error', scope: 'page',  desc: '含 en / zh-CN / x-default', when: 'i18n' },
  { id: 'hreflang-target',   level: 'error', scope: 'cross', desc: 'hreflang 目标页面存在',     when: 'i18n' },

  // ── 链接 ──
  { id: 'internal-404',      level: 'error', scope: 'cross', desc: '站内链接对应真实产物' },
  { id: 'anchor-exists',     level: 'warn',  scope: 'cross', desc: '锚点在目标页面存在' },
  { id: 'trailing-slash',    level: 'warn',  scope: 'cross', desc: '内链尾部斜杠与产物一致' },
  { id: 'no-param-link',     level: 'error', scope: 'page',  desc: '禁止遗留 ?from= 内链' },

  // ── robots.txt ──
  { id: 'robots-exists',     level: 'error', scope: 'file',  desc: 'robots.txt 存在且非全站 Disallow' },
  { id: 'robots-sitemap',    level: 'error', scope: 'file',  desc: 'robots.txt 声明 sitemap 且目标存在' },

  // ── sitemap ──
  { id: 'sitemap-index',     level: 'error', scope: 'file',  desc: 'sitemap-index 存在、命名空间正确' },
  { id: 'sitemap-clean',     level: 'error', scope: 'file',  desc: 'URL 不重复、不带参数或锚点' },
  { id: 'sitemap-sync',      level: 'error', scope: 'cross', desc: 'sitemap 与产物双向一致' },
  { id: 'sitemap-lastmod',   level: 'error', scope: 'file',  desc: 'lastmod 恰好一个、格式正确、非未来时间' },

  // ── 图片 sitemap(无图片 sitemap 时关闭)──
  { id: 'imgmap-ns',         level: 'error', scope: 'file',  desc: '图片 sitemap 命名空间正确',        when: 'imageSitemap' },
  { id: 'imgmap-count',      level: 'error', scope: 'file',  desc: '每条目 1–1000 张图片',             when: 'imageSitemap' },
  { id: 'imgmap-url',        level: 'error', scope: 'file',  desc: '图片 URL 为 http(s)、不重复无参数', when: 'imageSitemap' },
  { id: 'imgmap-file',       level: 'error', scope: 'cross', desc: '本地图片文件真实存在',             when: 'imageSitemap' },
  { id: 'imgmap-onpage',     level: 'error', scope: 'cross', desc: '图片实际出现在所属页面中',         when: 'imageSitemap' },
  { id: 'imgmap-lastmod',    level: 'error', scope: 'cross', desc: '图片 lastmod 与页面 sitemap 一致', when: 'imageSitemap' },
  { id: 'imgmap-legacy',     level: 'warn',  scope: 'file',  desc: 'image:title / image:caption 已弃用', when: 'imageSitemap' },

  // ── 结构化数据 ──
  { id: 'jsonld-valid',      level: 'error', scope: 'page',  desc: '每个 JSON-LD 块是合法 JSON' },
  { id: 'jsonld-required',   level: 'error', scope: 'page',  desc: '按 schema-rules.json 校验必填字段' },
  { id: 'jsonld-scope',      level: 'error', scope: 'page',  desc: 'WebSite / Organization 仅出现在首页' },
  { id: 'article-type',      level: 'error', scope: 'page',  desc: '文章页含 Article 或 BlogPosting' },
  { id: 'no-searchaction',   level: 'warn',  scope: 'page',  desc: '不声明不存在的 SearchAction' },
];

四个字段的含义:id 是稳定标识(也是白名单的键),level 决定 error 阻断构建 / warn 只报告,scope 决定执行阶段(filepagesitecross),when 是特性开关。


四、配置模板

// seo/config.js

// 特性开关:按站点实际情况打开
export const FEATURES = {
  i18n: false,           // 多语言站点(启用 lang / hreflang 相关 4 条规则)
  imageSitemap: false,   // 有图片 sitemap(启用 imgmap-* 共 7 条规则)
};

// 白名单:键为规则 id,值为豁免的目标
// 每条必须写明【日期】和【原因】,否则半年后没人知道为什么在这里
export const ALLOWLIST = {
  'title-duplicate': [
    '/search/',          // 2026-08-01 搜索页与首页同标题,已 noindex,可接受
  ],
  'internal-404': [
    // 示例:'/legacy/old-page/',  // 2026-08-01 待迁移,跟踪于 #42
  ],
  'img-alt': [
    // 示例:'/about/',            // 2026-08-01 装饰性图片,待补 alt
  ],
};

特性开关这一步不能省。 上面 30 条规则里有 11 条只对多语言站或有图片 sitemap 的站成立。硬塞给不适用的站点,会立刻产生 11 类必然失败——人的第一反应就是把整个检查关掉,然后这套东西就废了。


五、阈值配置

// references/thresholds.json
{
  "brand": "BLKTECH",
  "title":       { "min": 30,  "max": 60  },
  "description": { "min": 70,  "max": 155 },
  "headline":    { "max": 110 },
  "clickDepth":  { "max": 3 },
  "redirectChain": { "max": 2 }
}

单位是半角当量不是字符数:全角字符算 2,其余算 1。中英文混排必须这样算,否则中文站的截断问题几乎全部漏检——原理和实现见系列②

brand 用于跨页重复检测时剥离共同的品牌后缀。文章名 - 站名 这类模板,不剥后缀就永远发现不了真重复。

这些阈值是社区长期测量的经验值,不是官方规范,会随搜索引擎改版波动。放在配置文件里而不是硬编码进脚本,改阈值不该需要改代码。


六、CI 配置

两套,触发时机和阻断策略完全不同。

每次构建:内链与产物一致性

# .github/workflows/validate.yml
name: validate
on: [push, pull_request]
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install
      - run: bun run validate          # build + seo:audit,error 时退出码 1
      - uses: actions/upload-artifact@v4
        if: always()                   # 失败时更需要这份 JSON
        with: { name: seo-audit, path: .audit/seo.json }

定时:外链与结构指标

# .github/workflows/periodic-audit.yml
name: periodic-audit
on:
  schedule:
    - cron: '0 3 * * 1'   # 每周一:外链探测
    - cron: '0 4 1 * *'   # 每月一日:内链结构
  workflow_dispatch:
jobs:
  periodic:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: bun install && bun run build
      - run: bun seo/check-external.mjs || true    # 外链失效不阻断
      - run: bun seo/link-graph.mjs     || true    # 结构指标不阻断
      - uses: actions/upload-artifact@v4
        with: { name: periodic-audit, path: .audit/ }

两个 || true 是刻意的。外链失效不是你的错、第三方站点抖动频繁;结构指标没有唯一正确答案。只要有一次因为别人的服务器打嗝导致你发不了版,这个检查就会在一周内被注释掉。


七、脚本实现索引

本页不重复贴实现代码。各检查的原理与完整实现:

检查范畴 正文 关键实现
内链 404、锚点、尾斜杠、外链探测 ① 死链审计 crawl-local.mjscheck-external.mjs
title / description 宽度与重复 ② Meta 审计 display-width.mjsextract-meta.mjs
JSON-LD 三层校验 ③ 结构化数据 extract-jsonld.mjs
sitemap 集合运算与信号矛盾 ④ Sitemap 差异 sitemap-diff.mjs
点击深度、入链分布、连通分量 ⑤ 内链结构 link-graph.mjs
runner、loader、四种 scope ⑥ 内置 audit runner.jsload.js

八、分阶段落地清单

不要一次上 30 条规则。

  • 阶段 1:跑通 runner + 3 条最确定的规则(h1-exactly-onetitle-presentinternal-404
  • 阶段 2:把现有问题全部修掉或全部加白名单,让基线归零
  • 阶段 3:接进 CI,此时 CI 应该是绿的
  • 阶段 4:每次加 2–3 条规则,修完再加下一批
  • 阶段 5:补上 SKILL.md,让 Agent 承担解读
  • 阶段 6:加定时任务,跑外链与结构指标

阶段 2 是成败关键。基线不归零就接 CI,等于 CI 从第一天起就是红的,之后所有人都会习惯性忽略它——包括你自己。宁可先把已知问题加进白名单标上 TODO,也不要让 CI 带着一屏红色上线。


九、自动化覆盖不到的部分

这是整套东西最该说清楚的一节。上面所有工具加起来,覆盖的是技术正确性,不是内容有效性

以下事项无法自动化,需要人工定期做:

上线前抽查(每次发布)

  • 用 Rich Results Test 抽查 1–2 个代表页面(无公开 API,只能手动)
  • 移动端实际打开,确认渲染正常
  • 确认首屏关键内容不依赖 JS 渲染(静态审计看不到 JS 之后的内容)
  • 新页面的 title 与 description 自己读一遍,是不是套话

周期性复核(每月 / 每季度)

  • Search Console 的「网页索引编制」报告——权威但滞后,本地审计替代不了
  • Core Web Vitals 实测数据(需要真实用户环境)
  • 外链失效后替代目标是否合适(脚本只知道断了,不知道该换成什么)
  • 搜索引擎政策变化——尤其结构化数据的富媒体资格规则,建议每年复核并更新 references/

根本上自动化不了的

  • 内容本身是否匹配搜索意图
  • 内容质量与竞品的相对水平
  • 内链分布是否符合你的内容战略(工具能算分布,判断不了战略)

一句实话作为收尾:

技术审计全绿的站,完全可以没有任何排名。

这套 Skill 保证的是「没有技术问题拖后腿」,不是「能拿到流量」。它的价值在于把技术问题彻底从变量列表里划掉——这样当排名不理想时,你知道该去看内容,而不是怀疑是不是哪里有个 404。


十、系列导航

「SEO Skills 工具箱」完整系列:

  1. 死链与断链审计
  2. Meta 审计:title、description、H1
  3. 结构化数据与 JSON-LD 校验
  4. Sitemap 差异审计与孤儿页发现
  5. 内链结构审计
  6. 内置 seo:audit 与 Skill 封装
  7. 当前:配套资源

七条原则:

  • 确定性交给脚本,判断交给模型(①)
  • 能量化的写成阈值表,不能量化的给判断依据(②)
  • CI 的阻断线画在「有唯一正确答案」的地方(②③④⑤)
  • 报告要给根因,不是给症状列表(③)
  • 集合运算前先做归一化——脚本不会报错,只会安静地全错(④)
  • 算图指标前先剔除模板化链接(⑤)
  • 凡是能用规则表达的判断,都不该交给模型执行(⑥)

相关阅读:

NEXT ACTION / 下一步

从系列第一篇开始读

把读到的方法变成一个小行动,完成后再回来迭代。

继续

RELATED / 相关推荐

接着读这些

按同一栏目、标签与技术栈为你挑选。