关于 blog-post-counter
blog-post-counter 是一个用于内容与 SEO 分析的 Skill,涵盖博客文章数量比较、内容新鲜度审计和关键词映射。它结合站点地图检查与 Ahrefs 数据,生成竞争对比及 HTML 报告。
主要功能
- 01比较目标与竞争者的博客文章数量
- 02审计文章更新时间与发布节奏
- 03映射非品牌信息类关键词差距
- 04生成整合式 HTML 报告
适用场景
- 01比较多个公司的博客内容规模
- 02识别内容更新停滞或时间戳失真
- 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
详细介绍
博客文章计数 + 关键词映射技能
一个技能包含两种工作流:
- 博客文章计数 — 统计并比较目标公司与竞争对手的博客文章数量,可选择进行内容新鲜度审查
- 关键词映射 — 获取竞争对手的自然关键词(美国、非品牌、信息类),并与目标公司的关键词覆盖范围交叉比对,以发现差距和机会
工作流 A — 博客文章计数
步骤 1 — 将每家公司解析为 URL
对于提供的每家公司名称:
- 如果直接给出了 URL,则使用该 URL。
- 如果只给出了名称,则运行
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 步 — 新鲜度输出
在博客数量表旁边(或下方)生成逐域名的新鲜度摘要。比较多个域名时,使用可视化工具渲染并排对比组件。组件必须显示:
- 时间戳类型徽标 —“构建时生成的时间戳 ⚠️”或“真实日期 ✓”
- 关键统计数据 — 文章总数、最新文章日期、距最新文章的天数、唯一日期数
- 新鲜度条形图 — 每个分档显示数量和百分比
- 每月发布活动 — 按月份显示的小型条形图(仅博客文章,过去 12 个月)
- 警示横幅:
- 构建时生成的时间戳 → 显示警告横幅,说明这些日期并非真实编辑日期
- 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,000HIGH— 搜索量 500–999MEDIUM— 搜索量 200–499LOW— 搜索量 100–199NEW— 搜索量 < 100
输出中的每一行代表一个竞品关键词。每行包含以下字段:
keywordvolumecompetitor_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展示