关于 seo-analytics
面向网站运营者和分析人员的 SEO 分析技能,调用 Google PageSpeed Insights API 生成性能与 SEO 报告。需提供 API 密钥,并输出控制台摘要和交互式 HTML 报告。
主要功能
- 01分析性能、无障碍、最佳实践与 SEO
- 02评估 Core Web Vitals
- 03生成移动端与桌面端指标
- 04输出交互式 HTML 图表报告
- 05需要 Google PageSpeed Insights API 密钥
适用场景
- 01检查网站页面速度
- 02生成 Core Web Vitals 报告
- 03审查网站 SEO 与 Lighthouse 指标
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ rebyteai-template/ rebyte-skills --skill seo-analytics安装后在 Claude Code 中输入/seo-analytics确认已加载。
附带文件analyze.tsREADME.md
详细介绍
SEO 分析技能
使用 Google PageSpeed Insights API,为任意网站生成全面的 SEO 和性能分析报告。
适用场景
- “分析 https://example.com 的 SEO”
- “检查我的网站性能”
- “为……生成 Core Web Vitals 报告”
- “……的页面速度得分是多少?”
- “对……进行 SEO 审核”
API 密钥要求
重要:此技能需要 Google PageSpeed Insights API 密钥。如果用户尚未提供 API 密钥,必须先向用户索取,然后才能继续。
未提供 API 密钥时
如果用户请求 SEO 分析,但未提供 API 密钥,请回复:
要运行 SEO 分析,我需要 Google PageSpeed Insights API 密钥。
获取 API 密钥的方法(约需 2 分钟):
- 前往 Google Cloud Console
- 创建新项目,或选择现有项目
- 导航至 APIs & Services > Library
- 搜索“PageSpeed Insights API”,然后点击 Enable
- 前往 APIs & Services > Credentials
- 点击 Create Credentials > API Key
- 复制新生成的 API 密钥
获取密钥后,请提供给我,我就会开始分析。
API 密钥用法
可通过以下两种方式提供 API 密钥:
- 作为命令行参数:
npx ts-node analyze.ts <URL> <API_KEY> - 作为环境变量:
PAGESPEED_API_KEY
快速开始
User: "Analyze https://eng0.ai"
# If no API key provided, ask user for one first
# With API key:
Output:
- ./seo-report.html (interactive report with charts)
- Console summary with key metrics
执行方法
第 1 步:运行分析脚本
npx ts-node analyze.ts https://example.com YOUR_API_KEY
或使用环境变量:
PAGESPEED_API_KEY=your_key npx ts-node analyze.ts https://example.com
第 2 步:内联执行(替代方法)
在当前目录中创建并运行 analyze.ts:
// analyze.ts - SEO Analysis Script
const url = process.argv[2];
const apiKey = process.argv[3] || process.env.PAGESPEED_API_KEY;
if (!url) {
console.error('Usage: npx ts-node analyze.ts <URL> [API_KEY]');
console.error('Or set PAGESPEED_API_KEY environment variable');
process.exit(1);
}
interface AuditResult {
title?: string;
description?: string;
score?: number | null;
displayValue?: string;
numericValue?: number;
}
interface PageSpeedResult {
lighthouseResult: {
categories: {
performance: { score: number };
accessibility: { score: number };
'best-practices': { score: number };
seo: { score: number };
};
audits: {
[key: string]: AuditResult;
};
};
}
async function analyzeUrl(targetUrl: string, strategy: 'mobile' | 'desktop'): Promise<PageSpeedResult> {
let apiUrl = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=${encodeURIComponent(targetUrl)}&strategy=${strategy}&category=performance&category=accessibility&category=best-practices&category=seo`;
if (apiKey) {
apiUrl += `&key=${apiKey}`;
}
console.log(`Analyzing ${strategy}...`);
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
return response.json() as Promise<PageSpeedResult>;
}
function getScoreColor(score: number): string {
if (score >= 90) return '#0cce6b';
if (score >= 50) return '#ffa400';
return '#ff4e42';
}
function generateReport(mobile: PageSpeedResult, desktop: PageSpeedResult, targetUrl: string): string {
const m = mobile.lighthouseResult;
const d = desktop.lighthouseResult;
const mobileScores = {
performance: Math.round(m.categories.performance.score * 100),
accessibility: Math.round(m.categories.accessibility.score * 100),
bestPractices: Math.round(m.categories['best-practices'].score * 100),
seo: Math.round(m.categories.seo.score * 100),
};
const desktopScores = {
performance: Math.round(d.categories.performance.score * 100),
accessibility: Math.round(d.categories.accessibility.score * 100),
bestPractices: Math.round(d.categories['best-practices'].score * 100),
seo: Math.round(d.categories.seo.score * 100),
};
const webVitals = {
mobile: {
lcp: m.audits['largest-contentful-paint'],
tbt: m.audits['total-blocking-time'],
cls: m.audits['cumulative-layout-shift'],
fcp: m.audits['first-contentful-paint'],
si: m.audits['speed-index'],
tti: m.audits['interactive'],
},
desktop: {
lcp: d.audits['largest-contentful-paint'],
tbt: d.audits['total-blocking-time'],
cls: d.audits['cumulative-layout-shift'],
fcp: d.audits['first-contentful-paint'],
si: d.audits['speed-index'],
tti: d.audits['interactive'],
},
};
// Extract top opportunities
const opportunities: string[] = [];
for (const [key, audit] of Object.entries(m.audits)) {
if (audit.score !== undefined && audit.score < 0.9 && audit.title && audit.description) {
opportunities.push(`<li><strong>${audit.title}</strong>: ${audit.description.split('.')[0]}.</li>`);
}
}
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SEO Report - ${targetUrl}</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; line-height: 1.6; }
.container { max-width: 1200px; margin: 0 auto; padding: 2rem; }
h1 { font-size: 1.8rem; margin-bottom: 0.5rem; }
.url { color: #666; font-size: 0.9rem; margin-bottom: 2rem; word-break: break-all; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 1.5rem; margin-bottom: 2rem; }
.card { background: white; border-radius: 8px; padding: 1.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.card h2 { font-size: 1.1rem; margin-bottom: 1rem; color: #444; }
.scores { display: flex; justify-content: space-around; text-align: center; }
.score-item { display: flex; flex-direction: column; align-items: center; }
.score-circle { width: 60px; height: 60px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 1.2rem; font-weight: bold; color: white; margin-bottom: 0.5rem; }
.score-label { font-size: 0.75rem; color: #666; }
.vitals-table { width: 100%; border-collapse: collapse; }
.vitals-table th, .vitals-table td { padding: 0.75rem; text-align: left; border-bottom: 1px solid #eee; }
.vitals-table th { font-weight: 500; color: #666; font-size: 0.85rem; }
.good { color: #0cce6b; }
.needs-improvement { color: #ffa400; }
.poor { color: #ff4e42; }
.chart-container { position: relative; height: 300px; }
.opportunities { list-style: none; }
.opportunities li { padding: 0.75rem 0; border-bottom: 1px solid #eee; font-size: 0.9rem; }
.opportunities li:last-child { border-bottom: none; }
.timestamp { text-align: center; color: #999; font-size: 0.8rem; margin-top: 2rem; }
</style>
</head>
<body>
<div class="container">
<h1>SEO Analysis Report</h1>
<p class="url">${targetUrl}</p>
<div class="grid">
<div class="card">
<h2>Mobile Scores</h2>
<div class="scores">
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(mobileScores.performance)}">${mobileScores.performance}</div>
<span class="score-label">Performance</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(mobileScores.accessibility)}">${mobileScores.accessibility}</div>
<span class="score-label">Accessibility</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(mobileScores.bestPractices)}">${mobileScores.bestPractices}</div>
<span class="score-label">Best Practices</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(mobileScores.seo)}">${mobileScores.seo}</div>
<span class="score-label">SEO</span>
</div>
</div>
</div>
<div class="card">
<h2>Desktop Scores</h2>
<div class="scores">
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(desktopScores.performance)}">${desktopScores.performance}</div>
<span class="score-label">Performance</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(desktopScores.accessibility)}">${desktopScores.accessibility}</div>
<span class="score-label">Accessibility</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(desktopScores.bestPractices)}">${desktopScores.bestPractices}</div>
<span class="score-label">Best Practices</span>
</div>
<div class="score-item">
<div class="score-circle" style="background: ${getScoreColor(desktopScores.seo)}">${desktopScores.seo}</div>
<span class="score-label">SEO</span>
</div>
</div>
</div>
</div>
<div class="grid">
<div class="card">
<h2>Core Web Vitals - Mobile</h2>
<table class="vitals-table">
<tr><th>Metric</th><th>Value</th><th>Status</th></tr>
<tr>
<td>Largest Contentful Paint (LCP)</td>
<td>${webVitals.mobile.lcp?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.lcp?.numericValue || 0) <= 2500 ? 'good' : (webVitals.mobile.lcp?.numericValue || 0) <= 4000 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.lcp?.numericValue || 0) <= 2500 ? 'Good' : (webVitals.mobile.lcp?.numericValue || 0) <= 4000 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>Total Blocking Time (TBT)</td>
<td>${webVitals.mobile.tbt?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.tbt?.numericValue || 0) <= 200 ? 'good' : (webVitals.mobile.tbt?.numericValue || 0) <= 600 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.tbt?.numericValue || 0) <= 200 ? 'Good' : (webVitals.mobile.tbt?.numericValue || 0) <= 600 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>Cumulative Layout Shift (CLS)</td>
<td>${webVitals.mobile.cls?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.cls?.numericValue || 0) <= 0.1 ? 'good' : (webVitals.mobile.cls?.numericValue || 0) <= 0.25 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.cls?.numericValue || 0) <= 0.1 ? 'Good' : (webVitals.mobile.cls?.numericValue || 0) <= 0.25 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>First Contentful Paint (FCP)</td>
<td>${webVitals.mobile.fcp?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.fcp?.numericValue || 0) <= 1800 ? 'good' : (webVitals.mobile.fcp?.numericValue || 0) <= 3000 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.fcp?.numericValue || 0) <= 1800 ? 'Good' : (webVitals.mobile.fcp?.numericValue || 0) <= 3000 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>Speed Index</td>
<td>${webVitals.mobile.si?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.si?.numericValue || 0) <= 3400 ? 'good' : (webVitals.mobile.si?.numericValue || 0) <= 5800 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.si?.numericValue || 0) <= 3400 ? 'Good' : (webVitals.mobile.si?.numericValue || 0) <= 5800 ? 'Needs Work' : 'Poor'}</td>
</tr>
<tr>
<td>Time to Interactive (TTI)</td>
<td>${webVitals.mobile.tti?.displayValue || 'N/A'}</td>
<td class="${(webVitals.mobile.tti?.numericValue || 0) <= 3800 ? 'good' : (webVitals.mobile.tti?.numericValue || 0) <= 7300 ? 'needs-improvement' : 'poor'}">${(webVitals.mobile.tti?.numericValue || 0) <= 3800 ? 'Good' : (webVitals.mobile.tti?.numericValue || 0) <= 7300 ? 'Needs Work' : 'Poor'}</td>
</tr>
</table>
</div>
<div class="card">
<h2>Score Comparison</h2>
<div class="chart-container">
<canvas id="comparisonChart"></canvas>
</div>
</div>
</div>
<div class="card">
<h2>Optimization Opportunities</h2>
<ul class="opportunities">
${opportunities.slice(0, 10).join('\n ') || '<li>No significant issues found. Great job!</li>'}
</ul>
</div>
<p class="timestamp">Generated on ${new Date().toLocaleString()}</p>
</div>
<script>
new Chart(document.getElementById('comparisonChart'), {
type: 'radar',
data: {
labels: ['Performance', 'Accessibility', 'Best Practices', 'SEO'],
datasets: [
{
label: 'Mobile',
data: [${mobileScores.performance}, ${mobileScores.accessibility}, ${mobileScores.bestPractices}, ${mobileScores.seo}],
borderColor: '#4285f4',
backgroundColor: 'rgba(66, 133, 244, 0.2)',
},
{
label: 'Desktop',
data: [${desktopScores.performance}, ${desktopScores.accessibility}, ${desktopScores.bestPractices}, ${desktopScores.seo}],
borderColor: '#34a853',
backgroundColor: 'rgba(52, 168, 83, 0.2)',
}
]
},
options: {
scales: { r: { beginAtZero: true, max: 100 } },
plugins: { legend: { position: 'bottom' } }
}
});
</script>
</body>
</html>`;
}
async function main() {
try {
console.log('Starting SEO analysis for:', url);
console.log('');
// Sequential requests to avoid rate limiting
const mobile = await analyzeUrl(url, 'mobile');
const desktop = await analyzeUrl(url, 'desktop');
const report = generateReport(mobile, desktop, url);
const fs = await import('fs');
const outputPath = './seo-report.html';
fs.writeFileSync(outputPath, report);
// Print summary
const m = mobile.lighthouseResult;
const d = desktop.lighthouseResult;
console.log('');
console.log('=== SEO Analysis Complete ===');
console.log('');
console.log('Mobile Scores:');
console.log(` Performance: ${Math.round(m.categories.performance.score * 100)}/100`);
console.log(` Accessibility: ${Math.round(m.categories.accessibility.score * 100)}/100`);
console.log(` Best Practices: ${Math.round(m.categories['best-practices'].score * 100)}/100`);
console.log(` SEO: ${Math.round(m.categories.seo.score * 100)}/100`);
console.log('');
console.log('Desktop Scores:');
console.log(` Performance: ${Math.round(d.categories.performance.score * 100)}/100`);
console.log(` Accessibility: ${Math.round(d.categories.accessibility.score * 100)}/100`);
console.log(` Best Practices: ${Math.round(d.categories['best-practices'].score * 100)}/100`);
console.log(` SEO: ${Math.round(d.categories.seo.score * 100)}/100`);
console.log('');
console.log('Core Web Vitals (Mobile):');
console.log(` LCP: ${m.audits['largest-contentful-paint']?.displayValue || 'N/A'}`);
console.log(` TBT: ${m.audits['total-blocking-time']?.displayValue || 'N/A'}`);
console.log(` CLS: ${m.audits['cumulative-layout-shift']?.displayValue || 'N/A'}`);
console.log('');
console.log(`Report saved to: ${outputPath}`);
console.log('Open the HTML file in a browser to view the interactive report.');
} catch (error) {
console.error('Error:', error);
process.exit(1);
}
}
main();
运行命令:
npx ts-node analyze.ts https://eng0.ai YOUR_API_KEY
输出
此技能会生成:
- 控制台摘要 - 快速查看所有得分
- seo-report.html - 交互式 HTML 报告,包含:
- 移动端和桌面端得分卡片
- 带状态指示器的 Core Web Vitals 表格
- 对比移动端和桌面端的雷达图
- 优先级最高的优化机会
指标说明
Lighthouse 得分(0-100)
- 性能:页面加载速度和交互性
- 无障碍:页面对残障用户的友好程度
- 最佳实践:现代 Web 开发标准
- SEO:搜索引擎优化基础
Core Web Vitals
| 指标 | 良好 | 需要改进 | 较差 |
|---|---|---|---|
| LCP(最大内容绘制) | < 2.5s | 2.5-4s | > 4s |
| TBT(总阻塞时间) | < 200ms | 200-600ms | > 600ms |
| CLS(累积布局偏移) | < 0.1 | 0.1-0.25 | > 0.25 |
API 详情
使用 Google PageSpeed Insights API v5:
- 免费:基本用法无需 API 密钥
- 速率限制:每 100 秒约 25 次请求
- 数据:真实的 Lighthouse 审核结果
输出示例
=== SEO Analysis Complete ===
Mobile Scores:
Performance: 89/100
Accessibility: 95/100
Best Practices: 100/100
SEO: 91/100
Core Web Vitals (Mobile):
LCP: 1.8 s
TBT: 150 ms
CLS: 0.02
Report saved to: ./seo-report.html
评论
支持 Markdown