把死链审计做成 Skill 的正确拆法是:抓取链接和采集状态码交给确定性脚本,判断误报、分类和给修复动作交给模型,中间用一份写清判定规则与输出格式的说明书连接。内链断链应阻断构建,外链失效只定期报告。
先说结论
死链检查是典型的「重复执行、判定标准明确、输出格式固定」的活。这类活不该每次重新写提示词,应该固化成一个 Skill。
但真正决定这个 Skill 好不好用的,不是它跑在哪个平台上:
Skill 的价值 90% 在那份「判定规则 + 输出格式」的说明书里,平台只是投放位置。
同一份死链审计说明书,放进 Claude Code 是 SKILL.md,放进 Cursor 是一条 rule,放进 Codex 是 AGENTS.md 的一节,放进自建 Agent 是一段系统提示。换平台要重写的只有文件名。
这篇按这个思路,从零搭出一个能用的死链审计 Skill:说明书怎么写、哪两个脚本必须自己实现、报告长什么样、怎么接进 CI,以及实践中最常踩的误报坑。
一、死链为什么值得系统治理
先排掉一个站不住脚的理由:抓取预算。
很多 SEO 文章一上来就说死链浪费抓取预算。这对几十万页的电商站成立,对几百页的内容站基本是伪命题——Googlebot 不差你这点配额。用一个不成立的理由说服自己做一件事,做不了几次就会放弃。
真正的三个理由是:
1. 内链权重断流。 内链是站内权重传递的通道。一条指向 404 的内链,权重传过去就没了,且目标页面拿不到任何提升。站内互链越密,这个损失越大。
2. sitemap 与实际不一致会降低信任度。 sitemap 里躺着一批已经 404 的 URL,等于反复告诉搜索引擎「这些页面很重要请来抓」,抓到的却是 404。这比单纯的死链更糟。
3. AI 抓取器遇到 404 不会重试。 这是最近两年新增的成本。传统搜索引擎有重访机制,一次抓失败还会再来。而 AI 搜索的抓取行为更接近「一次性取用」——抓到 404,这一页在这轮回答里就等于不存在。你做的 GEO 优化再好,链接是断的,内容就进不了答案。
第三条也解释了为什么死链治理的优先级近两年在上升。
二、现有做法卡在哪
| 做法 | 问题 |
|---|---|
| Screaming Frog 免费版 | 500 URL 上限,站稍大就不够;结果要人工逐条点开判断 |
| 在线检测工具 | 只爬得到能从首页点到的页面,孤儿页和 sitemap 差异查不出来 |
| 自己写脚本 | 每个项目重写一次;跑完输出一堆 URL,没有人真的会去读 |
| 直接让 AI「帮我查死链」 | 每次结果格式都不一样,判定标准全凭当次发挥,403 和 404 混为一谈 |
归纳成三个病根:
- 不可复用:知识留在个人脑子里或某次对话里,换项目、换人就归零
- 判定标准不统一:这次把 403 算死链,下次不算,两份报告没法比较
- 结果不可执行:给你一张 404 列表,然后呢?谁去改、改成什么、哪些可以忽略
Skill 要解决的正是这三条,尤其是第三条。
三、AI Skills 到底是什么
抛开各家规范的差异,一个 Skill 就是能力单元的三件套:
| 组成 | 作用 | 谁来执行 |
|---|---|---|
| 说明书 | 什么时候用、按什么步骤做、怎么判定、输出成什么格式 | 模型读 |
| 确定性脚本 | 抓取、解析、发请求、采集状态码——不需要判断的部分 | 机器跑 |
| 参考资料 | 状态码判定表、误报白名单、修复动作对照 | 模型按需查 |
三件套里,脚本和参考资料是跨平台通用的纯资产,说明书也只有承载文件不同。落地位置对照:
| 平台 | 说明书放哪 | 触发方式 |
|---|---|---|
| Claude Code / Claude 应用 | .claude/skills/<name>/SKILL.md |
按 frontmatter 的 description 自动匹配 |
| Cursor | .cursor/rules/*.mdc |
glob 匹配或手动 @ 引用 |
| Codex | AGENTS.md 中的一节 |
进入目录自动加载 |
| 自建 Agent / SDK | 系统提示片段或工具描述 | 自己路由 |
所以本文后面给出的说明书内容,你复制到哪个文件里都能用。
什么时候值得做成 Skill
不是所有事都该封装。判断标准:
| 特征 | 一句提示词就够 | 值得做成 Skill |
|---|---|---|
| 执行频率 | 一次性 | 每周/每次发布都跑 |
| 判定标准 | 当场想就行 | 有明确规则,且规则会积累 |
| 输出格式 | 看懂就行 | 需要跨时间对比、要能交接给别人 |
| 步骤数 | 1–2 步 | 多步且有先后依赖 |
| 误报处理 | 无所谓 | 需要维护白名单 |
死链审计五条全中,所以它是个特别标准的例子。反过来,「帮我把这段文案改短」就不该做成 Skill。
四、死链审计到底要查什么
这一节是整个 Skill 的知识内核。大部分人只查第 1 条,而实际会伤到 SEO 的有 11 类:
| # | 类型 | 说明 | 严重度 |
|---|---|---|---|
| 1 | 内链 404 / 5xx | 站内指向不存在页面的链接 | 🔴 高 |
| 2 | 外链失效 | 引用的外部资源已下线 | 🟡 中 |
| 3 | 重定向链 / 循环 | A→B→C 多跳,或 A→B→A 死循环 | 🟡 中 |
| 4 | 软 404 | 返回 200,但页面内容是「页面不存在」 | 🔴 高 |
| 5 | 锚点失效 | #section 在目标页面找不到对应 id |
🟢 低 |
| 6 | 图片 / 静态资源 404 | <img src>、CSS、JS 加载失败 |
🟡 中 |
| 7 | sitemap 与实际不一致 | sitemap 里有已删页面,或漏了已上线页面 | 🔴 高 |
| 8 | 孤儿页 | 已上线但站内没有任何链接指向 | 🟡 中 |
| 9 | canonical 异常 | canonical 指向 404 或指向一个重定向 | 🔴 高 |
| 10 | 混合内容 | HTTPS 页面里嵌 http:// 资源 |
🟡 中 |
| 11 | 尾部斜杠不一致 | 站内一半写 /guides/foo,一半写 /guides/foo/ |
🟡 中 |
第 4、11 两条值得单独说,因为它们最隐蔽:
软 404 骗过所有只看状态码的工具。很多 SPA 或配置不当的服务器,访问不存在的路径时返回 200 加一个「页面不存在」的壳。搜索引擎会把它当正常页面收录,于是你的索引里多出一堆空内容页。只有把状态码和页面内容一起看才能发现——这正是模型比脚本强的地方。
尾部斜杠在 Astro、Next.js 这类静态站上高发。构建产物是 /guides/foo/index.html,托管平台通常会把 /guides/foo 301 到 /guides/foo/。功能上没问题,但每条写错的内链都多走一跳重定向。几百条内链累积起来,就是几百次无谓的往返。
五、Skill 的三层设计
目录结构:
seo-link-audit/
├── SKILL.md # 说明书:流程、判定规则、输出格式
├── scripts/
│ ├── crawl-local.mjs # 构建产物离线检查(内链、锚点、资源)
│ └── check-external.mjs # 外链联网探测(并发、重试、限流)
└── references/
├── status-rules.md # 状态码判定表
└── allowlist.txt # 误报白名单
分工的原则只有一句,也是整个设计的核心:
确定性的事交给脚本,需要判断的事交给模型。
具体到这个 Skill:
| 任务 | 归属 | 理由 |
|---|---|---|
| 遍历 HTML、提取链接 | 脚本 | 纯字符串处理,模型做既慢又可能漏 |
| 发 HTTP 请求、拿状态码 | 脚本 | 需要并发和重试控制,模型做不了 |
| 判断 403 是真挂了还是被反爬拦了 | 模型 | 要结合域名、响应体、历史记录判断 |
| 识别软 404 | 模型 | 要读页面内容语义 |
| 决定「改链接」还是「加 301」 | 模型 | 要看这条链接在什么位置、被谁引用 |
| 生成人能读的报告 | 模型 | 脚本只能吐 JSON |
让模型去遍历文件提取链接,是最常见的误用。 那是脚本三秒钟能做完且不会出错的事,交给模型既慢、又贵、还可能漏掉。
六、说明书怎么写:六块骨架
一份能用的 Skill 说明书,必须包含这六块。下面每块都给出可直接填入的真实内容——放进 SKILL.md、.mdc 还是 AGENTS.md 都一样。
第 1 块:触发条件
写清「什么时候该用我」,这决定 Agent 能不能在对的时机想起它。写得含糊,Skill 就永远不会被触发。
---
name: seo-link-audit
description: 审计网站的死链、断链、重定向链、软 404 和 sitemap 一致性,
产出带修复动作的报告。当用户提到死链检查、断链、404 排查、上线前链接
检查、SEO 技术审计时使用。
---
对比一下反面写法:description: 检查链接——这种描述几乎不可能被正确匹配。描述里要出现用户真实会说的词,「死链」「404」「上线前检查」都是。
第 2 块:检查范围
明确边界,尤其是不查什么——这能避免每次跑出一堆无关噪音。
## 检查范围
查:
- 站内 HTML 中的 `<a href>`、`<img src>`、`<link href>`、`<script src>`
- sitemap.xml 中列出的所有 URL
- canonical 与 og:url 指向的地址
不查:
- `mailto:`、`tel:`、`javascript:` 协议
- allowlist.txt 中列出的域名
- 需要登录态才能访问的后台路径
第 3 块:执行步骤
按顺序写,并明确每步调用哪个脚本、产出什么中间文件。
## 执行步骤
1. 确认检查目标:本地构建产物 `dist/` 还是线上站点。默认先跑本地。
2. 运行 `scripts/crawl-local.mjs`,产出 `.audit/internal.json`
3. 读取 internal.json,若存在内链 404,立即标记为阻断级问题
4. 运行 `scripts/check-external.mjs`,产出 `.audit/external.json`
5. 对所有 403 / 429 / 超时项,按「误报处理」一节复核
6. 对所有返回 200 的可疑页面,抓取正文前 200 字,判断是否为软 404
7. 按「输出格式」生成报告,写入 `.audit/report.md`
第 4 块:判定规则表
这是全篇最有复用价值的部分,也是每次跑结果都一致的原因。
## 判定规则
| 状态 | 结论 | 动作 |
|---|---|---|
| 200 + 正常内容 | 通过 | 无 |
| 200 + 内容为"页面不存在" | 软 404 | 修复目标页或改链接 |
| 301 / 308 | 永久重定向 | 内链直接改成最终 URL |
| 302 / 307 | 临时重定向 | 确认是否本该是 301 |
| 400 | URL 格式错误 | 检查是否有非法字符 |
| 401 / 403 | 疑似反爬 | 转人工复核,不直接判死 |
| 404 | 死链 | 按修复策略处理 |
| 410 | 已明确删除 | 移除链接 |
| 429 | 触发限流 | 降速重试,不判死 |
| 5xx | 服务端异常 | 间隔重试 3 次仍失败才判死 |
| 超时 / DNS 失败 | 不确定 | 换时间点重试,连续 2 次失败才判死 |
注意几条故意不判死的规则:401/403/429/5xx 都不直接进死链列表。宁可漏报也不误报——一份混着误报的报告,人看两次就不看了。
第 5 块:误报处理
## 误报处理
以下情况标记为「需人工确认」,不计入死链总数:
- 域名在 allowlist.txt 中(已知强反爬:知乎、微信公众号、部分政府站)
- 返回 403 但换 User-Agent 后返回 200
- 返回 429,且同域名其他链接正常
- 链接只在 JS 执行后才出现(静态抓取拿不到上下文)
新发现的稳定误报,追加到 allowlist.txt 并注明日期和原因。
最后一句是关键:让白名单随使用积累。这是 Skill 比一次性脚本更值钱的地方——规则会越用越准。
第 6 块:输出格式
固定格式,才能跨时间对比。
## 输出格式
报告分四段:
1. **摘要**:扫描页面数 / 链接总数 / 阻断级问题数 / 待确认数
2. **阻断级问题**:内链 404、canonical 异常、sitemap 不一致
3. **建议修复**:外链失效、重定向链、尾部斜杠不一致
4. **需人工确认**:403 / 429 / 超时
每条包含:目标 URL、来源页面、状态、类型、**建议动作**。
按「来源页面」分组,方便一次改完一个文件。
「按来源页面分组」是个小但重要的细节。按目标 URL 排序的报告,修的时候要在文件间反复横跳;按来源页分组,可以一个文件一次改完。
七、两个脚本
脚本是这个 Skill 里唯一需要自己写的部分,也是跨平台完全通用的资产。
脚本一:构建产物离线检查
不联网,直接查构建产物,几秒钟跑完。内链检查完全不需要发网络请求——目标页面存不存在,看文件系统就知道。
// scripts/crawl-local.mjs
import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
import { join, relative } from 'node:path';
const DIST = process.argv[2] ?? 'dist';
const SITE = process.env.SITE_URL ?? 'https://example.com';
async function walk(dir) {
const out = [];
for (const e of await readdir(dir, { withFileTypes: true })) {
const p = join(dir, e.name);
if (e.isDirectory()) out.push(...await walk(p));
else if (e.name.endsWith('.html')) out.push(p);
}
return out;
}
// 文件路径 → URL 路径:dist/guides/foo/index.html → /guides/foo/
const toUrlPath = (file) =>
'/' + relative(DIST, file).replace(/index\.html$/, '').replace(/\\/g, '/');
const files = await walk(DIST);
const pages = new Map(); // urlPath → { ids:Set }
const links = []; // 所有待检查链接
for (const file of files) {
const html = await readFile(file, 'utf8');
const from = toUrlPath(file);
// 收集本页所有锚点 id,供锚点检查用
const ids = new Set([...html.matchAll(/\sid="([^"]+)"/g)].map((m) => m[1]));
pages.set(from, { ids });
const attrs = /(?:href|src)="([^"#][^"]*)?(#[^"]*)?"/g;
for (const [, raw = '', hash = ''] of html.matchAll(attrs)) {
if (!raw && !hash) continue;
if (/^(mailto:|tel:|javascript:|data:)/.test(raw)) continue;
links.push({ from, raw, hash: hash.slice(1) });
}
}
const internal = [];
const external = new Set();
for (const { from, raw, hash } of links) {
const isAbs = /^https?:\/\//.test(raw);
if (isAbs && !raw.startsWith(SITE)) { external.add(raw); continue; }
// 站内:统一解析成绝对路径
const path = new URL(raw || from, SITE + from).pathname;
const norm = path.endsWith('/') ? path : path + '/';
const exists = pages.has(norm) || pages.has(path);
if (!exists) {
internal.push({ from, to: path, type: 'internal-404' });
} else if (path !== norm && pages.has(norm)) {
// 写成 /guides/foo,实际是 /guides/foo/ → 多一跳重定向
internal.push({ from, to: path, type: 'trailing-slash' });
} else if (hash && !pages.get(norm)?.ids.has(hash)) {
internal.push({ from, to: path + '#' + hash, type: 'anchor-missing' });
}
}
await mkdir('.audit', { recursive: true });
await writeFile('.audit/internal.json',
JSON.stringify({ pages: pages.size, internal, external: [...external] }, null, 2));
console.log(`扫描 ${pages.size} 页,内链问题 ${internal.length} 条,外链 ${external.size} 个`);
process.exit(internal.some((i) => i.type === 'internal-404') ? 1 : 0);
用正则解析 HTML 有已知局限(注释里的链接、属性顺序异常会漏),但对构建产物这种格式规整的输出足够了。在意准确率可以换成 linkedom 解析,代价是多一个依赖。
最后一行的 process.exit(1) 是为后面接 CI 埋的:有内链 404 就以非零码退出。
脚本二:外链联网探测
外链必须联网查,重点是并发控制、重试和限流。
// scripts/check-external.mjs
import { readFile, writeFile } from 'node:fs/promises';
const UA = 'Mozilla/5.0 (compatible; LinkAudit/1.0)';
const CONCURRENCY = 8;
const TIMEOUT = 10_000;
const { external } = JSON.parse(await readFile('.audit/internal.json', 'utf8'));
const allow = new Set(
(await readFile('references/allowlist.txt', 'utf8').catch(() => ''))
.split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#'))
);
async function probe(url, method = 'HEAD', attempt = 1) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
try {
const res = await fetch(url, {
method, redirect: 'manual',
headers: { 'User-Agent': UA }, signal: ctrl.signal,
});
// 部分服务器不支持 HEAD,降级用 GET 再试一次
if ((res.status === 405 || res.status === 403) && method === 'HEAD') {
return probe(url, 'GET', attempt);
}
// 限流和 5xx 退避重试,最多 3 次
if ((res.status === 429 || res.status >= 500) && attempt < 3) {
await new Promise((r) => setTimeout(r, attempt * 2000));
return probe(url, method, attempt + 1);
}
return { url, status: res.status, location: res.headers.get('location') };
} catch (err) {
if (attempt < 3) {
await new Promise((r) => setTimeout(r, attempt * 2000));
return probe(url, method, attempt + 1);
}
return { url, status: 0, error: err.name === 'AbortError' ? 'timeout' : err.message };
} finally {
clearTimeout(timer);
}
}
const targets = external.filter((u) => ![...allow].some((d) => u.includes(d)));
const results = [];
const queue = [...targets];
await Promise.all(
Array.from({ length: CONCURRENCY }, async () => {
while (queue.length) results.push(await probe(queue.shift()));
})
);
await writeFile('.audit/external.json', JSON.stringify(results, null, 2));
const dead = results.filter((r) => r.status === 404 || r.status === 410);
const review = results.filter((r) => [401, 403, 429, 0].includes(r.status));
console.log(`外链 ${results.length} 个:确认失效 ${dead.length},待人工确认 ${review.length}`);
三个容易被忽略的细节:
redirect: 'manual':默认的follow会自动跟随重定向,你拿到的是最终状态码,重定向链就查不出来了- HEAD 降级 GET:不少服务器对 HEAD 返回 405 或 403,直接判死就是误报
- 退避重试:
attempt * 2000的递增间隔,避免把对方(和自己)打限流
八、报告长什么样
脚本产出 JSON,模型按说明书的输出格式转成报告。真实跑一遍的样子:
# 链接审计报告
扫描 47 页 / 链接 612 条 / 阻断级 3 / 建议修复 11 / 待确认 4
## 🔴 阻断级问题
### src/content/guides/astro-content-site-guide.md
| 目标 | 状态 | 类型 | 建议动作 |
|---|---|---|---|
| /guides/deploy-astro/ | 404 | 内链死链 | 改为 /guides/deploy-astro-on-vps/ |
### sitemap.xml
| 目标 | 状态 | 类型 | 建议动作 |
|---|---|---|---|
| /guides/old-post/ | 404 | sitemap 含已删页 | 从 sitemap 移除;如有外链则加 301 |
## 🟡 建议修复
### src/content/practice/building-blktech-site.md
| 目标 | 状态 | 类型 | 建议动作 |
|---|---|---|---|
| /resources/learning-paths | 301 | 尾部斜杠 | 补 `/`,省一跳重定向 |
| https://old-tool.dev/docs | 404 | 外链失效 | 换官方新地址或删除链接 |
## ⚪ 需人工确认
| 目标 | 状态 | 说明 |
|---|---|---|
| https://zhuanlan.zhihu.com/p/xxx | 403 | 疑似反爬,浏览器可正常打开 |
和一张裸 URL 列表的差别在于:每一条都直接给了动作,而且按文件分好组。这份报告可以直接扔给 Agent 说「按报告修阻断级问题」,也可以自己照着改。
九、按类型给修复动作
| 类型 | 动作 | 注意 |
|---|---|---|
| 内链 404 | 直接改链接指向正确 URL | 不要用 301 兜底,见下 |
| 外链 404 | 换新地址;找不到替代就删链接或去掉超链 | 别留着「等以后修」 |
| 重定向链 | 内链改成最终 URL | 超过 2 跳一定要拉平 |
| 软 404 | 修目标页或让它返回真 404 | 两者取一,别留 200 空壳 |
| sitemap 含已删页 | 从 sitemap 移除 | 有外部流量的另加 301 |
| 孤儿页 | 从相关页面加内链 | 或确认它本就该 noindex |
| canonical 指向 404 | 改成实际存在的 URL | 这条对索引影响最大 |
| 尾部斜杠 | 统一成构建产物的形式 | 顺手加进 lint 规则 |
| 已知反爬域名 | 加白名单 | 注明日期和原因 |
单独强调一条反直觉的:
内链不要靠 301 兜底,要直接改成最终 URL。
301 是给外部流量和老书签用的兜底机制。内链走 301 意味着每次访问都白走一跳,而且你完全有能力改对——链接就在自己仓库里。用重定向解决内链问题,是把自己能修的事推给了服务器每次请求去承担。
十、接进 CI
Skill 手动跑有价值,但真正省事的是让它自动跑。关键是内链和外链要用完全不同的策略:
| 内链 | 外链 | |
|---|---|---|
| 触发时机 | 每次构建 | 每周定时 |
| 失败处理 | 阻断构建 | 只报告,不阻断 |
| 理由 | 是你自己的错,且必然可修 | 别人的站挂了,且抖动多 |
# .github/workflows/link-audit.yml
name: link-audit
on:
push:
schedule:
- cron: '0 3 * * 1' # 每周一 03:00 查外链
jobs:
internal:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install && bun run build
- run: bun scripts/crawl-local.mjs dist # 有内链 404 则退出码 1,构建失败
external:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- run: bun install && bun run build
- run: bun scripts/crawl-local.mjs dist || true
- run: bun scripts/check-external.mjs
- uses: actions/upload-artifact@v4
with: { name: link-audit, path: .audit/ }
外链检查千万不要阻断构建。 外链失效不是你的错,而且第三方站点抖动频繁——只要有一次因为别人的服务器打嗝导致你发不了版,这个检查就会在一周内被注释掉。
十一、误报与坑
跑过几轮后一定会遇到的:
403 反爬。 知乎、微信公众号、部分政府和媒体站会拦截非浏览器 UA。换 UA 能解决一部分,剩下的加白名单。判断方法:浏览器能打开就是误报。
429 限流。 同一域名下链接太多时容易触发。除了退避重试,更好的做法是按域名分组、每个域名串行。
爬自己站把自己打限流。 检查线上站点时,并发开太大会被自己的 CDN 或防火墙拦。所以本文优先推荐检查本地构建产物——不联网、更快、也不会误伤。
需登录的链接。 后台路径、会员页在检查范围里就该排除,别等报告出来才发现半页都是 401。
JS 渲染后才出现的链接。 静态抓取拿不到。要么接无头浏览器,要么在说明书里写明「本 Skill 不覆盖客户端渲染的链接」——明确说明不覆盖,比假装覆盖了要好。
相对路径解析。 ../foo 这类相对链接必须以当前页面 URL 为基准解析,直接拼字符串一定出错。上面脚本用 new URL(raw, base) 就是为此。
十二、系列导航
这是「SEO Skills 工具箱」系列的第一篇。整个系列把技术 SEO 审计拆成几个各自独立、可单独使用的 Skill:
- 当前:死链与断链审计
- Meta 审计:title、description、H1 的缺失、重复与超长
- 结构化数据与 JSON-LD 校验
- Sitemap 与实际路由的差异审计、孤儿页发现
- 内链结构审计:权重分布、内容孤岛与锚文本
- 内置 seo:audit 与 Skill 封装
- 配套资源:说明书、规则注册表与检查清单
它们共用同一套设计原则——确定性交给脚本,判断交给模型,中间用一份写死了判定规则和输出格式的说明书连接。把这条原则吃透,后面几个 Skill 你自己就能写出来。
相关阅读:
- Astro 内容站搭建指南——里面有一份 SEO / GEO 基础清单
- 展示型独立站进阶优化——GEO 搜索意图怎么落到页面结构上
- 内容发布自动化——把审计接进发布流程
RELATED / 相关推荐
接着读这些
按同一栏目、标签与技术栈为你挑选。
AI Skills 高阶:把 SEO 检查内置进项目,做成一条命令 + 一个 Skill
把分散的五类 SEO 检查合并成项目内置的 seo:audit 脚本,用统一的规则注册表管理 24 项检查与 error/warn 分级,再封装成 Skill,让检测、解析和修复方案一次给全。
AI Skills 实战:Meta 审计 Skill——查 title、description 与 H1 的缺失、重复与超长
把 title、description、H1 的判定阈值和改写依据固化成 Skill 说明书,用半角当量宽度替代字符数解决中文截断误判,让模型直接给出改写候选而不只是报告超长。
AI Skills 实战:结构化数据校验 Skill——语法对不代表标记对
JSON-LD 校验分语法、类型、一致性三层,校验器只覆盖第一层。用本地规则表查必填字段与类型选择,让模型比对标记内容与页面可见内容,堵住真正会丢富媒体资格的问题。