跳到主要内容
IM智引科技

研究 / 外贸与 B2B 增长

AI Skill:竞品关键词差距分析

竞品有排名而你没有的词,是最确定的增长机会——市场需求已经被验证。这个 Skill 用脚本做多竞品词库的集合运算,找出共同覆盖但你缺失的词(最高价值),模型负责按业务相关性过滤和排序,输出可直接进注册表的机会清单。

BLKTECH 编辑部2026年8月4日12 分钟难度 实战低成本Node.jsAhrefsAI Agent

差距分析的核心是集合运算:多个竞品都有排名而你完全没有的词价值最高(需求被多方验证)。脚本做集合运算并计算机会分(搜索量×竞品覆盖数÷难度),模型负责剔除业务不相关词和判断是否可复用现有页面,输出待建页面和待优化页面两类清单。

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 写法

NEXT ACTION / 下一步

继续系列:AI 外贸站建设全系列

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

继续

RELATED / 相关推荐

接着读这些

按同一分类、系列与标签为你挑选。