結論先講

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 自動抓,寫成腳本定期跑。

設定步驟

  1. 到 Google Cloud Console 建立專案
  2. 啟用 Search Console API
  3. 建立 Service Account,下載 JSON 金鑰
  4. 在 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

把所有數據整合到一個地方看。免費方案:

  1. Google Looker Studio(原 Data Studio)— 直接串 GSC、GA4,最省事
  2. Notion Database — 手動或 API 更新,適合小團隊
  3. 自建網頁 — 用 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 設定更簡單。


本系列文章

  1. SEO 基礎與注意事項
  2. 技術 SEO 實作
  3. 內容 SEO 策略
  4. 網站搬家 SEO
  5. GSC 實戰指南
  6. Open Graph 與社群分享
  7. AEO 基礎
  8. AEO 內容策略
  9. SEO vs AEO 整合策略
  10. Core Web Vitals 效能優化
  11. SEO/AEO 監控自動化(本篇)
  12. 案例:從 0 到被搜到