はじめに

「うちのSEO、競合と比べて何が弱いんだろう」という相談はよく受けます。中小ECや個人事業のサイトでは、SEOツールに月額を払い続ける余裕がなく、感覚で記事を足しているケースが多いです。

ただ、ここで最初にはっきりさせておきたいことがあります。競合他社のSearch ConsoleやGA4などの非公開データは、こちらからは取得できません。 これは権限の問題で、いくらツールやAIを使っても変わりません。「競合のキーワード流入が丸見えになる」といった話は、自社データには当てはまらない別物だと考えてください。

ですので、この記事のスタンスはシンプルです。

  1. 自社のSearch ConsoleとGA4のデータをBigQuery(以下 BQ)で結合し、自分の手札を正確に把握する
  2. その上でClaude Codeに弱点と伸びしろの仮説を出させる
  3. 競合については検索結果や上位ページといった公開情報からあくまで「推定」する

自社データは事実、競合データは推定。この線引きを守るだけで、分析の精度と納得感はかなり変わります。


Search ConsoleデータのBigQueryエクスポートを活用する

自社の検索流入を正確に見るには、Search Console(以下 GSC)の「一括データエクスポート」をBQに溜めるのが出発点です。GA4のレポートではオーガニックの検索クエリの大半が (not provided) になってしまい、どのキーワードで来たかがほぼ見えないためです。

GSCのエクスポートを有効にすると、BQ側に次のようなテーブルが日次で蓄積されていきます。

  • searchdata_site_impression … サイト単位のクエリ別パフォーマンス
  • searchdata_url_impression … URL単位のクエリ別パフォーマンス

このうちURL単位のテーブルには、クエリ・ページ・クリック数・インプレッション数・平均掲載順位(に相当する sum_position)が入っています。これだけで「どのページが、どのクエリで、どれくらい表示されてどれくらいクリックされたか」が手元のSQLで自由に集計できるようになります。

エクスポートの設定手順や、GA4とのエクスポート差分の基本は、別記事のGA4×BigQueryでSearch Consoleデータを結合してオーガニック分析するで詳しく扱っています。ここでは「もう溜まっている」前提で先に進みます。

まず見るべきは平均掲載順位とCTR

競合と差をつける前に、自社の「取りこぼし」を探します。具体的には、次の2パターンが伸びしろの宝庫です。

  • 掲載順位は高い(1〜2ページ目)のにクリック率(CTR)が低いクエリ … タイトルや説明文の改善余地
  • **インプレッションは多いのに掲載順位が中位(11〜20位あたり)**のクエリ … あと一押しで1ページ目に届く候補

次のSQLは、URL単位テーブルから直近28日のクエリ別パフォーマンスを集計し、掲載順位とCTRを出すものです。

-- 直近28日:クエリ別の表示・クリック・平均掲載順位・CTR
SELECT
  query,
  SUM(impressions)                              AS impressions,
  SUM(clicks)                                   AS clicks,
  SAFE_DIVIDE(SUM(clicks), SUM(impressions))    AS ctr,
  -- sum_position はインプレッション加重の合計。0始まりなので +1 して順位化
  SAFE_DIVIDE(SUM(sum_position), SUM(impressions)) + 1 AS avg_position
FROM `your_project.searchconsole.searchdata_url_impression`
WHERE data_date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 28 DAY)
                    AND DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)
  AND query IS NOT NULL
GROUP BY query
HAVING impressions >= 50
ORDER BY impressions DESC

HAVING impressions >= 50 は、表示回数が極端に少ないノイズのようなクエリを除くためのものです。数値はサイトの規模に合わせて調整してください。


流入クエリ×GA4行動・CVの分析

GSC単体では「クリックされた」までしか分かりません。クリックした人がサイト内でどう動き、購入や問い合わせ(CV)に至ったかはGA4データ側にあります。ここを結合すると、「集客はできているのに売上につながっていないクエリ」が見えてきます。

GSCはクエリ×ページ、GA4はページ×行動という単位なので、ランディングページ(URL)をキーにして突き合わせます。注意点として、GSCのクリックとGA4のセッションは計測の仕組みが違うため、件数は完全には一致しません。あくまで傾向をつかむための結合だと理解しておいてください。

結合をずらさないために、最初に集計の単位をはっきり決めておきます。

  • GA4側の「ランディングページ」= セッションの入口URLとします。具体的には session_start イベントが発火した瞬間の page_location を入口URLとして採用します。GA4の page_location(event_params)はプロトコル+ホストを含む完全URL(例 https://example.com/foo)なので、CONCAT('https://example.com', ...) のように接頭辞を足してはいけません。足すとURLが二重になり、GSC側のURLと一切マッチしなくなります。
  • もし page_view イベント全部の page_location を拾ってしまうと、入口ではない回遊先のURLまで混ざり、GSCの「検索で着地したページ」という単位とずれます。入口に揃えるため session_start の行に限定します。
  • GSCの url とGA4の page_location は、表記ゆれ(末尾スラッシュ、クエリパラメータ、www の有無)を両者そろえて正規化してから結合します。下のSQLでは normalize_url() 相当の処理として、クエリ除去と末尾スラッシュ除去を両側に同じようにかけています。
-- ランディングページ(=セッション入口URL)をキーに、GSCのクエリ流入とGA4のCVを結合
-- 両側で同じ正規化(クエリ除去・末尾スラッシュ除去)をかけて突き合わせる
WITH gsc AS (
  SELECT
    -- GSCのURLを正規化:?以降を落とし、末尾スラッシュを除く
    RTRIM(REGEXP_REPLACE(url, r'\?.*$', ''), '/') AS landing_url,
    SUM(clicks)      AS gsc_clicks,
    SUM(impressions) AS gsc_impressions
  FROM `your_project.searchconsole.searchdata_url_impression`
  WHERE data_date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 28 DAY)
                      AND DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY)
  GROUP BY landing_url
),
ga4 AS (
  SELECT
    -- page_location は完全URL。接頭辞を足さず、そのまま同じ正規化をかける
    RTRIM(
      REGEXP_REPLACE(
        (SELECT value.string_value FROM UNNEST(event_params)
         WHERE key = 'page_location'),
        r'\?.*$', ''),
      '/') AS landing_url,
    COUNT(*)                              AS sessions,  -- session_start に限定済み
    COUNTIF(event_name = 'purchase')      AS purchases
  FROM `your_project.analytics_000000.events_*`
  WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 28 DAY))
                          AND FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY))
    -- 入口URLに揃えるため session_start の行だけを使う(page_view 全URLは混ぜない)
    AND event_name = 'session_start'
  GROUP BY landing_url
)
SELECT
  g.landing_url,
  g.gsc_clicks,
  g.gsc_impressions,
  a.sessions,
  a.purchases,
  SAFE_DIVIDE(a.purchases, g.gsc_clicks) AS cv_per_click
FROM gsc AS g
LEFT JOIN ga4 AS a USING (landing_url)
WHERE g.gsc_clicks >= 20
ORDER BY g.gsc_clicks DESC

なお、purchase のように session_start とは別の行で発火するイベントをセッション入口URLに正確に紐づけたい場合は、本来 ga_session_id(と user_pseudo_id)でセッションを組み立て、そのセッションの入口URLにCVを割り当てるのが厳密です。上のSQLは「入口URLに来た数」と「期間内のCV総数」をURL単位で並べる簡易版で、傾向把握には十分ですが、CVの帰属を厳密にしたいときはセッション単位で組み直してください。

cv_per_click が極端に低いページは、「検索からは来てもらえているのに、ページの中身や導線でCVを取りこぼしている」候補です。逆に cv_per_click が高いのにクリック数が少ないページは、順位を上げる価値が高いページだと判断できます。

URLの正規化(末尾スラッシュ、クエリパラメータ、wwwの有無など)が甘いと結合がずれます。上のSQLでは両側に同じ正規化をかけていますが、www の有無やホスト名の違いがある場合は、そこも合わせて整形するステップを足してください。GA4データをBQで扱う基本構成はClaude Code × MCPで社内のBigQuery分析を安全に回すでも触れています。


Claude Codeで弱点・伸びしろの仮説を出す

ここからがClaude Codeの出番です。SQLの結果(CSVや集計済みテーブル)を渡し、「数字を読んで仮説を出す」役割を任せます。人間が全クエリを眺めるのは現実的ではないので、当たりをつける作業を肩代わりしてもらう、という使い方です。

ポイントは、前提と制約をプロンプトに明記することです。特に「競合データは持っていない」「これは自社データである」と最初に伝えないと、AIが勝手に競合の内部数値を語り出し、もっともらしい嘘になりかねません。

プロンプトの骨子はこんな形です。

あなたはSEOアナリストです。以下は当社サイト自身のデータです。
- gsc_query_perf.csv … 自社のクエリ別 表示/クリック/平均掲載順位/CTR
- lp_cv.csv … 自社のランディングページ別 クリック/セッション/購入

制約:
- 競合他社の非公開データ(GSC・GA4)は一切持っていません。推測で競合の数値を作らないでください。
- 各指摘には、根拠となった数値(クエリ名・順位・CTRなど)を必ず添えてください。

依頼:
1. 「順位は高いがCTRが低い」クエリ上位10件と、改善方針(タイトル/説明文の観点)
2. 「11〜20位でインプレッションが多い」あと一歩のクエリ上位10件
3. 「クリックは多いがCVが弱い」ページ上位5件と、考えられる原因の仮説
4. それぞれ、すぐ着手できる施策と、効果を確かめる指標をセットで

このように「数値の根拠を必ず添えて」と縛ると、検証可能な指摘だけが返ってきやすくなります。返ってきた仮説はそのまま信じず、必ず元のSQL結果で裏取りする前提で扱ってください。AIの役割は「結論を出すこと」ではなく「人間が確認すべき場所を絞り込むこと」です。


競合は公開情報から「推定」する。その限界も知っておく

⚠️ 繰り返しになりますが、競合のSearch ConsoleやGA4の中身は見られません。 こちらが競合について知れるのは、誰でもアクセスできる公開情報だけです。

現実的に集められるのは、次のようなものです。

  • 狙っているクエリで実際に検索して、上位に出てくるページを確認する
  • 上位ページの見出し構成・文字量・扱っているトピックの幅を読む
  • そのページがどんな検索意図に答えているか(比較・手順・事例など)を分類する
  • 自社の同テーマページと並べて、抜けている観点を洗い出す

これらをClaude Codeに渡して、「自社ページと上位ページの構成の差分」「自社に足りていない見出し候補」を出させると、リライトの方針づくりがはかどります。ここでも入力はあくまで公開情報であり、AIに渡すのも「自分で見て集めた事実」だけにします。

その上で、この推定には次のような限界があることを正直に押さえておきます。

  • 検索結果は地域・端末・パーソナライズで変わるため、自分に見える順位が全員に共通とは限らない
  • 上位ページの構成は分かっても、なぜ上位なのか(被リンク・ドメインの実績・更新頻度など)は外からは確定できない
  • 競合の流入数・CV数といった成果の数字は推測の域を出ない

つまり競合分析は「ヒントを得る」までが射程で、自社データのような確かな根拠にはなりません。意思決定の軸はあくまで自社データに置き、競合は補助線として使うのが安全です。


まとめ

この記事のスタンスを最後に整理します。

  • 競合他社の非公開データ(GSC・GA4)は取得できません。これは前提として変わりません。
  • 確かな根拠になるのは自社のSearch ConsoleとGA4のデータです。BQで結合し、まず自分の手札を正確に把握します。
  • 「順位は高いがCTRが低い」「あと一歩で1ページ目」「クリックは多いがCVが弱い」の3観点が、伸びしろを探す入口になります。
  • Claude Codeには「これは自社データ」「競合は推測しない」「根拠の数値を添える」と縛った上で仮説出しを任せ、結果は必ず元データで裏取りします。
  • 競合は検索結果や上位ページなどの公開情報から「推定」するにとどめ、その限界を理解した上で補助的に使います。

派手な「競合丸見えツール」よりも、自社データを正確に読み切るほうが、結局のところ着実に差がつきます。まずは溜まっているGSCとGA4のデータを、一度きちんと突き合わせてみるところから始めてみてください。