mcpField
B
blog-post-counter

blog-post-counter

github.com/infrasity-labs营销与增长150130 次安装17 周前更新
代码仓库

关于 blog-post-counter

blog-post-counter 是一个用于内容与 SEO 分析的 Skill,涵盖博客文章数量比较、内容新鲜度审计和关键词映射。它结合站点地图检查与 Ahrefs 数据,生成竞争对比及 HTML 报告。

主要功能

  1. 01比较目标与竞争者的博客文章数量
  2. 02审计文章更新时间与发布节奏
  3. 03映射非品牌信息类关键词差距
  4. 04生成整合式 HTML 报告

适用场景

  1. 01比较多个公司的博客内容规模
  2. 02识别内容更新停滞或时间戳失真
  3. 03发现竞争者有排名而目标未覆盖的关键词

MCP 相关

工作流使用 Ahrefs MCP 获取关键词、流量和页面数据;输出包含整合式 HTML 报告。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/infrasity-labs/dev-gtm-claude-skills --skill blog-post-counter

安装后在 Claude Code 中输入/blog-post-counter确认已加载。

附带文件README.md

详细介绍

博客文章计数 + 关键词映射技能

一个技能包含两种工作流:

  1. 博客文章计数 — 统计并比较目标公司与竞争对手的博客文章数量,可选择进行内容新鲜度审查
  2. 关键词映射 — 获取竞争对手的自然关键词(美国、非品牌、信息类),并与目标公司的关键词覆盖范围交叉比对,以发现差距和机会


工作流 A — 博客文章计数


步骤 1 — 将每家公司解析为 URL

对于提供的每家公司名称:

  1. 如果直接给出了 URL,则使用该 URL。
  2. 如果只给出了名称,则运行 curl -sL "https://<likely-domain>/robots.txt",确认网站存在并查找站点地图。如果返回 404,则快速搜索 "<company name>" official website,找到正确的域名。

步骤 2 — 查找站点地图

从 robots.txt 中提取 Sitemap: 行。常见模式:

  • 单个站点地图:Sitemap: https://example.com/sitemap.xml
  • 站点地图索引:包含多行 Sitemap:,或引用子站点地图的 sitemap-index.xml

如果 robots.txt 中没有站点地图,请按以下顺序尝试这些备用方法:

/sitemap.xml
/sitemap_index.xml
/sitemap-index.xml
/blog/sitemap.xml

步骤 3 — 统计博客文章

获取站点地图,并统计属于博客文章的 URL。使用以下 bash 模式:

curl -sL -A "Mozilla/5.0" "https://example.com/sitemap.xml" \
  | grep -o '<loc>[^<]*</loc>' \
  | grep -iE '/blog/|/posts?/|/articles?/|/news/' \
  | grep -v -E '^<loc>https://[^/]+/blog/?</loc>$' \
  | wc -l

重要: 排除博客索引页本身(例如 /blog 或 /blog/)——只统计单篇文章的 URL。

站点地图索引处理: 如果站点地图是索引(包含 <sitemap> 标签而非 <url> 标签),则提取子站点地图 URL,并获取博客专用站点地图:

curl -sL -A "Mozilla/5.0" "https://example.com/sitemap-index.xml" \
  | grep -o '<loc>[^<]*</loc>' \
  | grep -i 'blog'
# Then fetch that sub-sitemap and count

通过 JS 渲染的网站: 部分博客页面通过 JavaScript 渲染,可能无法通过站点地图获取文章数量。在这种情况下,可使用 site:example.com/blog 搜索运算符进行估算。

边缘情况:

  • 部分网站使用 /resources/、/insights/、/learn/ 或 /hub/,而不是 /blog/ — 如果按 /blog/ 搜索结果为 0,请检查站点地图结构。
  • 如果站点地图非常大(>1MB),请搜索多种博客路径模式。

步骤 4 — 整理输出

收集完所有数量后,生成排名表,并突出显示目标公司:

Blog Post Count — [Target] vs Competitors
==========================================

Rank  Company          Posts   URL
────  ───────────────  ──────  ──────────────────────
 1    Hackmamba        137     hackmamba.io           ← COMPETITOR
 2    Infrasity        137     infrasity.com          ← COMPETITOR  
 3  ▶ Kubiya           95      kubiya.ai              ← TARGET
 4    Orgn             7       orgn.com               ← COMPETITOR

▶ = your company   Total companies analysed: 4

然后添加简短总结:

  • 目标公司的排名(例如,“第 3 名,共 4 家”)
  • 与领先者的差距(例如,“比排名第一的竞争对手少 42 篇文章”)
  • 与上一名的差距(如果并非排名第一)
  • 任何值得注意的情况(例如,网站非常新,或并列第一)

内容新鲜度审查(仅限博客文章)

每当用户询问内容新鲜度、近期情况、发布频率,或该审查能为博客文章数量比较提供有用背景时,都要执行此审查。

步骤 5 — 提取博客文章的 lastmod 日期

对于每个域名,仅从站点地图中提取博客文章 URL 及其 lastmod 日期:

curl -sL "https://example.com/sitemap.xml" \
  | python3 -c "
import sys, re
content = sys.stdin.read()
urls = re.findall(r'<url>(.*?)</url>', content, re.DOTALL)
for u in urls:
    loc = re.search(r'<loc>(.*?)</loc>', u)
    lastmod = re.search(r'<lastmod>(.*?)</lastmod>', u)
    if loc and lastmod:
        l = loc.group(1)
        if re.search(r'/blog/|/posts?/|/articles?/|/news/', l) and not re.match(r'https?://[^/]+/(blog|posts?|articles?|news)/?$', l):
            print(lastmod.group(1), l)
"
步骤 6 — 检测构建时间戳膨胀

在将 lastmod 日期视为真实信号之前,请检查这些日期是每篇文章各自的编辑日期,还是构建时批量生成的时间戳:

from datetime import datetime, timezone

dates = [...]  # all lastmod values for blog posts

unique_dates = set(d[:10] for d in dates)  # compare date part only
if not dates:
    stamp_type = "UNKNOWN"
elif len(unique_dates) <= 3:
    # Likely build-time stamps — flag as unreliable
    stamp_type = "BUILD-TIME"
else:
    stamp_type = "REAL"

构建时间戳 = 所有(或几乎所有)博客文章共享同一日期,通常集中在很短的时间范围内(几秒到几分钟)。这在 Next.js/Vercel 等类似 SSG 框架中很常见,因为这些框架会在每次部署时重新生成站点地图。这些日期并不反映内容实际撰写或更新的时间。

真实日期 = 每篇文章都有不同的 lastmod,日期分布在数周或数月之间。这些是可信的内容新鲜度信号。

务必在输出中明确标记构建时间戳,让用户了解这些日期对 SEO 或内容分析没有参考意义。

第 7 步 — 计算新鲜度分档

使用今天的日期,按每篇博客文章的 lastmod 将其划分到以下分档:

分档时间
过去 30 天发布不到 30 天
30–90 天发布 30–89 天
91–180 天发布 90–179 天
181–365 天发布 180–364 天
超过 365 天发布 365 天或更久
from datetime import datetime, timezone

today = datetime.now(timezone.utc)
buckets = {"<30d": 0, "30-90d": 0, "91-180d": 0, "181-365d": 0, ">365d": 0}

for date_str in lastmod_dates:
    dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=timezone.utc)
    days = (today - dt).days
    if days < 30: buckets["<30d"] += 1
    elif days < 90: buckets["30-90d"] += 1
    elif days < 180: buckets["91-180d"] += 1
    elif days < 365: buckets["181-365d"] += 1
    else: buckets[">365d"] += 1

另请计算:

  • 最新文章日期及距今天数
  • 最早文章日期
  • 每月发布节奏 — 按 YYYY-MM 对文章分组,展示一段时间内的发布量
  • 距上次发布的天数 — 关键的陈旧度指标
第 8 步 — 新鲜度输出

在博客数量表旁边(或下方)生成逐域名的新鲜度摘要。比较多个域名时,使用可视化工具渲染并排对比组件。组件必须显示:

  1. 时间戳类型徽标 —“构建时生成的时间戳 ⚠️”或“真实日期 ✓”
  2. 关键统计数据 — 文章总数、最新文章日期、距最新文章的天数、唯一日期数
  3. 新鲜度条形图 — 每个分档显示数量和百分比
  4. 每月发布活动 — 按月份显示的小型条形图(仅博客文章,过去 12 个月)
  5. 警示横幅:
    • 构建时生成的时间戳 → 显示警告横幅,说明这些日期并非真实编辑日期
    • 90 天或更久没有发布文章 → 显示警告横幅,指出内容缺口
    • 真实日期且近期有发布文章 → 显示信息横幅,确认该信号可信

摘要中应包含的新鲜度 SEO 解读:

  • 构建时生成的时间戳:指出 Google 会忽略虚高的 lastmod 日期,因此这些日期不提供新鲜度信号
  • 真实日期,文章发布于 30 天内:新鲜度信号强,对 SEO 有利
  • 真实日期,90 天或更久没有发布文章:存在内容缺口,可能会失去新鲜度排名信号,落后于定期发布内容的竞争对手
  • 真实日期,批量发布激增后随即减少:指出这一模式(可能是迁移或冲刺,并非持续稳定的发布节奏)

注意事项(工作流 A)

  • 始终从站点地图统计 — 这是最准确、最完整的数据来源。
  • robots.txt 始终是查找站点地图 URL 的首选位置。
  • /blog 索引页绝不能计作一篇文章。
  • 如果公司完全没有博客栏目,报告数量为 0 并注明这一点。
  • 站点地图数据可能会滞后数天;如有相关影响,请注明。
  • 按顺序查询所有公司(每家公司使用一个 bash 代码块),以避免触发速率限制。
  • 评估新鲜度时,始终仅筛选博客文章 URL — 排除静态页面、服务页面、术语表等。
  • 绝不要将构建时生成的时间戳视为真实的新鲜度数据。始终检测并标记。


工作流 B — 关键词映射

将竞争对手的自然搜索关键词覆盖范围与目标域名进行对照,以发现内容缺口和排名机会。


第 1 步 — 获取域名指标

通过 Ahrefs site-explorer-metrics 获取竞争对手和目标域名的 org_keywords 与 org_traffic:

tool: site-explorer-metrics
params:
  target: <domain>
  mode: subdomains
  date: <latest>
  select: org_keywords, org_traffic

对两个域名都执行此操作。org_keywords 数量用于确定需要多少批次才能获取完整的关键词集。


第 2 步 — 获取竞争对手关键词

使用 Ahrefs site-explorer-organic-keywords,并设置以下固定筛选条件,获取竞争对手的完整关键词集:

tool: site-explorer-organic-keywords
params:
  target: <competitor domain>
  country: us
  mode: subdomains
  is_branded: false
  is_informational: true
  limit: 500
  select: keyword, volume, best_position, sum_traffic, best_position_url, is_branded

使用不同的 order_by 值获取多个批次,以尽可能提高覆盖率:

  • 批次 1:order_by: sum_traffic:desc
  • 批次 2:order_by: volume:desc
  • 批次 3:order_by: best_position:asc

每获取一个批次后,都要按关键词对目前收集到的所有批次去重。每个关键词保留 best_position 最低(排名最佳)的条目。

持续分批获取,直到连续两个批次都没有新关键词,或批次数超过第 1 步估算的关键词总量。


第 3 步 — 获取目标域名关键词

对目标域名执行完全相同的获取流程 — 使用相同的筛选条件、相同的多种排序方式,以及相同的去重逻辑。

tool: site-explorer-organic-keywords
params:
  target: <target domain>
  country: us
  mode: subdomains
  is_branded: false
  is_informational: true
  limit: 500
  select: keyword, volume, best_position, sum_traffic, best_position_url, is_branded

将结果存储为查询映射:{ keyword → { best_position, sum_traffic, best_position_url } }


第 4 步 — 交叉比对

针对第 2 步中的每个竞品关键词,在第 3 步的目标映射表中查找。分配以下状态:

双方均有排名:

  • WINNING — 目标排名 < 竞品排名(目标领先)
  • IMPROVE — 竞品排名 < 目标排名(竞品领先)

目标没有排名:

  • CRITICAL — 搜索量 ≥ 1,000
  • HIGH — 搜索量 500–999
  • MEDIUM — 搜索量 200–499
  • LOW — 搜索量 100–199
  • NEW — 搜索量 < 100

输出中的每一行代表一个竞品关键词。每行包含以下字段:

  • keyword
  • volume
  • competitor_rank(best_position)
  • competitor_traffic(sum_traffic)
  • target_rank(best_position,或 null)
  • target_traffic(sum_traffic,或 null)
  • target_url(best_position_url,或 null)
  • status

说明(工作流 B)

  • 行 = 仅包含竞品关键词。报告回答的问题是:“竞品在哪些方面获得了自然流量,而目标完全没有相关排名?”
  • is_branded=false 和 is_informational=true 筛选条件在提取数据时应用,而不是后处理时应用。这样从一开始就能确保数据集干净。
  • 竞品和目标的关键词提取使用相同的筛选参数,确保比较对称。
  • 如果目标关键词存在于映射表中,但竞品关键词查询返回 null,则该竞品关键词未被捕获——它可能不在 Ahrefs 跟踪的前 100 个排名位置内。
  • 统计卡片中的计数必须仅反映筛选后的(非品牌、信息型)数据集。


综合 HTML 报告

两个工作流的数据都会汇总到一个 HTML 报告文件中。先完整运行工作流 A 和工作流 B,收集所有数据,然后一次性生成报告。


报告结构(从上到下)

1. 页眉
  • 深色背景(#0f1117)
  • 报告标题:[Target] vs [Competitor] — 内容与关键词报告
  • 副标题:目标域名 · 竞品域名 · 日期 · 已应用的筛选条件
  • 两个域名标签:目标(绿色圆点)和竞品(红色圆点),分别显示各自的 org_keywords 和 org_traffic
2. 博客文章数量部分
  • 部分标题:“博客文章数量”
  • 排名表格:排名 · 公司 · 文章数 · URL · 目标/竞品标签
  • 突出显示目标行
  • 摘要行:目标排名、与领先者的差距、与上一名的差距
3. 内容新鲜度部分
  • 部分标题:“内容新鲜度”
  • 每个域名显示一张卡片,包含:
    • 时间戳类型徽章(构建时 ⚠️ 或真实日期 ✓)
    • 文章总数
    • 最新文章日期 + 距今天数
    • 新鲜度区间细分(每个区间一条柱形图)
    • 每月发布活动(过去 12 个月,柱状图)
    • 如果 90 天以上没有发布文章,或检测到构建时的时间戳,则显示提醒横幅
4. 关键词映射部分
  • 部分标题:“关键词差距 — [竞品] vs [目标]”
  • 统计卡片:All · Winning · Improve · Critical · High · Medium · Low · New(显示计数,点击可筛选)
  • 单个表格,每个竞品关键词占一行:
    • 关键词 · 搜索量条 · 竞品排名 · 竞品流量 · 目标排名 · 目标流量 · 目标 URL · 状态徽章
  • 默认排序:竞品流量降序
  • 点击表头可按所有列排序
5. 页脚
  • 数据来源:Ahrefs API(关键词数据)· Sitemap(博客文章数量)
  • 数据提取日期
  • 筛选条件:美国 · 非品牌 · 信息型意图

样式

  • 字体: DM Sans(正文)+ DM Mono(数字、徽章、等宽值)
  • 页眉: #0f1117 背景,#edeae3 文字,#7a8190 副标题
  • 正文背景: #f7f6f2
  • 卡片/表格: #ffffff 背景,#e4e2db 边框,#faf9f6 表头行
  • 状态徽章颜色:
    • WINNING — 绿色(#14724e,背景:#e5f5ee)
    • IMPROVE — 橙色(#994d00,背景:#fff0e0)
    • CRITICAL — 红色(#c0392b,背景:#fce8e6)
    • HIGH — 琥珀色(#b85c00,背景:#fdf0e0)
    • MEDIUM — 黄色(#7c5c00,背景:#fdf7e0)
    • LOW — 青绿色(#1a6e7a,背景:#e0f4f7)
    • NEW — 灰色(#555,背景:#efefef)
  • 排名颜色: 排名 1–3 为绿色 · 排名 4–6 为中绿色 · 排名 7–10 为橙色 · 排名 11 及以上为红色
  • 流量条: 高度 3px,按数据集中的最大流量缩放,使用低饱和度填充色(#ccc8be)
  • 所有分区都以可见分隔线隔开,并使用 DM Mono 大写分区标签

生成规则

  • 在编写任何 HTML 之前,收集 Workflow A 和 Workflow B 的所有数据
  • 将所有数据作为内联 JavaScript 数组注入——渲染时不获取外部数据
  • 生成单个自包含 .html 文件——除 Google Fonts CDN 外不依赖任何外部资源
  • 保存到 /mnt/user-data/outputs/report-[target]-vs-[competitor].html
  • 保存后使用 present_files 展示

评论

支持 Markdown