基于TypeScript实现GSC数据关联审计分数与流量异常检测
构建可扩展的SEO仪表盘(TypeScript方案)
需求概述
- 合并Google Search Console(GSC)页面数据与审计分数(兼容URL格式差异)
- 计算审计分数与点击量的Pearson相关系数,验证流量与分数的关联
- 自动检测日展示量/点击量的统计显著性异常波动
- 识别排名4-20的"Striking Distance"页面作为快速优化机会
已有数据样本
// GSC数据(来自Search Console API) const gscPages = [ { url: '/blog/react-seo', clicks: 1240, impressions: 18500, ctr: 0.067, position: 4.2 }, { url: '/blog/meta-tags', clicks: 380, impressions: 9200, ctr: 0.041, position: 8.7 }, { url: '/blog/seo-audit', clicks: 55, impressions: 3100, ctr: 0.018, position: 19.1 }, ]; // 审计分数(来自审计工具) const auditResults = [ { url: '/blog/react-seo', score: 88, issues: [] }, { url: '/blog/meta-tags', score: 71, issues: [] }, { url: '/blog/seo-audit', score: 44, issues: [] }, ]; // 日展示量时序数据 const dailyData = [ { date: '2026-02-01', value: 8500 }, { date: '2026-02-02', value: 8900 }, { date: '2026-02-05', value: 24800 }, // 疑似峰值 { date: '2026-02-07', value: 2100 }, // 疑似谷值 ];
解决方案实现
1. 合并GSC数据与审计结果(处理URL格式差异)
先实现URL标准化逻辑,统一处理协议、域名、尾部斜杠等差异,再基于标准化后的URL合并数据集。
// URL标准化:去除协议、域名,统一尾部无斜杠 const normalizeUrl = (url: string): string => { const parsed = new URL(url.startsWith('http') ? url : `https://example.com${url}`); let path = parsed.pathname; if (path !== '/' && path.endsWith('/')) { path = path.slice(0, -1); } return path; }; // 合并GSC数据与审计结果 const mergeSeoData = (gscData: typeof gscPages, auditData: typeof auditResults) => { const auditMap = new Map(auditData.map(item => [normalizeUrl(item.url), item])); return gscData.map(gscItem => { const normalizedUrl = normalizeUrl(gscItem.url); const auditItem = auditMap.get(normalizedUrl); return { ...gscItem, score: auditItem?.score ?? null, issues: auditItem?.issues ?? [] }; }); }; // 使用示例 const mergedData = mergeSeoData(gscPages, auditResults);
2. 计算Pearson相关系数(审计分数与点击量)
实现Pearson相关系数计算逻辑,同时筛选高潜力优化机会(排名4-20且审计分数较低的页面)。
// 计算Pearson相关系数 const calculatePearsonCorrelation = (mergedData: ReturnType<typeof mergeSeoData>) => { const validPoints = mergedData.filter(item => item.score !== null); if (validPoints.length < 2) return { correlation: 0, topOpportunities: [] }; const n = validPoints.length; const sumX = validPoints.reduce((acc, item) => acc + item.score!, 0); const sumY = validPoints.reduce((acc, item) => acc + item.clicks, 0); const sumXY = validPoints.reduce((acc, item) => acc + (item.score! * item.clicks), 0); const sumX2 = validPoints.reduce((acc, item) => acc + (item.score! ** 2), 0); const sumY2 = validPoints.reduce((acc, item) => acc + (item.clicks ** 2), 0); // Pearson公式计算 const numerator = n * sumXY - sumX * sumY; const denominator = Math.sqrt((n * sumX2 - sumX ** 2) * (n * sumY2 - sumY ** 2)); const correlation = denominator === 0 ? 0 : numerator / denominator; // 筛选Top优化机会:排名4-20且分数低于70的页面,按展示量排序 const topOpportunities = validPoints .filter(item => item.position >=4 && item.position <=20 && item.score! <70) .sort((a,b) => b.impressions - a.impressions) .slice(0,10); return { correlation: Number(correlation.toFixed(3)), topOpportunities }; }; // 使用示例 const correlationResult = calculatePearsonCorrelation(mergedData);
3. 时序数据异常检测(Z-score法)
基于Z-score统计量检测异常,超出±2倍标准差的数据视为统计显著异常。
// 检测时序数据异常 const detectAnomalies = (timeSeries: typeof dailyData, threshold = 2) => { const values = timeSeries.map(item => item.value); const n = values.length; if (n < 3) return []; // 计算均值和标准差 const mean = values.reduce((acc, val) => acc + val, 0) / n; const variance = values.reduce((acc, val) => acc + (val - mean) **2, 0) / n; const stdDev = Math.sqrt(variance); // 筛选Z-score超出阈值的点 return timeSeries.filter(item => { const zScore = Math.abs((item.value - mean) / stdDev); return zScore > threshold; }); }; // 使用示例 const anomalyResult = detectAnomalies(dailyData);
4. 识别"Striking Distance"页面与排名分桶
筛选排名4-20的页面,同时统计各排名区间的页面数量。
// 生成排名分桶与Striking Distance列表 const getRankingBuckets = (gscData: typeof gscPages) => { const buckets = [ { range: '1-3', count: 0 }, { range: '4-10', count: 0 }, { range: '11-20', count: 0 }, { range: '21+', count: 0 }, ]; const strikingDistance = gscData.filter(item => item.position >=4 && item.position <=20); // 统计各分桶数量 gscData.forEach(item => { const pos = item.position; if (pos <=3) buckets[0].count++; else if (pos <=10) buckets[1].count++; else if (pos <=20) buckets[2].count++; else buckets[3].count++; }); return { strikingDistance, rankingBuckets: buckets }; }; // 使用示例 const { strikingDistance, rankingBuckets } = getRankingBuckets(gscPages);
预期输出示例
相关系数与优化机会
{ correlation: 0.741, topOpportunities: [ { url: '/blog/seo-audit', clicks: 55, impressions: 3100, ctr: 0.018, position: 19.1, score: 44, issues: [] } ] }
异常检测结果
[ { date: '2026-02-05', value: 24800 }, // 峰值异常 { date: '2026-02-07', value: 2100 } // 谷值异常 ]
排名分桶统计
[ { range: '1-3', count: 0 }, { range: '4-10', count: 2 }, { range: '11-20', count: 1 }, { range: '21+', count: 0 } ]
内容的提问来源于stack exchange,提问作者Al Amin
相关产品推荐
相关产品推荐

