内链流审计检查四件事:每个 Cluster 是否有指向 Pillar 的内链、锚文本是否使用 Pillar 的 PKW(而非"点击这里")、内链是否在正文前段、Pillar 是否链接到所有下属 Cluster。脚本构建链接图并排除全局导航,模型判断锚文本的语义匹配度。
AI Skill:Pillar-Cluster 内链流审计——权重流动是否形成闭环
为什么这个审计不可省略
整个 Pillar-Cluster 架构的核心机制是:Cluster 页获得外链 → 内链把权重传给 Pillar → Pillar 排名商业大词。
内链缺失或锚文本写错,这套机制就是空转。而且不会有任何报错——页面正常显示,构建正常通过,只是权重没有流动。
这是最容易悄悄失效的一环,也是最需要定期审计的一环。
审计的四个检查项
检查一:每个 Cluster 是否有指向 Pillar 的内链
判定:
- 有 → 通过
- 无 → error(权重完全断流)
检查二:锚文本是否使用 Pillar 的 PKW
判定:
- 锚文本完全包含 Pillar PKW → 通过(最优)
- 锚文本部分包含(如缺少 "manufacturer")→ warn
- 锚文本是 "click here"/"learn more"/"back to products" → error
检查三:内链位置
判定:
- 正文前 500 字内 → 通过(权重最高)
- 正文中后段 → 通过(正常)
- 仅在页脚或侧边栏 → warn(权重传递效率低)
- 仅在面包屑 → warn(面包屑有效但不足,需要正文内链补充)
检查四:Pillar 是否链接到所有下属 Cluster
判定:
- Pillar 链接到全部下属 Cluster → 通过
- 遗漏部分 Cluster → warn(遗漏的 Cluster 是孤儿页风险)
关键方法论:算图前必须剔除全局导航
这是内链审计里最容易出错的地方,站内已有的「内链结构审计」Skill 也强调了这一点。
问题:如果不剔除全局导航,每个页面都“链接”到了导航栏里的所有 Pillar,审计结果会显示“所有 Cluster 都有指向 Pillar 的内链”——但这些链接是全站模板生成的,不是正文里的精准锚文本。
剔除方法:
// 识别并剔除全局导航链接的启发式规则
// 规则 1:出现在 <nav>、<header>、<footer> 内的链接
// 规则 2:在 90% 以上的页面都出现的相同 (href, anchor) 组合
// 规则 3:在 Astro 项目中,可以直接排除来自 BaseLayout 组件的链接
规则2最通用:统计每个 (href, anchorText) 组合在多少个页面出现,超过阈值(如 80% 页面)的判定为模板链接,从权重图中剔除。
Skill 说明书
# Skill:Pillar-Cluster 内链流审计
## 何时使用
- 定期(每季度)内链健康检查
- Pillar 页排名停滞不前时
- 新增一批 Cluster 页后
## 输入
- 构建产物目录(如 `dist/`,含所有 HTML 文件)
- `registry.json`(获取 Pillar 与 Cluster 的对应关系和 PKW)
## 执行步骤
### Step 1:运行确定性脚本
执行 `scripts/link-flow-audit.mjs`。
脚本会:
1. 解析所有 HTML,提取正文区域(排除 nav/header/footer)内的链接
2. 用 80% 阈值剔除模板链接
3. 按注册表的 page_type 构建 Pillar-Cluster 归属关系
4. 输出每个 Cluster 的内链状况
### Step 2:锚文本语义判断(你来做)
脚本会输出每条 Cluster → Pillar 内链的锚文本。
对每条锚文本,判断与目标 Pillar 的 PKW 的匹配程度:
**完全匹配**:锚文本包含 PKW 的全部核心词
PKW: "centrifugal pump manufacturer"
锚文本: "centrifugal pump manufacturer" ✅
锚文本: "industrial centrifugal pump manufacturer" ✅(含修饰词,仍算完全)
**部分匹配**:锚文本包含 PKW 的部分核心词,但缺少关键商业词
PKW: "centrifugal pump manufacturer"
锚文本: "centrifugal pumps" ⚠️(缺少 manufacturer)
锚文本: "our pump products" ⚠️(过于泛化)
**无效锚文本**:不含任何 PKW 词素
锚文本: "click here" ❌
锚文本: "learn more" ❌
锚文本: "back to products" ❌
### Step 3:输出报告并给出具体修改建议
每个问题项必须给出:
- 具体的文件路径
- 当前的锚文本原文
- 建议替换成的锚文本
- 建议的句子上下文(让锚文本自然嵌入,不要生硬)
## 输出格式
Pillar-Cluster 内链流审计报告
生成时间:[YYYY-MM-DD] Cluster 页总数:[N] Pillar 页总数:[N] 权重流动完整率:[N]%(有精准锚文本内链的 Cluster / 总 Cluster)
ERROR:权重完全断流([N] 项)
E1. /applications/centrifugal-pump-mining/
- 归属 Pillar:/products/centrifugal-pumps/ (PKW: centrifugal pump manufacturer)
- 状况:正文内无任何指向该 Pillar 的链接
- 仅有面包屑链接(锚文本 “Centrifugal Pumps”)
- 修改建议:在正文第二段添加内链 当前句子: “Mining dewatering applications require robust pump designs.” 改为: “Mining dewatering applications require robust pump designs. As an experienced [centrifugal pump manufacturer], we supply heavy-duty models rated for continuous abrasive service.” (方括号内为内链锚文本,指向 /products/centrifugal-pumps/)
ERROR:无效锚文本([N] 项)
E2. /blog/centrifugal-vs-gear-pump/
- 归属 Pillar:/products/centrifugal-pumps/
- 当前锚文本:“click here”
- 位置:正文第 8 段
- 修改建议:锚文本改为 “centrifugal pump manufacturer” 当前句子: “To source pumps for your project, click here.” 改为: “To source pumps for your project, contact our [centrifugal pump manufacturer] team.”
WARN:锚文本部分匹配([N] 项)
W1. /products/centrifugal-pumps/cp-100/
- 当前锚文本:“centrifugal pumps”(缺少 “manufacturer”)
- 位置:正文第 1 段
- 建议:改为 “centrifugal pump manufacturer”
- 优先级:中(已有内链,只是锚文本可优化)
WARN:Pillar 遗漏下属 Cluster([N] 项)
W2. /products/centrifugal-pumps/
- 应链接的 Cluster:8 个
- 实际链接:6 个
- 遗漏:
- /products/centrifugal-pumps/cp-300/
- /applications/centrifugal-pump-hvac/
- 建议:在产品卡片矩阵和应用场景区补充这两个入口
通过项
- [N] 个 Cluster 有完全匹配的精准锚文本内链
- [N] 个 Pillar 完整链接了全部下属 Cluster
## 禁止行为
- 不要在未剔除全局导航的情况下判定"内链存在"
- 修改建议必须包含完整的句子上下文(不要只说"加个内链")
- 不要建议在一个页面里重复添加指向同一 Pillar 的多条内链
(Google 只计第一条的锚文本权重)
确定性脚本
// scripts/link-flow-audit.mjs
import fs from 'fs/promises';
import path from 'path';
import { parse } from 'node-html-parser';
const registry = JSON.parse(await fs.readFile('registry.json', 'utf8'));
const DIST = 'dist';
// 建立 Pillar 映射:category → { slug, pkw }
const pillars = new Map();
for (const row of registry) {
if (row.page_type === 'pillar') {
pillars.set(row.page_slug, { pkw: row.pkw, category: row.category });
}
}
// 建立 Cluster → Pillar 归属映射
const clusterToPillar = new Map();
for (const row of registry) {
if (row.page_type?.startsWith('cluster')) {
// 按 category 字段找对应 Pillar
const pillar = [...pillars.entries()]
.find(([, v]) => v.category === row.category);
if (pillar) {
clusterToPillar.set(row.page_slug, {
pillarSlug: pillar[0],
pillarPkw: pillar[1].pkw,
});
}
}
}
// 递归收集所有 HTML 文件
async function collectHtml(dir) {
const out = [];
for (const entry of await fs.readdir(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) out.push(...await collectHtml(full));
else if (entry.name.endsWith('.html')) out.push(full);
}
return out;
}
const htmlFiles = await collectHtml(DIST);
// 第一遍:统计每个 (href, anchor) 组合出现的页面数,用于识别模板链接
const linkFrequency = new Map();
const pageLinks = new Map();
for (const file of htmlFiles) {
const html = await fs.readFile(file, 'utf8');
const root = parse(html);
// 剔除 nav / header / footer
root.querySelectorAll('nav, header, footer').forEach(el => el.remove());
const main = root.querySelector('main') || root.querySelector('body');
if (!main) continue;
const urlPath = '/' + path.relative(DIST, file)
.replace(/index\.html$/, '')
.replace(/\\/g, '/');
const links = main.querySelectorAll('a[href]').map((a, idx) => ({
href: a.getAttribute('href'),
anchor: a.text.trim(),
// 粗略的位置估算:该链接在正文中的字符偏移
position: main.innerHTML.indexOf(a.outerHTML),
}));
pageLinks.set(urlPath, links);
for (const link of links) {
const key = `${link.href}|||${link.anchor}`;
linkFrequency.set(key, (linkFrequency.get(key) ?? 0) + 1);
}
}
// 识别模板链接(出现在 80% 以上页面的相同 href+anchor 组合)
const TEMPLATE_THRESHOLD = htmlFiles.length * 0.8;
const templateLinks = new Set(
[...linkFrequency.entries()]
.filter(([, count]) => count >= TEMPLATE_THRESHOLD)
.map(([key]) => key)
);
// 第二遍:检查每个 Cluster 的内链状况
const results = [];
for (const [clusterSlug, { pillarSlug, pillarPkw }] of clusterToPillar) {
const links = pageLinks.get(clusterSlug) ?? [];
// 找出指向 Pillar 的非模板链接
const pillarLinks = links.filter(l => {
const key = `${l.href}|||${l.anchor}`;
if (templateLinks.has(key)) return false; // 剔除模板链接
return l.href === pillarSlug || l.href === pillarSlug.replace(/\/$/, '');
});
const bodyLength = 5000; // 估算值,用于判断"前段"
results.push({
cluster: clusterSlug,
pillar: pillarSlug,
pillarPkw,
hasLink: pillarLinks.length > 0,
links: pillarLinks.map(l => ({
anchor: l.anchor,
position: l.position,
inEarlySection: l.position < bodyLength * 0.3,
})),
});
}
// 检查 Pillar 是否链接到所有下属 Cluster
const pillarCoverage = [];
for (const [pillarSlug, { category }] of pillars) {
const expectedClusters = [...clusterToPillar.entries()]
.filter(([, v]) => v.pillarSlug === pillarSlug)
.map(([slug]) => slug);
const links = pageLinks.get(pillarSlug) ?? [];
const linkedHrefs = new Set(links.map(l => l.href));
const missing = expectedClusters.filter(c =>
!linkedHrefs.has(c) && !linkedHrefs.has(c.replace(/\/$/, ''))
);
pillarCoverage.push({
pillar: pillarSlug,
expectedClusters: expectedClusters.length,
linkedClusters: expectedClusters.length - missing.length,
missing,
});
}
console.log(JSON.stringify({
summary: {
totalPages: htmlFiles.length,
clusterCount: clusterToPillar.size,
pillarCount: pillars.size,
clustersWithLink: results.filter(r => r.hasLink).length,
templateLinksExcluded: templateLinks.size,
},
clusterResults: results,
pillarCoverage,
}, null, 2));
与站内已有内链审计 Skill 的边界
| Skill | 关注点 | 输出 |
|---|---|---|
| 内链结构审计(已有) | 全站图论指标:权重分布、内容孤岛、锚文本多样性 | 结构性问题清单 |
| 本 Skill | 特定架构约束:Cluster→Pillar 是否闭环 | 具体到句子的修改建议 |
前者是通用的内链健康检查,后者是针对 Pillar-Cluster 架构的专项验证。两者互补,不重复。
→ AI Skill:GEO 覆盖审计——多语言 / AI 可见性缺口分析
RELATED / 相关推荐
接着读这些
按同一分类、系列与标签为你挑选。
GEO 审计升级:从「结构合规」到「有没有被引用」
之前那个 GEO 覆盖审计 Skill 只能查结构,查不了覆盖——因为它没有期望值可比。补上意图注册表之后,审计从一层变三层:结构、覆盖与一致性、引用实测。其中复述块与主答块的一致性检查是纯确定性的,脚本能百分之百判定,这是升级带来的最大收益。
AI Skill:页面覆盖度审计——找出注册表中无对应页面的词
注册表里躺着一批词,但对应的页面还没建——这是最容易被遗忘的增长机会。这个 Skill 用脚本做注册表与实际路由的差集比对,模型负责按业务价值排序并输出可执行的建页清单。
AI Skill:注册表审计——关键词抢词与归属冲突检测
把注册表的唯一性约束检查固化成一个可复用 Skill:确定性脚本负责扫描注册表和页面内容,模型负责判断语义级抢词和给出合并建议,中间用一份写清判定规则和输出格式的说明书连接。