4つの指標を見に行くのをやめて「週1レコード」に集約した話

  • #計測設計
  • #GSC
  • #GA4
  • #Bing
  • #SEO
  • #自動化
4つの指標を見に行くのをやめて「週1レコード」に集約した話

この記事について

ゼロから始めたAI執筆ブログが検索エンジンにどう発見・評価されていくかを追うために、GSC・Google索引カバレッジ・Bing・GA4という4つの指標をバラバラに見に行くのをやめ、週1回1レコードへ集約するスクリプトを作った。副産物として、robots.txtが実在しないURLを指していたバグも見つかった。

実装ステータス: 実装済み・稼働中(毎週月曜9:15にlaunchdで起動)

4つのダッシュボードを毎回見に行く運用の限界

サイトを立ち上げてから検索エンジンにどう評価されていくかを追う場合、確認したい指標は最低でも4系統ある。

ソース見たいもの
GSC(Google Search Console)直近7日の表示回数・クリック数・上位クエリ
Google索引カバレッジサイトマップ上の全URLが実際にインデックスされているか
Bing Webmaster Tools直近7日の表示・クリック
GA4直近7日のセッション数・PV数・流入チャネル内訳

この4つを毎週手作業で開いて数字をメモしていくと、「先週と比べてどうだったか」を人間の記憶に頼ることになる。数字が並んでいても比較の基準がその場限りで消えるため、増減の意味付けができない。そこで、この4系統を1本のスクリプトにまとめ、週1回1行のレコードとしてJSONLに追記する仕組みを作った。

設計 — 取得・追記・台帳・通知の4段

やっていることは単純で、4系統を順番に叩いて1つの辞書にまとめ、機械可読なJSONLと人間可読なMarkdown台帳の両方に出力し、最後にDiscordへ短い週報を流す。

scripts/zashstudio_observatory.py
  → sites/zashstudio/docs/metrics/observatory.jsonl   (1行=1週分のスナップショット)
  → sites/zashstudio/docs/metrics/OBSERVATORY.md       (時系列の人間用台帳)
  → Discord通知(前週比のdelta付き)

Discordに流す文面は、前週の記録と突き合わせて増減を出す。

🔭 定点観測 2026-07-27
GA4: 42sess/58PV (+6) | GSC: 表示210 クリック3 (+40) | Google索引: 12/18 | Bing: 表示5 クリック0

この「前週比」を出すために必要なのは、前の週の記録が壊れていないことだ。この前提が、後述する事故で崩れた。

fail-soft設計 — 1系統が死んでも他の記録は続ける

4系統はそれぞれ別のAPIを叩くため、依存先ごとに独立して障害が起きる。1つのAPIキーが失効していても、残り3系統の記録と週報は止めたくない。そのため、各ソースの取得は個別にtry/exceptで囲み、失敗したソースだけnullにして残りは記録を続ける設計にした。

for key, fn in [
    ("gsc", lambda: fetch_gsc()),
    ("google_coverage", fetch_google_coverage),
    ("bing", lambda: fetch_bing(env)),
    ("ga4", lambda: fetch_ga4(env)),
]:
    try:
        rec[key] = fetch_with_retry(key, fn)
    except Exception as e:
        rec[key] = None
        errors.append(f"{key}: {type(e).__name__}: {e}")

ここまでは素直な設計だった。ところが、この設計のまま実際に運用したところ、fail-softにしてはいけないケースが1つあることが分かった。

事故 — 「回線が上がる前に走った」だけの失敗までfail-softしてしまった

2026年7月27日、実行機がネットワーク復帰直後にこのジョブを発火させた。DNSがまだ引けない状態で4系統すべてにアクセスし、当然すべて失敗した。fail-soft設計のとおり、スクリプトはこの失敗を「取得できなかった」として記録し、nullだらけのレコードをJSONLに追記した。

これが翌週の比較を壊した。前週比のdeltaは「1つ前の記録」を基準に計算するため、基準側がnullのレコードだと、翌週の正常な取得結果との差分が計算不能になる。API側の障害ではなく、実行機の起動タイミングの問題が、台帳という資産そのものを汚した格好になった。

対処は次の4点にまとめた。

  1. 判定を始める前に、実際に使うホスト(oauth2.googleapis.com・自サイトドメイン・ssl.bing.com)への疎通を待つ処理を先頭に入れる
  2. 待った上での失敗のうち、回線起因の失敗だけを自動リトライする(3回・30秒間隔)
  3. API側のエラー(403や400のような設定ミス)はリトライせず即座に諦める。回線障害と設定ミスを同じ扱いにすると、本物の設定ミスが再試行のノイズに埋もれる
  4. 全系統が回線起因で失敗した場合は、レコード自体を残さない。「観測できなかった週は空白が正しい」という判断で、null混じりの汚れたレコードより欠測のほうが後工程にとって安全
失敗の種類扱い
回線が上がっていない(DNS不通など)疎通待ち→ダメなら再試行→全滅なら記録しない
API側のエラー(403/400など)即座に諦めてnullとして記録(他ソースは継続)
同日に再実行して成功した場合直前の「取得失敗つき」レコードを新しい結果で置き換える

「1系統ずつのfail-soft」と「全滅パターンの検知」は別の設計判断だと分けて考えないと、この事故と同じ穴にまた落ちる。

副産物 — robots.txtが実在しないURLを指していた

このスクリプトを組む過程で、索引カバレッジの取得結果に「未発見」の状態が想定より多いことに気づいた。原因を辿ると、スクリプトとは無関係な場所にバグが見つかった。

// 修正前
Sitemap: ${baseUrl}/sitemap-index.xml

// 修正後
Sitemap: ${baseUrl}/sitemap.xml

robots.txtが案内していたサイトマップのURLが、実際には存在しないパスだった。実体のサイトマップは単一のsitemap.xmlとして配信されていたが、robots.txt側は複数サイトマップ構成を前提にしたsitemap-index.xmlという別名を案内し続けていた。検索エンジンのクローラーはrobots.txtのこの案内を頼りにサイトマップを見つけるため、存在しないパスを渡され続けると新規URLの発見が遅れる。これがGSCで「検出されていないURL」が多発していた一因だった。

定点観測を始める前は、この手のインフラ的な設定ミスは気づかれないまま放置されやすい。数字を毎週並べて記録し始めたことで、「数字がおかしい」ことをきっかけに「見に行く理由」が生まれた点は、計測を始める副次的な効果として大きかった。

動かしてみて分かったこと

  • 4系統をまとめて1レコードにする設計自体は単純だが、「取得できなかった」の扱いを1種類で済ませてはいけないという教訓が実運用で出た。回線障害・API設定ミス・全滅、の3パターンを分けて初めて、台帳という資産を守れる
  • インフラの副産物バグ(robots.txt)は、計測を始めなければ気づかなかった可能性が高い。定点観測は「数字を追う」以上に「数字の異常から設定ミスを掘り出す」役に立った

動作環境: Astro(静的サイトジェネレータ)+ Google Search Console API / Bing Webmaster API / GA4 Data API + Python 3
更新履歴: 2026-08-03 — 初稿
訂正履歴: なし