内链结构审计要先剔除全局导航链接,否则链接图退化成完全图、所有指标失效。剔除后用 BFS 算点击深度、统计横向入链分布、找连通分量、检查锚文本描述性。图指标本身没有对错,必须由模型结合内容战略解读,因此这类检查不应阻断构建。
先说结论
前四篇查的都是错误:链接断了(①)、字段缺了(②)、标记错了(③)、信号矛盾了(④)。每一条都能指着说「这里错了」。
这一篇不一样。一个站可以做到:零死链、Meta 全合格、结构化数据全绿、sitemap 零矛盾——同时内链结构一塌糊涂。
因为结构问题不在任何单条链接上:
没有一条链接是坏的,但它们组成的形状可能是坏的。
比如你最想推的那篇文章,全站只有 1 个内链指向它;比如有 7 篇文章互相链来链去,和站点主体几乎不通;比如某个页面要点 5 次才能从首页到达。逐条检查这些链接,每一条都完全正常。
要看见形状,得把内链当成图来算。
一、开始之前:必须先剔除全局导航
这是本篇最重要的一条,做错了后面全部白算。
大多数站点的 header 和 footer 里有一套全局导航,出现在每一个页面上。如果你把页面上所有 <a> 都算进链接图:
- 每个页面都链向导航里的那 10 个页面
- 于是那 10 个页面各自获得 N 个入链(N = 全站页面数)
- 每个页面到它们的距离都是 1 跳
图退化成了近似完全图,所有指标同时失去意义。 点击深度全是 1,入链分布被导航项垄断,连通分量只剩一个。你会得到一份「一切正常」的报告,而它什么也没测出来。
正确做法是只统计正文区域的链接:
// 只取 <main> 内的链接,排除 header / footer / 侧边栏
const body = html.split('<main')[1]?.split('</main>')[0] ?? html;
差别有多大?本站实测:
| 统计范围 | 最大点击深度 | 入链中位数 | 可达页面 |
|---|---|---|---|
全部 <a> |
1 跳 | 被导航项垄断 | 全部 |
仅 <main> 内 |
2 跳 | 9 | 64 / 66 |
只有第二行的数据能拿来做判断。
这条同样适用于面包屑、相关文章推荐、分页控件等模板化链接。 判断标准很简单:这个链接是「因为这篇内容需要」而存在,还是「因为模板长这样」而存在?只有前者反映真实的内容关系。
二、四个可测指标
| 指标 | 算法 | 回答什么问题 |
|---|---|---|
| 点击深度 | 从首页 BFS | 这页要点几次才能到? |
| 入链分布 | 入度统计 | 站内认为哪些页面重要? |
| 连通性 | 连通分量 | 有没有和主体断开的内容团? |
| 锚文本 | 文本聚合 | 链接告诉了搜索引擎什么? |
四个指标有个共同特点,也是本篇 Skill 设计的关键:
图指标本身没有对错,全靠解读。
「这页入链是 2」既不是好也不是坏——取决于它是一篇你想推的核心文章,还是一个归档页。这决定了模型在这个 Skill 里承担的比例是五篇里最高的。
三、点击深度
从首页出发的最短路径长度。经验共识是超过 3 跳的页面权重明显衰减,抓取频率也会下降。
BFS 就够了,不需要复杂算法:
const depth = new Map([['/', 0]]);
const queue = ['/'];
while (queue.length) {
const cur = queue.shift();
for (const next of graph.get(cur) ?? []) {
if (graph.has(next) && !depth.has(next)) {
depth.set(next, depth.get(cur) + 1);
queue.push(next);
}
}
}
const unreachable = [...graph.keys()].filter((p) => !depth.has(p));
本站实测结果:
0 跳: 1 页 (首页)
1 跳: 28 页
2 跳: 35 页
3 跳+: 0 页
不可达: 2 页 /about/ /newsletter/
深度分布很健康——全站最深 2 跳,因为首页和栏目索引页承担了枢纽作用。
值得注意的是最后一行的 2 个不可达页面。/about/ 和 /newsletter/ 从首页出发、只走正文链接是到不了的,它们完全依赖 header 里的全局导航存活。
这不一定是问题(导航链接搜索引擎同样会爬),但它揭示了一件事:这两个页面没有任何一篇内容认为有必要提到它们。如果 /newsletter/ 是个转化目标,那这就是实打实的机会损失——正文里一次都没被提及的订阅页,转化率不会好。
四、入链分布:它反映的是组织方式,不是质量
统计每个页面被多少个其他页面的正文链接。本站实测的 Top 5:
65 入链 / (首页)
56 入链 /resources/ (栏目索引)
44 入链 /guides/ (栏目索引)
27 入链 /ai/ (栏目索引)
15 入链 /guides/展示型独立站进阶优化/ ← 第一个内容页
首页和栏目页占据前四位是正常的枢纽结构。真正要看的是文章之间的横向链接——把栏目页排除后,只统计「文章 → 文章」:
文章页共 53 篇
横向入链为 0 的:1 篇
最少:
0 入链 /practice/first-week-content-ops/
1 入链 /resources/learning-paths/
2 入链 /guides/docker-basics-for-solo-devs/
最多:
11 入链 /guides/展示型独立站进阶优化/
10 入链 /resources/hyperframes-video-workflow-template/
10 入链 /guides/hyperframes-skills-first-video/
这组数据里藏着一个容易被误读的规律:
入链最多的全部是系列文章,入链最少的全部是单篇文章。
原因很朴素——系列文章末尾有导航节,天然互相链接;单篇文章再好也没人链它。所以:
内链分布反映的是内容的组织方式,不是内容的质量。
这个结论直接影响该怎么修:看到「某篇文章入链只有 2」,正确的反应不是「这篇文章不重要」,而是「这篇文章没有被组织进任何结构里」。修法是给它找到归属——并入某个系列、或从相关文章里主动引用,而不是机械地到处塞链接。
判断「这个分布合不合理」需要知道你的内容战略是什么,脚本给不出答案。这是模型在这个 Skill 里最主要的工作。
五、连通性与内容孤岛
把链接图看作无向图求连通分量,能找出和主体断开的内容团。
比①的孤儿页检测更进一步的是:孤岛可以由多个页面组成,每个页面都有入链,但整团和主体不通。逐页检查永远发现不了——每一页看起来都好好的。
// 无向连通分量
const undirected = new Map();
for (const [from, tos] of graph) {
for (const to of tos) {
if (!graph.has(to)) continue;
(undirected.get(from) ?? undirected.set(from, new Set()).get(from)).add(to);
(undirected.get(to) ?? undirected.set(to, new Set()).get(to)).add(from);
}
}
const seen = new Set(); const components = [];
for (const node of graph.keys()) {
if (seen.has(node)) continue;
const comp = []; const stack = [node];
while (stack.length) {
const cur = stack.pop();
if (seen.has(cur)) continue;
seen.add(cur); comp.push(cur);
for (const n of undirected.get(cur) ?? []) if (!seen.has(n)) stack.push(n);
}
components.push(comp);
}
理想结果是一个大分量 + 零星孤立点。出现两个以上规模相当的分量,说明站点内容实际上分成了互不往来的两块——常见于「后来加了个新主题,但从没和老内容互相引用」。
六、锚文本
链接的文字告诉搜索引擎目标页是关于什么的。三类问题:
| 问题 | 例子 | 影响 |
|---|---|---|
| 无信息锚文本 | 「点击这里」「阅读更多」「详情」 | 浪费了一次描述机会 |
| 过度一致 | 全站 20 条链接锚文本一字不差 | 有过度优化嫌疑 |
| 完全无关 | 锚文本和目标页主题对不上 | 信号混乱 |
本站实测无信息锚文本 0 处——这类问题在手写 Markdown 的站上很少见,在用可视化编辑器或从模板生成的站上则很普遍。
第二、三类都需要模型判断:「20 条锚文本都叫『Astro 教程』」到底是自然的(确实都在指同一篇教程)还是过度优化,得看上下文。脚本只能把锚文本按目标页聚合起来,判断交给模型。
七、Skill 设计:模型占比最高的一篇
| 任务 | 归属 |
|---|---|
| 剔除全局导航、构建链接图 | 脚本 |
| BFS 算点击深度 | 脚本 |
| 入度统计、连通分量 | 脚本 |
| 锚文本按目标页聚合 | 脚本 |
| 判断深度分布是否合理 | 模型 |
| 判断入链分布是否匹配内容战略 | 模型 |
| 判断孤岛该合并还是该断开 | 模型 |
| 判断锚文本是自然还是过度优化 | 模型 |
| 给出补哪条内链的具体建议 | 模型 |
脚本在这一篇只负责把图算出来,所有「这算好还是不好」的问题全归模型。
最后一项特别值得说。发现「文章 X 只有 2 个入链」之后,真正有用的输出不是这句话本身,而是:
「从《A》第 3 节讲到部署时,可以链到 X;从《B》的相关阅读里也该有 X。」
这需要同时理解 X 讲什么、A 和 B 讲什么、在哪个位置插入是自然的。这是整个系列里模型价值最高的一处——脚本连边都摸不到。
八、报告样例
本站真实扫描结果:
# 内链结构审计报告
统计范围:<main> 内链接(已剔除全局导航)
页面 66 / 文章 53 / 最大深度 2 跳
## ✅ 点击深度
0 跳 1 页 · 1 跳 28 页 · 2 跳 35 页 · 3 跳+ 0 页
结构扁平,首页与栏目索引承担枢纽作用,无深层页面。
## 🟡 正文不可达(2 页)
| 页面 | 说明 |
|---|---|
| /about/ | 仅存在于 header 导航,无任何正文引用 |
| /newsletter/ | 同上 |
**判定**:/about/ 可接受(工具型页面)。
/newsletter/ 若为转化目标则是机会损失——53 篇文章无一提及订阅入口。
**建议**:在系列文章末尾的「相关阅读」中加入订阅引导。
## 🟡 横向入链偏低(3 篇)
| 文章 | 横向入链 |
|---|---|
| /practice/first-week-content-ops/ | 0 |
| /resources/learning-paths/ | 1 |
| /guides/docker-basics-for-solo-devs/ | 2 |
**根因**:三篇均为独立单篇,未归入任何系列。入链最多的
11 篇全部来自「展示型独立站」与「HyperFrames」两个系列
——系列末尾的导航节带来了天然互链。
**建议**:不要机械补链。first-week-content-ops 与
content-publishing-automation 主题相邻,可在后者正文中
自然引用;或将两者组织为内容运营小系列。
## ✅ 锚文本
无信息锚文本 0 处。
报告里真正有价值的是每一条判定后面的那句解释:为什么 /about/ 不可达可以接受而 /newsletter/ 不行,为什么入链低的都是单篇文章。数字是脚本给的,判断是模型给的,而人只会读判断。
九、修复策略
| 问题 | 动作 | 注意 |
|---|---|---|
| 深度 > 3 跳 | 从栏目页或相关文章增加入口 | 别靠加导航项解决 |
| 正文不可达 | 判断是否为工具型页面;是转化目标就必须补 | 导航链接不能替代正文引用 |
| 横向入链为 0 | 先找归属,再补链 | 归入系列比零散加链有效 |
| 内容孤岛 | 从主体内容主动引用,建立双向连接 | 单向链接不足以打通 |
| 无信息锚文本 | 换成描述目标页内容的短语 | |
| 锚文本过度一致 | 自然变化即可,不必刻意改写 | 别为了「多样性」写得不通顺 |
一条贯穿的原则:
内链要因为内容需要而存在,不是因为 SEO 需要而存在。
为了提升某页入链数,在 20 篇不相关的文章里硬塞链接——这既帮不了排名,也损害阅读体验。内链分布应该是内容组织的自然结果,如果某篇文章实在找不到自然的引用位置,那要解决的是它在内容体系里的定位问题,不是链接数量问题。
十、CI 策略:这一篇完全不阻断
系列前四篇的阻断线各不相同,这一篇的答案最干脆:全部不阻断,只出报告。
理由回到那条一以贯之的判据——这件事有没有唯一正确答案:
| 检查 | 有标准答案吗 |
|---|---|
| 内链指向 404 | ✅ 有 |
| title 跨页重复 | ✅ 有 |
| sitemap ∩ noindex | ✅ 有 |
| 某页入链只有 2 | ❌ 没有 |
| 深度 3 跳 | ❌ 没有 |
| 锚文本重复 8 次 | ❌ 没有 |
给结构指标设阈值(「每篇文章至少 3 个入链」)会立刻产生副作用:为了达标而互相塞链接,制造出一堆对读者无意义的链接。指标一旦成为目标,就不再是好指标。
所以这个 Skill 的正确用法是周期性人工触发,而不是每次 push 都跑:
name: link-structure
on:
schedule:
- cron: '0 4 1 * *' # 每月一次
workflow_dispatch: # 需要时手动触发
月度跑一次,把报告当作内容规划的输入——看看这个月新发的文章有没有被组织进结构里,比在 CI 里卡构建有用得多。
至此五篇的 CI 策略全景:
| 系列 | 阻断 | 放行 | 频率 |
|---|---|---|---|
| ① 死链 | 内链 404 | 外链失效 | 每次构建 / 每周 |
| ② Meta | 缺失、跨页重复 | 长度、措辞 | 每次构建 |
| ③ 结构化数据 | L1+L2+客观 L3 | 语义一致性 | 每次构建 |
| ④ Sitemap | 信号矛盾 | 漏收录、孤儿页 | 每次构建 |
| ⑤ 内链结构 | 无 | 全部 | 每月 |
从「必须阻断」到「完全不阻断」,判据始终只有一条。
十一、系列导航
「SEO Skills 工具箱」五类检查至此讲完:
- 死链与断链审计
- Meta 审计:title、description、H1
- 结构化数据与 JSON-LD 校验
- Sitemap 差异审计与孤儿页发现
- 当前:内链结构审计
- 内置 seo:audit 与 Skill 封装——把五类检查合并成一条命令
- 配套资源:说明书、规则注册表与检查清单
六条原则:
- 确定性交给脚本,判断交给模型(①)
- 能量化的写成阈值表,不能量化的给判断依据(②)
- CI 的阻断线画在「有唯一正确答案」的地方(②③④⑤)
- 报告要给根因,不是给症状列表(③)
- 集合运算前先做归一化——脚本不会报错,只会安静地全错(④)
- 算图指标前先剔除模板化链接,否则测的是模板不是内容(⑤)
五类检查下来会发现,真正区分「能用的 Skill」和「玩具」的,从来不是模型多聪明,而是判定规则写得够不够细、边界划得够不够清。这些规则可以慢慢积累——每次发现一个误报就往参考资料里加一条,用得越久越准。这正是把它做成 Skill 而不是一次性脚本的意义。
下一篇把这五类检查合并成项目内置的一条 validate 命令,并给出 Skill 层该怎么只做解释、不做检测。
相关阅读:
- Astro 内容站搭建指南——内容结构设计决定了内链的天然形状
- 第一周内容运营——内容组织与发布节奏
RELATED / 相关推荐
接着读这些
按同一栏目、标签与技术栈为你挑选。
AI Skills 高阶:把 SEO 检查内置进项目,做成一条命令 + 一个 Skill
把分散的五类 SEO 检查合并成项目内置的 seo:audit 脚本,用统一的规则注册表管理 24 项检查与 error/warn 分级,再封装成 Skill,让检测、解析和修复方案一次给全。
AI Skills 实战:把网站死链与断链审计做成一个可复用 Skill
把死链检查的判定规则和输出格式固化成一份 Skill 说明书,配合两个确定性脚本,让 AI Agent 每次都按同一套 SOP 产出可执行的审计报告,并接入 CI 持续拦截。
AI Skills 实战:Meta 审计 Skill——查 title、description 与 H1 的缺失、重复与超长
把 title、description、H1 的判定阈值和改写依据固化成 Skill 说明书,用半角当量宽度替代字符数解决中文截断误判,让模型直接给出改写候选而不只是报告超长。