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 / position | GA4(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,000 | GA4(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の動き | まず疑うこと |
|---|---|---|---|
| A | Search Consoleの動き順位上昇、表示回数増、クリック横ばい | GA4の動きセッション横ばい | まず疑うことタイトル・説明文が検索意図と合っていない |
| B | Search Consoleの動き順位上昇、表示回数横ばい | GA4の動きセッション横ばい | まず疑うこと上がったクエリの検索ボリュームが小さい |
| C | Search Consoleの動きクリック増 | GA4の動きセッション横ばい | まず疑うこと結合キーのずれ、計測の欠落、期間のずれ |
| D | Search 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.csvClaude 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の合計との照合まで済ませてください。ここが合えば、以降の月次実行はスクリプトと検証ループに任せられます。