差距分析的核心是集合运算:多个竞品都有排名而你完全没有的词价值最高(需求被多方验证)。脚本做集合运算并计算机会分(搜索量×竞品覆盖数÷难度),模型负责剔除业务不相关词和判断是否可复用现有页面,输出待建页面和待优化页面两类清单。
AI Skill:竞品关键词差距分析
差距分析的价值层次
不是所有“竞品有你没有”的词都值得做。按价值排序:
| 层次 | 定义 | 价值 | 说明 |
|---|---|---|---|
| 1 | 3 个竞品都有排名,你完全没有 | 最高 | 需求被多方验证,且你缺失整个主题 |
| 2 | 2 个竞品有,你排名 > 50 | 高 | 有页面但内容不足 |
| 3 | 1 个竞品有,你完全没有 | 中 | 可能是该竞品的独特定位,需判断是否适合你 |
| 4 | 竞品排名比你高很多(你 20+,竞品 Top 5) | 中高 | 已有页面,优化即可,成本最低 |
第 4 层常被忽略但 ROI 最高——你已经有页面和一定权重,只需优化内容就能提升,比新建页面快得多。
一、数据准备
需要导出的数据
竞品数据(每个竞品一份):
工具:Ahrefs Site Explorer → Organic Keywords
筛选:排名 1–20(只看有实际流量的词)
导出字段:Keyword, Position, Volume, KD (难度), URL
自己站点的数据(两个来源,都要):
来源 1:Ahrefs 导出自己域名的 Organic Keywords(同样筛选 1–50)
来源 2:GSC 导出 Query 数据(更准,包含 Ahrefs 抓不到的长尾词)
为什么两个来源都要:Ahrefs 的关键词库有限,很多长尾词它没收录,但 GSC 有真实曝光数据。只用 Ahrefs 会误判“我没有这个词”,实际上你有曝光只是排名靠后。
竞品选择
选 3 个,类型要分开:
竞品 A:行业头部(流量最大的,看天花板在哪)
竞品 B:规模相近(最直接的对标,最容易学)
竞品 C:细分领域强者(在某个应用场景做得特别深的)
只选头部竞品的问题:它们的词往往需要很高权重才能排,对你不现实。规模相近的竞品的词才是可达成的目标。
二、Skill 说明书
# Skill:竞品关键词差距分析
## 何时使用
- 季度内容规划前
- 注册表覆盖度审计(第 46 篇)显示覆盖率已达标,需要发现新机会
- 进入新的应用场景或产品线前
## 输入
- 竞品关键词 CSV × 3(Ahrefs/Semrush 导出)
- 自己站点的关键词 CSV(Ahrefs 导出)
- GSC Query 导出 CSV(可选但强烈建议)
- `registry.json`(判断是否已规划)
## 执行步骤
### Step 1:运行集合运算脚本
执行 `scripts/competitor-gap.mjs`
脚本输出四类清单(对应上文四个价值层次)和机会分排序。
### Step 2:业务相关性过滤(你来判断,最关键的一步)
脚本输出的词里会有大量**不该做的词**。逐个判断并剔除:
**必须剔除的类型**:
1. 业务范围外的产品/服务
例:竞品做泵也做阀门,"ball valve manufacturer" 应剔除
(除非你也要进入这个品类)
2. 竞品品牌词
例:"[竞品名] pump price" — 无法也不该争
3. 售后/维修类词(如果你不提供该服务)
例:"pump repair service near me"
4. 地域限定词与你的市场不符
例:"pump manufacturer india"(如果你不做印度市场)
5. 二手/租赁类词
例:"used centrifugal pump for sale"
**保留但降低优先级的类型**:
- 搜索量 < 50 的极长尾词(除非商业意图极强)
- 难度 (KD) > 60 且你当前域名权重较低的词
**剔除时必须记录原因**——下次分析时可以直接复用剔除列表,
避免重复判断。
### Step 3:判断可否复用现有页面(你来判断)
对每个保留的词,检查是否可以并入现有页面而不是新建:
判断规则:
1. 从注册表找出与该词**意图相同且主题相近**的现有页面
2. 如果找到,且该词可以作为现有页面的 SKW → 归为"优化现有页面"
3. 如果该词的意图或主题与所有现有页面都不同 → 归为"新建页面"
**倾向复用而非新建**:新页面需要从零积累权重,把词加进已有权重的
页面通常更快见效。只有当该词代表一个独立的搜索意图时才新建。
⚠️ 加词到现有页面前,必须确认不会违反注册表的唯一性约束
(该词不能是其他页面的 PKW),也不能在该页面的 NKW 里。
### Step 4:输出机会清单
## 输出格式
竞品关键词差距分析报告
分析日期:[YYYY-MM-DD] 竞品:[A域名]([N] 词)、[B域名]([N] 词)、[C域名]([N] 词) 自有词库:[N] 词(Ahrefs [N] + GSC 独有 [N])
差距总览
| 层次 | 词数 | 剔除后 | 说明 |
|---|---|---|---|
| L1 三竞品都有你没有 | 84 | 31 | 最高价值 |
| L2 两竞品有你排名>50 | 46 | 19 | 高价值 |
| L3 单竞品有你没有 | 210 | 22 | 需判断定位 |
| L4 你有但排名远落后 | 38 | 28 | ROI 最高 |
优先级 1:优化现有页面([N] 项,ROI 最高)
1. /applications/centrifugal-pump-chemical-transfer/
当前排名情况:
| 词 | 你的排名 | 竞品最佳 | 月搜索量 |
|---|---|---|---|
| chemical transfer pump selection | 24 | 3(竞品B) | 210 |
| acid transfer pump material | 未收录 | 5(竞品A) | 140 |
竞品 B 的对应页面:[URL] 竞品覆盖了什么你没有的:
- 具体酸类的材料对照表(硫酸/盐酸/硝酸各自的推荐材料)
- 浓度分界点的明确说明
- CIP 清洗兼容性说明
建议动作:
- 在现有页面新增 H2 “Material Selection by Chemical Type”, 含 HTML 表格覆盖 5–6 种常见介质
- 将
acid transfer pump material加入注册表该页的 SKW - 预估工时:2 小时
- 唯一性检查:✅ 该词不是其他页面的 PKW,不在本页 NKW 中
优先级 2:新建页面([N] 项)
1. PKW: centrifugal pump for mining dewatering
- 竞品覆盖:A(排名 4)、B(排名 8)、C(排名 6)→ 三家都有
- 月搜索量:320 难度 KD:28
- 机会分:89(搜索量 × 竞品数 ÷ 难度)
- 意图:engineering 建议 page_type:cluster-app
- 建议 slug:/applications/centrifugal-pump-mining-dewatering/
- 相关长尾词(可作为 SKW):
- mine dewatering pump selection(85)
- slurry pump for mining(60)
- 竞品页面共同覆盖的内容点(写作时应包含): □ 含固量对泵型选择的影响 □ 磨损件材料(高铬合金 vs 橡胶衬里) □ 井下安装的空间限制 □ 相关标准:ISO 5199、AS 2417
已剔除([N] 项,附原因)
| 词 | 竞品覆盖 | 搜索量 | 剔除原因 |
|---|---|---|---|
| ball valve manufacturer | A, B | 1200 | 业务范围外(不做阀门) |
| pump repair service | B, C | 480 | 不提供维修服务 |
| used pump for sale | A | 320 | 不做二手业务 |
| [竞品A] pump price | A | 90 | 竞品品牌词 |
(剔除列表已保存至 data/excluded-keywords.json,
下次分析自动过滤)
数据缺口提醒
以下高价值词缺少难度数据(Ahrefs 未收录), 建议手动查一次 SERP 判断可行性:
- centrifugal pump for battery electrolyte(新兴场景,估计难度低)
## 禁止行为
- 不要把所有差距词都列为机会(业务相关性过滤是必需步骤)
- 剔除词必须记录原因并保存到排除列表
- 不要在未做唯一性检查的情况下建议把词加入现有页面
- 优先级 1(优化现有页面)必须排在优先级 2(新建)之前呈现
三、集合运算脚本
// scripts/competitor-gap.mjs
import fs from 'fs/promises';
import { parse } from 'csv-parse/sync';
// ── 读取数据 ──
async function readKeywordCsv(file) {
const raw = await fs.readFile(file, 'utf8');
const rows = parse(raw, { columns: true, skip_empty_lines: true });
return rows.map(r => ({
keyword: (r.Keyword ?? r.keyword ?? '').toLowerCase().trim(),
position: Number(r.Position ?? r.position ?? 999),
volume: Number(r.Volume ?? r.volume ?? 0),
kd: Number(r.KD ?? r.Difficulty ?? r.kd ?? 0) || null,
url: r.URL ?? r.url ?? null,
})).filter(r => r.keyword);
}
const competitorFiles = [
{ name: 'competitor-a', file: 'data/competitor-a.csv' },
{ name: 'competitor-b', file: 'data/competitor-b.csv' },
{ name: 'competitor-c', file: 'data/competitor-c.csv' },
];
const competitors = [];
for (const c of competitorFiles) {
try {
competitors.push({ name: c.name, keywords: await readKeywordCsv(c.file) });
} catch {
console.error(`Warning: ${c.file} not found, skipping`);
}
}
const ownAhrefs = await readKeywordCsv('data/own-keywords.csv');
// GSC 数据(更全的长尾词来源)
let ownGsc = [];
try {
const raw = await fs.readFile('data/gsc-queries.csv', 'utf8');
const rows = parse(raw, { columns: true, skip_empty_lines: true });
ownGsc = rows.map(r => ({
keyword: (r.Query ?? '').toLowerCase().trim(),
position: Number(r.Position ?? 999),
volume: null,
impressions: Number(r.Impressions ?? 0),
})).filter(r => r.keyword);
} catch {
console.error('Warning: gsc-queries.csv not found');
}
// 合并自有词库:GSC 优先(数据更准)
const ownMap = new Map();
for (const k of ownAhrefs) ownMap.set(k.keyword, k);
for (const k of ownGsc) {
const existing = ownMap.get(k.keyword);
ownMap.set(k.keyword, {
...k,
volume: existing?.volume ?? null,
source: existing ? 'both' : 'gsc-only',
});
}
// 排除列表(历史剔除的词,自动过滤)
let excluded = new Set();
try {
const list = JSON.parse(
await fs.readFile('data/excluded-keywords.json', 'utf8')
);
excluded = new Set(list.map(e => e.keyword.toLowerCase()));
} catch {}
// ── 构建竞品词的覆盖映射 ──
const competitorCoverage = new Map(); // keyword → [{ competitor, position, url }]
for (const comp of competitors) {
for (const kw of comp.keywords) {
if (excluded.has(kw.keyword)) continue;
if (!competitorCoverage.has(kw.keyword)) {
competitorCoverage.set(kw.keyword, {
keyword: kw.keyword,
volume: kw.volume,
kd: kw.kd,
competitors: [],
});
}
competitorCoverage.get(kw.keyword).competitors.push({
name: comp.name,
position: kw.position,
url: kw.url,
});
}
}
// ── 分层 ──
const L1 = []; // 三竞品都有,你完全没有
const L2 = []; // 两竞品有,你排名 > 50
const L3 = []; // 单竞品有,你完全没有
const L4 = []; // 你有但排名远落后于竞品
for (const [keyword, data] of competitorCoverage) {
const own = ownMap.get(keyword);
const compCount = data.competitors.length;
const bestCompPosition = Math.min(...data.competitors.map(c => c.position));
// 机会分:搜索量 × 竞品覆盖数 ÷ (难度 or 30)
const opportunityScore = Math.round(
(data.volume || 0) * compCount / (data.kd || 30)
);
const entry = { ...data, own: own ?? null, bestCompPosition, opportunityScore };
if (!own) {
if (compCount >= 3) L1.push(entry);
else if (compCount === 1) L3.push(entry);
else L2.push(entry); // 2 家有,你没有 → 归 L2
} else {
if (own.position > 50 && compCount >= 2) {
L2.push(entry);
} else if (own.position > 15 && bestCompPosition <= 5) {
// 你有排名但远落后 → ROI 最高
L4.push({ ...entry, gap: own.position - bestCompPosition });
}
}
}
// 按机会分排序
const byScore = (a, b) => b.opportunityScore - a.opportunityScore;
L1.sort(byScore);
L2.sort(byScore);
L3.sort(byScore);
L4.sort((a, b) => b.gap - a.gap);
// ── 关联注册表:判断是否已规划 ──
const registry = JSON.parse(await fs.readFile('registry.json', 'utf8'));
const registeredKeywords = new Set(
registry.map(r => r.keyword.toLowerCase())
);
const registeredPkw = new Map(
registry.map(r => [r.pkw.toLowerCase(), r.page_slug])
);
function annotate(list) {
return list.map(e => ({
...e,
alreadyInRegistry: registeredKeywords.has(e.keyword),
isOtherPagePkw: registeredPkw.get(e.keyword) ?? null,
}));
}
console.log(JSON.stringify({
summary: {
competitorsAnalyzed: competitors.length,
competitorKeywordsTotal: competitorCoverage.size,
ownKeywords: ownMap.size,
ownSources: {
ahrefs: ownAhrefs.length,
gscOnly: ownGsc.filter(k => !ownAhrefs.find(a => a.keyword === k.keyword)).length,
},
excludedFiltered: excluded.size,
layers: {
L1_allCompetitorsHaveIt: L1.length,
L2_twoCompetitorsYouWeak: L2.length,
L3_oneCompetitorHasIt: L3.length,
L4_youHaveButFarBehind: L4.length,
},
},
// 交给模型做业务相关性过滤
L1: annotate(L1).slice(0, 60),
L2: annotate(L2).slice(0, 40),
L3: annotate(L3).slice(0, 40),
L4: annotate(L4).slice(0, 40),
}, null, 2));
四、排除列表的维护
data/excluded-keywords.json 是这个 Skill 的长期资产——跑几轮之后,它会自动过滤掉大量不需要重复判断的词。
[
{
"keyword": "ball valve manufacturer",
"reason": "业务范围外(不做阀门)",
"excludedAt": "2026-08-04",
"reconsiderIf": "进入阀门品类"
},
{
"keyword": "pump repair service",
"reason": "不提供维修服务",
"excludedAt": "2026-08-04",
"reconsiderIf": "开展售后服务业务"
}
]
reconsiderIf 字段很有用——业务变化时可以快速找出哪些词该重新纳入。
五、分析频率
| 频率 | 理由 |
|---|---|
| 每季度一次 | 竞品的关键词布局变化周期是月级到季度级,更频繁没有新信息 |
| 进入新品类前 | 分析该品类的竞品,评估进入难度 |
| 竞品有明显动作时 | 如竞品上线新产品线或大批内容 |
不要每月跑——数据变化不大,但每次都要花 1–2 小时做业务相关性过滤,投入产出不划算。
六、辅助 Skills 六篇小结
到这里,辅助体系六篇完成。六个 Skill 覆盖了内容生产的完整链路:
| Skill | 输入 | 输出 | 位置 |
|---|---|---|---|
| 第 51 篇 关键词聚类分类 | 原始词库 | 带意图标签的聚类 | 词 → 页面规划 |
| 第 56 篇 竞品差距分析(本篇) | 竞品词库 | 机会清单 | 发现新词 |
| 第 53 篇 Content Brief | 注册表 + 数据 | 写作简报 | 规划 → 写作 |
| 第 54 篇 pSEO 批量生成 | 数据矩阵 | 批量页面 | 规模化生产 |
| 第 52 篇 Meta 批量生成 | 注册表 | Title/Description | 页面元数据 |
| 第 55 篇 Schema 批量生成 | entities.yaml | JSON-LD | 结构化数据 |
典型的工作流串联:
第 56 篇 竞品差距分析 → 发现机会词
↓
第 51 篇 聚类分类 → 确定意图和归属页面
↓
第 45 篇 注册表审计 → 检查冲突后写入注册表
↓
第 53 篇 Content Brief → 生成写作简报
↓
第 54 篇 pSEO 生成(批量)或人工写作(单页)
↓
第 52 篇 Meta 生成 + 第 55 篇 Schema 生成
↓
第 50 篇 健康度报告 → 验证整体状态
十二个 Skill(审计 6 + 辅助 6)合起来就是一套完整的 SEO/GEO 运营操作系统。
→ 信任体系建设:认证 / 案例 / About Us 的 B2B 写法
RELATED / 相关推荐
接着读这些
按同一分类、系列与标签为你挑选。
AI Skill:Content Brief 生成——给定关键词输出写作简报
写作前的 30 分钟决定了文章质量的上限。这个 Skill 把注册表字段、竞品 SERP 结构、内链机会自动组装成一份写作简报,让写作者(人或 AI)拿到就能动笔,不需要再做调研和结构设计。
AI Skill:关键词聚类与商业/工程/知识意图分类
把第 02 篇手工做的意图分类和聚类固化成可复用 Skill:脚本负责词形规则的确定性判定和聚类的机械合并,模型负责规则覆盖不到的模糊词和 SERP 意图判断,输出可直接导入注册表的 CSV。
AI Skill:Meta 标签批量生成(基于注册表字段)
Title 和 Meta description 是 SEO 里唯一同时影响排名和点击率的元素,但手写几十个页面的 Meta 极其枯燥。这个 Skill 用注册表字段驱动批量生成,脚本负责长度校验和唯一性检查,模型负责在字符限制内写出有点击欲的文案。