Claude Media
Claude CodeでGA4とSearch Consoleを突き合わせ、流入が伸びないページを探す

Claude CodeでGA4とSearch Consoleを突き合わせ、流入が伸びないページを探す

Search ConsoleとGA4のAPIをClaude Codeに叩かせ、順位は上がったのに流入が増えないページを洗い出す手順です。結合キーの正規化、期間の揃え方、順位を固定したCTRの読み方を扱います。

順位が上がったのに流入が増えないページは、2つのデータの突き合わせで見つかる

Search Consoleの平均掲載順位が改善しているのに、GA4のセッションは横ばい。この状態は片方のツールを眺めていても原因が分かりません。順位と表示回数はSearch Console、訪問後の動きはGA4が持っているからです。

手順は3段階です。Search Consoleのページ別データとGA4のランディングページ別データを取り、URLを正規化して結合し、順位帯をそろえたうえでCTRと流入を比べます。取得と結合の定型作業をClaude Codeにスクリプトとして書かせ、判断の部分は人間が握ります。

この記事のコードは、ここで使う2つのAPIの仕様に沿った骨組みです。実際のプロパティで動かすときは、認証まわりを自分の環境に合わせて足してください。

2つのAPIは何を返すのか

Search ConsoleのAPIで検索パフォーマンスを取るのは、Search Analyticsのqueryメソッドです。POST /sites/siteUrl/searchAnalytics/queryにJSONを投げると、指定したディメンションで集計した行が返ります。GA4側は、Data APIのrunReportメソッドがカスタムレポートを返します。

観点Search Console(searchAnalytics.query)GA4(Data API runReport)
主な指標Search Console(searchAnalytics.query)clicks / impressions / CTR / positionGA4(Data API runReport)sessionsなどの利用状況指標
ページを表す軸Search Console(searchAnalytics.query)page(URL)GA4(Data API runReport)landingPage(パス)
日付の基準Search Console(searchAnalytics.query)PT(UTC-7:00/8:00)GA4(Data API runReport)date(YYYYMMDD形式)。時間帯ディメンションはプロパティのタイムゾーン
1回の行数Search Console(searchAnalytics.query)rowLimitは1〜25,000、既定1,000GA4(Data API runReport)limit / offsetで調整
認証スコープSearch Console(searchAnalytics.query)webmasters.readonlyで読み取り可GA4(Data API runReport)別のOAuthまたはサービスアカウント

GA4のlandingPageは、セッション内で最初にページビューがあったページのパスです。クエリ文字列まで含めたい場合はlandingPagePlusQueryStringを使います。

Search Console側のリクエスト本文は次の形になります。

{
  "startDate": "2026-08-01",
  "endDate": "2026-08-31",
  "dimensions": ["page"],
  "rowLimit": 25000,
  "startRow": 0
}

dataStateを省略すると、確定したデータだけが返ります。allを指定すると直近の暫定データも含まれるので、前後比較の材料には向きません。

GA4側はlandingPageをディメンション、sessionsを指標にして同じ期間を指定します。Google検索経由に絞るなら、sessionSourceとsessionMediumでフィルタをかけます。

Claude Codeに渡す前提を先に決める

突き合わせのスクリプトは、認証情報を扱います。Claude Codeに書かせる前に、読ませないファイルと使わせる権限を決めておきます。

.claude/settings.jsonには、認証情報を置いたファイルをReadのdenyルールに入れます。パス指定はRead(./.env)やRead(./secrets/**)の形です。

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./secrets/**)"
    ]
  }
}

サービスアカウントの鍵ファイルをsecrets/に置く運用なら、この設定でClaudeのファイルツールからは読めなくなります。スクリプト自身は環境変数やパス指定で鍵を読めばよく、Claudeがその中身を見る必要はありません。

Search Console側は、読み取り専用のスコープwebmasters.readonlyで足ります。sitemapsやsitesのメソッドで登録や削除もできてしまうので、この分析では書き込み権限を渡さない構成にします。

CLAUDE.mdには、分析ルールを短く書いておきます。次のような断片が実用的です。

## GA4とSearch Consoleの突き合わせ
- 期間はSearch Consoleの日付(PT基準)に合わせ、暫定データは含めない
- 結合前に、両側のURLを同じ規則で正規化する
- 結合できなかった行の件数と上位10件を必ず出力する
- 集計後、Search ConsoleのUIの合計クリック数と照合する

GA4には自然言語で質問できる読み取り専用のMCPサーバーもあります。ただ、突き合わせは同じ手順を毎月回すので、質問のたびに結果が揺れる形より、スクリプトに固定した方が再現できます。MCPが増えるとコンテキストを圧迫する点は、MCPのツール定義がコンテキストを圧迫する理由で扱っています。

結合キーはURLの正規化で決まる

2つのデータをつなぐキーはページのURLですが、表記が食い違います。Search Consoleのpageは完全なURL、GA4のlandingPageはパスです。ここをそろえないと、結合しても行が大量に落ちます。

正規化で決めることは4つあります。

  • スキーム(https://)とホスト名を落とし、パスだけにする
  • クエリ文字列を残すか落とすかを決める(landingPageは落ちた形、landingPagePlusQueryStringは残った形)
  • 末尾スラッシュの有無を統一する
  • 大文字小文字の扱いを決める(Search Consoleのpageフィルタは大文字小文字を区別する)

pandasでの正規化と結合は、次のように書けます。

from urllib.parse import urlparse
import pandas as pd
 
def to_path(url: str) -> str:
    p = urlparse(url)
    path = p.path or "/"
    return path.rstrip("/") or "/"
 
gsc["path"] = gsc["page"].map(to_path)
ga4["path"] = ga4["landingPage"].map(to_path)
 
# GA4側は同じパスが複数行に分かれることがあるので、先に合算する
ga4 = ga4.groupby("path", as_index=False)["sessions"].sum()
 
merged = gsc.merge(ga4, on="path", how="outer", indicator=True)
print(merged["_merge"].value_counts())

indicator=Trueが肝です。left_onlyはSearch Consoleにあって、GA4のランディングページに現れなかったページです。right_onlyは、GA4にあって検索表示が無かったページを指します。この件数が全体の1割を超えるなら、分析の前に正規化を疑います。データ処理の書かせ方と検証ループの組み方は、pandasのデータ処理をClaude Codeに書かせる手順にまとめています。

期間と集計単位のずれを先に潰す

Search ConsoleのAPIは、startDateとendDateをPT(UTC-7:00/8:00)で解釈します。日本時間の日付とは半日以上ずれます。日単位の細かい突き合わせをするなら、この差を織り込む必要があります。月次や週次の集計なら、影響は小さくなります。

集計単位も違います。Search Consoleの指標は、検索結果での表示とクリックです。GA4のセッションは、サイト到着後の計測です。同じ「訪問」に見えても数は一致しません。

Search Consoleのヘルプでは、表の集計が次のように分かれると説明されています。

  • クエリ・国・デバイス・日付でグルーピングした表は、プロパティ単位の集計
  • ページと検索の見え方でグルーピングした表は、ページ単位の集計

APIでもaggregationTypeがこれに対応します。pageでグルーピングやフィルタをするときはautoにします。byPropertyを指定するとエラーになります。

つまり、ページ別の数字を足し上げても、プロパティ全体の合計とは一致しません。1つのクエリに同じサイトの2ページが出ると、プロパティ単位では1回の表示でも、ページ単位では両方に1回ずつ数えられるためです。突き合わせの検算に使う合計は、必ずpageでグルーピングした同じ条件の値を使います。

直近数日は、確定前の暫定データで数字が動きます。比較期間の終わりは、数日前に置きます。

GA4側の日付も注意が要ります。hourディメンションはプロパティのタイムゾーンで報告されると書かれていますが、dateの説明にはタイムゾーンの明記がありません。日単位で突き合わせるなら、プロパティ設定のレポートタイムゾーンを先に確認し、PTのSearch Consoleとのずれ幅を把握しておきます。

もう1つ、type(検索の種類)の指定でも数字が変わります。省略時の既定はwebで、DiscoverとGoogleニュースの結果は含まれません。

type対象
web対象検索の「すべて」タブ(既定。DiscoverとGoogleニュースは含まない)
image / video対象画像検索 / 動画検索
news対象検索の「ニュース」タブ
discover対象Discoverの結果
googleNews対象news.google.comとGoogleニュースアプリ

GA4にはDiscover経由のセッションも入るので、webだけを取ったSearch ConsoleのクリックがGA4のオーガニック流入より少なく見えることがあります。原因がDiscoverなら、typeをdiscoverにして別に取ると差が説明できます。

順位を揃えて読む — 「上がったのに伸びない」の4分類

平均掲載順位は、pageごとの表ではそのURLの平均順位です。ただし、クエリが混ざった平均なので、1位のクエリと30位のクエリが混じると実態が見えなくなります。可能ならpageとqueryの2軸で取り、主要クエリを絞って見ます。

順位が上がったページは、CTRの比較を順位帯でそろえて読みます。同じ順位帯でCTRが下がっていれば、タイトルや説明文の問題を疑えます。順位が上がって表示回数も増えたのにクリックが増えないなら、その順位帯の平均CTRに届いていないだけかもしれません。

突き合わせで見つかるページは、次の4型に振り分けます。

型Search Consoleの動きGA4の動きまず疑うこと
ASearch Consoleの動き順位上昇、表示回数増、クリック横ばいGA4の動きセッション横ばいまず疑うことタイトル・説明文が検索意図と合っていない
BSearch Consoleの動き順位上昇、表示回数横ばいGA4の動きセッション横ばいまず疑うこと上がったクエリの検索ボリュームが小さい
CSearch Consoleの動きクリック増GA4の動きセッション横ばいまず疑うこと結合キーのずれ、計測の欠落、期間のずれ
DSearch Consoleの動き順位上昇、クリック増GA4の動きセッション増、ただし到達後の行動が悪化まず疑うこと流入は増えているが、ページ内容と期待がずれている

C型は検索側の問題ではありません。Search Consoleでクリックが増えているのに、GA4のランディングページ側で拾えていないなら、正規化かリダイレクト、計測タグの問題を先に見ます。GA4のData APIには、Search ConsoleとリンクしていればorganicGoogleSearchClicksやorganicGoogleSearchImpressionsといった指標もあります。ただし、これらはSearch Consoleとの有効なリンクを前提にするので、リンクの有無から確認します。

A型とB型は、見分け方が違います。A型は表示回数が増えているので、CTRを順位帯別の基準と比べます。B型は表示回数が増えていないので、順位が上がったクエリを個別に見ます。

ここまでの判定は、あくまで疑う順序を決めるためのものです。原因を断定する材料は、この2つのデータには入っていません。

Claude Codeに検証ループを回させる

集計を書かせたら、出力を読ませて検算させます。次のようなプロンプトが、例として使えます(公式の例ではなく、この記事の手順に合わせた書き方です)。

gsc.csv(page, clicks, impressions, ctr, position)と
ga4.csv(landingPage, sessions)を、CLAUDE.mdの規則で正規化して結合して。
結合できなかった行数と上位10件を表示し、
gsc.csvのclicks合計をSearch ConsoleのUIの数字と照合できる形で出して。
そのうえで、position改善が0.5以上でsessionsの変化率が5%以内のページを一覧にして。

スクリプトが動いたら、次のコマンドで結果を確認します。

python compare.py --gsc gsc.csv --ga4 ga4.csv \
  --out candidates.csv
head -20 candidates.csv

Claude Codeには、出力の先頭行と_mergeの内訳を読ませ、想定と違えば正規化規則から見直させます。「結合できたので次へ」ではなく、落ちた行の中身を見ることが、この作業の検証です。

大きなサイトや定期実行での選び方

APIで取れる行数は、1回に最大25,000行です。それを超えるサイトは、startRowを進めながら繰り返し取ります。

ページ数が数万を超えるサイトでは、Search ConsoleのBigQueryへの一括エクスポートが選択肢になります。searchdata_url_impressionテーブルにURL単位のデータが日次で入り、匿名化されたクエリは含まれません。エクスポートは、プロパティの所有者だけが設定できます。GA4側もBigQueryに出せるので、SQLで結合する構成にもできます。BigQuery側の接続と自然言語での分析は、BigQueryをClaudeで自然言語のまま分析する手順で扱っています。

規模・頻度向く取り方理由
数百〜数千ページ、月1回向く取り方2つのAPIをスクリプトで取る理由構成が単純で、Claude Codeに書かせやすい
数万ページ以上向く取り方BigQueryの一括エクスポート理由1回25,000行の制限を意識しなくてよい
都度の質問向く取り方GA4のMCPサーバー理由再現性は低いが、すぐ聞ける

よくあるつまずき

  • 結合後の行数が急に減る: 末尾スラッシュや大文字小文字で、キーが合っていません。_mergeの内訳を確認します。
  • Search Consoleの合計と、ページ別の合計が合わない: 集計単位が、プロパティ単位とページ単位で違います。同じ条件の値で照合します。
  • 日次で見るとGA4と1日ずれる: Search ConsoleがPTで日付を切っています。週次以上にまとめると、ずれが目立たなくなります。
  • 直近の数字が後から変わる: dataStateが確定値のみか確認し、比較期間の終わりを数日前にします。
  • Search Consoleのクリックが、GA4のGoogleオーガニック流入より少ない: typeが既定のwebのままで、Discoverなどが入っていません。typeを切り替えて取り直し、差を確かめます。
  • 認証情報を誤ってClaudeに読ませた: Readのdenyルールを見直し、鍵は環境変数で渡します。

まとめ

順位が上がったのに流入が伸びないページは、URLを正規化して2つのデータを結合し、順位帯をそろえて見れば絞れます。A〜D型のどれかに振り分けると、タイトルの見直し、キーの修正、到達後の内容確認のどこから手を付けるかが決まります。

最初の1回は、_mergeの内訳とSearch ConsoleのUIの合計との照合まで済ませてください。ここが合えば、以降の月次実行はスクリプトと検証ループに任せられます。

この記事を共有:XはてブLinkedIn