結論先講
SEO 是持續的工作,不是一次性設定。 排名會因為演算法更新而掉、內部連結會因為改版而壞、索引數量會莫名減少。你需要自動化監控,讓系統在出問題時主動通知你,而不是三個月後才發現流量腰斬。
真實場景:我們的部落格有一次更新 Quartz 版本,不小心把 sitemap 的 URL 格式改了。結果 Google 在兩週內把 40% 的頁面標記為「已發現 - 尚未建立索引」。如果沒有監控,我根本不會知道。
你需要監控什麼
先搞清楚要盯什麼,再決定用什麼工具。
| 監控項目 | 為什麼重要 | 檢查頻率 |
|---|---|---|
| 索引數量 | 突然減少 = 有頁面被移除索引 | 每日 |
| 排名變化 | 核心關鍵字掉出前 10 = 流量大跌 | 每週 |
| CWV 分數 | 效能退化 = 排名下降 | 每週 |
| 壞連結 | 404 頁面 = 浪費爬取額度 + 使用者體驗差 | 每週 |
| Sitemap 狀態 | Sitemap 壞了 = Google 找不到新頁面 | 每日 |
| 網站可用性 | 網站掛了 = 一切歸零 | 每 5 分鐘 |
| 新增反向連結 | 有人引用你 = 可能帶來流量和排名提升 | 每週 |
免費監控工具一覽
1. Google Search Console(必裝)
GSC 是你的 SEO 儀表板,免費且直接來自 Google。核心報表:
- 成效報表 — 曝光次數、點擊次數、平均排名、CTR
- 涵蓋範圍 — 有多少頁面被索引、多少有問題
- Core Web Vitals — 真實使用者的效能數據
- 手動操作 — Google 是否對你的網站有懲罰
2. Google Analytics 4(流量追蹤)
GA4 負責告訴你「使用者從哪來、看了什麼、停了多久」。SEO 相關要看的:
- Organic Search 流量趨勢 — 整體 SEO 健康度
- Landing Page 報表 — 哪些頁面從搜尋帶來流量
- Engagement Rate — 使用者是不是一進來就離開
3. Bing Webmaster Tools(別忘了 Bing)
Bing 的市佔率在 AI 搜尋崛起後有所增長(Copilot 用 Bing)。而且 Bing Webmaster 有一些 GSC 沒有的功能:
- SEO 報告 — 直接告訴你哪些頁面有 SEO 問題
- 反向連結報告 — 免費看到誰連結到你(GSC 也有但 Bing 的更詳細)
- 站台掃描 — 自動檢查常見 SEO 問題
GSC API:用程式抓資料
手動登入 GSC 看數據太慢。用 API 自動抓,寫成腳本定期跑。
設定步驟
- 到 Google Cloud Console 建立專案
- 啟用 Search Console API
- 建立 Service Account,下載 JSON 金鑰
- 在 GSC 將 Service Account email 加為使用者
Node.js 範例:抓取搜尋效能
// fetch-gsc-data.mjs
import { google } from 'googleapis';
import { readFileSync, writeFileSync } from 'fs';
const SITE_URL = 'https://your-blog.com';
const KEY_FILE = './gsc-service-account.json';
async function getSearchPerformance(startDate, endDate) {
const auth = new google.auth.GoogleAuth({
keyFile: KEY_FILE,
scopes: ['https://www.googleapis.com/auth/webmasters.readonly'],
});
const searchconsole = google.searchconsole({ version: 'v1', auth });
const res = await searchconsole.searchanalytics.query({
siteUrl: SITE_URL,
requestBody: {
startDate,
endDate,
dimensions: ['query', 'page'],
rowLimit: 100,
// 只看排名前 20 的關鍵字
dimensionFilterGroups: [{
filters: [{
dimension: 'query',
operator: 'notContains',
expression: 'site:',
}],
}],
},
});
return res.data.rows || [];
}
// 取最近 7 天的資料
const endDate = new Date().toISOString().split('T')[0];
const startDate = new Date(Date.now() - 7 * 86400000).toISOString().split('T')[0];
const data = await getSearchPerformance(startDate, endDate);
writeFileSync('gsc-weekly.json', JSON.stringify(data, null, 2));
console.log(`取得 ${data.length} 筆搜尋數據`);索引數量監控
// check-index-count.mjs
import { google } from 'googleapis';
async function getIndexedPages(siteUrl, keyFile) {
const auth = new google.auth.GoogleAuth({
keyFile,
scopes: ['https://www.googleapis.com/auth/webmasters.readonly'],
});
const searchconsole = google.searchconsole({ version: 'v1', auth });
// 用 sitemaps API 查看索引狀態
const res = await searchconsole.sitemaps.list({ siteUrl });
const sitemaps = res.data.sitemap || [];
for (const sitemap of sitemaps) {
console.log(`Sitemap: ${sitemap.path}`);
console.log(` 已提交: ${sitemap.contents?.[0]?.submitted || 'N/A'}`);
console.log(` 已索引: ${sitemap.contents?.[0]?.indexed || 'N/A'}`);
}
return sitemaps;
}
await getIndexedPages('https://your-blog.com', './gsc-service-account.json');自動化週報腳本
把上面的資料整理成報告,每週自動寄到 Discord:
// weekly-seo-report.mjs
import { readFileSync } from 'fs';
const DISCORD_WEBHOOK = process.env.DISCORD_WEBHOOK_URL;
async function generateReport() {
// 本週 vs 上週的數據(假設已經存好)
const thisWeek = JSON.parse(readFileSync('gsc-weekly-current.json', 'utf-8'));
const lastWeek = JSON.parse(readFileSync('gsc-weekly-previous.json', 'utf-8'));
// 計算總體指標
const totalClicks = thisWeek.reduce((sum, r) => sum + (r.clicks || 0), 0);
const totalImpressions = thisWeek.reduce((sum, r) => sum + (r.impressions || 0), 0);
const avgPosition = thisWeek.reduce((sum, r) => sum + (r.position || 0), 0) / thisWeek.length;
const lastClicks = lastWeek.reduce((sum, r) => sum + (r.clicks || 0), 0);
const clickChange = ((totalClicks - lastClicks) / lastClicks * 100).toFixed(1);
// 找出排名下降最多的關鍵字
const drops = findRankingDrops(thisWeek, lastWeek);
const report = [
'## SEO 週報',
`**期間**: ${getDateRange()}`,
'',
'### 總覽',
`- 點擊數: **${totalClicks}** (${clickChange > 0 ? '+' : ''}${clickChange}%)`,
`- 曝光數: **${totalImpressions}**`,
`- 平均排名: **${avgPosition.toFixed(1)}**`,
'',
'### 排名變化警示',
...drops.map(d => `- ⚠️ "${d.query}" 排名從 ${d.oldPos} → ${d.newPos}`),
].join('\n');
return report;
}
function findRankingDrops(current, previous) {
const prevMap = new Map(previous.map(r => [r.keys?.[0], r.position]));
return current
.filter(r => {
const oldPos = prevMap.get(r.keys?.[0]);
return oldPos && r.position - oldPos > 3; // 排名掉 3 位以上
})
.map(r => ({
query: r.keys?.[0],
oldPos: prevMap.get(r.keys?.[0]).toFixed(1),
newPos: r.position.toFixed(1),
}))
.sort((a, b) => b.newPos - a.oldPos - (a.newPos - a.oldPos))
.slice(0, 5);
}
async function sendToDiscord(content) {
await fetch(DISCORD_WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content }),
});
}
const report = await generateReport();
await sendToDiscord(report);
console.log('週報已送出');壞連結檢查
壞連結不只影響使用者體驗,也浪費 Googlebot 的爬取預算。
// check-broken-links.mjs
import { readFileSync } from 'fs';
import { JSDOM } from 'jsdom';
async function checkLinks(sitemapUrl) {
// 從 sitemap 抓所有頁面
const sitemapRes = await fetch(sitemapUrl);
const sitemapXml = await sitemapRes.text();
const dom = new JSDOM(sitemapXml, { contentType: 'text/xml' });
const urls = [...dom.window.document.querySelectorAll('loc')]
.map(el => el.textContent);
console.log(`從 sitemap 取得 ${urls.length} 個頁面`);
const brokenLinks = [];
for (const pageUrl of urls) {
const res = await fetch(pageUrl);
if (!res.ok) {
brokenLinks.push({ url: pageUrl, status: res.status });
continue;
}
const html = await res.text();
const pageDom = new JSDOM(html);
const links = [...pageDom.window.document.querySelectorAll('a[href]')]
.map(a => a.href)
.filter(href => href.startsWith('http'));
// 檢查每個外部連結(加 rate limit 避免被封)
for (const link of links) {
try {
const linkRes = await fetch(link, { method: 'HEAD', redirect: 'follow' });
if (linkRes.status >= 400) {
brokenLinks.push({
source: pageUrl,
target: link,
status: linkRes.status,
});
}
} catch (e) {
brokenLinks.push({ source: pageUrl, target: link, error: e.message });
}
// 每個請求間隔 500ms
await new Promise(r => setTimeout(r, 500));
}
}
return brokenLinks;
}
const broken = await checkLinks('https://your-blog.com/sitemap.xml');
console.log(`找到 ${broken.length} 個壞連結`);
console.table(broken);即時警報設定
不是所有事情都適合放在週報裡。有些問題需要馬上知道。
警報優先度
| 事件 | 優先度 | 通知方式 |
|---|---|---|
| 網站掛了(HTTP 5xx) | 緊急 | 立即 Discord 通知 |
| 索引數量大幅下降(>20%) | 高 | 立即 Discord 通知 |
| CWV 變差(紅燈) | 中 | 週報 + Discord |
| 排名掉出前 10 | 中 | 週報 |
| 新增壞連結 | 低 | 週報 |
Discord Webhook 通知
// alert.mjs — 通用警報函式
async function sendAlert(webhookUrl, title, message, level = 'warning') {
const colors = {
critical: 0xff0000, // 紅色
warning: 0xffaa00, // 橘色
info: 0x0099ff, // 藍色
};
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
embeds: [{
title: `[SEO 警報] ${title}`,
description: message,
color: colors[level] || colors.info,
timestamp: new Date().toISOString(),
}],
}),
});
}
// 用法
await sendAlert(
process.env.DISCORD_WEBHOOK_URL,
'索引數量異常下降',
'索引頁面從 287 降至 215(-25%)。\n請檢查 GSC 涵蓋範圍報表。',
'critical'
);Uptime 監控
網站掛了是最嚴重的 SEO 問題。免費方案推薦:
- UptimeRobot(免費 50 個監控點)— 每 5 分鐘檢查一次
- Freshping(免費 50 個監控點)— 支援多地區
- 自建方案 — 用 cron + curl,便宜但需要維護
// uptime-check.mjs — 最簡單的自建方案
async function checkUptime(url) {
const start = Date.now();
try {
const res = await fetch(url, { signal: AbortSignal.timeout(10000) });
const responseTime = Date.now() - start;
if (!res.ok) {
await sendAlert(WEBHOOK, `${url} 回應 ${res.status}`, `回應時間: ${responseTime}ms`, 'critical');
} else if (responseTime > 3000) {
await sendAlert(WEBHOOK, `${url} 回應過慢`, `回應時間: ${responseTime}ms`, 'warning');
}
return { url, status: res.status, responseTime, ok: res.ok };
} catch (e) {
await sendAlert(WEBHOOK, `${url} 無法連線`, e.message, 'critical');
return { url, status: 0, error: e.message, ok: false };
}
}
// 每 5 分鐘跑一次(用 cron 排程)
await checkUptime('https://your-blog.com');建立 SEO Dashboard
把所有數據整合到一個地方看。免費方案:
- Google Looker Studio(原 Data Studio)— 直接串 GSC、GA4,最省事
- Notion Database — 手動或 API 更新,適合小團隊
- 自建網頁 — 用 Chart.js 或 D3.js,完全客製化
Looker Studio 快速設定
1. 開啟 lookerstudio.google.com
2. 建立新報表 → 新增資料來源 → Search Console
3. 選擇你的網站 → 「網站曝光」資料集
4. 拖入以下圖表:
- 折線圖:每日點擊數趨勢
- 表格:前 20 名關鍵字 + 排名 + 點擊
- 圓餅圖:裝置分佈(手機 vs 桌面)
- 計分卡:總點擊、總曝光、平均排名
5. 設定自動更新:每天更新
6. 分享連結給團隊
完整監控架構
每 5 分鐘:Uptime Check
↓ 異常 → Discord 警報
每天凌晨:GSC API → 索引數量
↓ 變化 > 20% → Discord 警報
每週一:GSC API → 搜尋效能
↓ 排名變化 + 點擊趨勢 → 週報 → Discord
每週三:壞連結掃描
↓ 新增壞連結 → 週報 → Discord
每月一:CWV 檢查(PageSpeed API)
↓ 退化 → Discord 警報
所有數據 → Looker Studio Dashboard
FAQ
Q: 小部落格也需要 SEO 監控嗎?
需要,但不用太複雜。最基本的:每週看一次 GSC、設一個 uptime 監控就好。等文章超過 50 篇、有穩定流量後,再加自動化腳本。
Q: GSC API 有使用限制嗎?
有,每天 200 個請求。對小網站來說很夠用。如果你有很多網站要監控,可以用 batch request 或分散到不同時段。
Q: 排名突然掉了怎麼辦?
先確認是個別頁面還是全站。個別頁面:檢查內容是否過時、連結是否壞了。全站:可能是 Google 演算法更新。去 Google Search Status Dashboard 看有沒有公告。
Q: 免費工具夠用嗎?還是需要買付費工具?
小型部落格免費工具完全夠。GSC + GA4 + UptimeRobot + 自己寫的腳本可以覆蓋 90% 的需求。付費工具(Ahrefs、SEMrush)主要價值在競品分析和關鍵字研究,不是監控。
Q: Discord 和 Slack 選哪個做通知?
看你團隊用什麼。兩個都支援 Webhook,寫法幾乎一樣。個人推薦 Discord,免費版功能更完整,而且 Webhook 設定更簡單。