Google広告で実は止まっているKWを見抜く4手順

キーワードの停止や除外を提案したあとで「それ、とっくに止まっています」と返された経験はないだろうか。広告運用の自動化を進めるほど、この誤判定は増える。原因ははっきりしている。Google広告APIでstatus を1階層しか見ていないからだ。キャンペーンが有効でも、広告グループが停止していれば配信はゼロ。ここでは実運用で3度同じミスを踏んだうえで固めた、実効的な配信状態を4階層で判定する手順を示す。GAQLのクエリと、そのままコピペできるチェックリストまで持ち帰れる構成にした。

1階層のstatusだけ見ると「止まっているKW」を提案してしまう

Google広告の管理画面は、親が止まっていても子の行を「有効」と表示する。APIも同じだ。ad_group_criterion.status が ENABLED でも、それはそのキーワード自身の設定値にすぎない。上位のキャンペーンや広告グループが PAUSED なら、実際のインプレッションは発生しない。

実際に踏んだ失敗はこうだ。90日間の消化額が大きいキーワードを抽出し、無駄打ちとして停止提案を出した。だがそのキーワードは、提案の3週間前にすでに停止されていた。90日という窓が、停止前の消化を丸ごと含んでいたわけだ。指摘を受けるまで、レポート上は完全に正しく見えていた。同じ種類のミスを3度繰り返して、ようやく判定ロジックそのものが誤っていると認めた。

誤判定の怖さは、間違った数字が出ることではない。「もう対処済みの問題」を提案として出し続けることで、自動化の出力そのものが信用されなくなる点にある。

実効配信の判定は「4階層すべてENABLED」+「有効な広告が1本以上」

判定条件を言葉にすると単純だ。次の5つを同時に満たしたときだけ、そのキーワードは配信されている。

  • campaign.status が ENABLED
  • ad_group.status が ENABLED
  • ad_group_criterion.status が ENABLED
  • キャンペーンの期間・予算が有効(campaign.primary_status で確認)
  • その広告グループに有効な広告が1本以上ある

最後の条件が抜けやすい。キーワードが有効でも、広告グループ内の広告が全て停止・不承認なら配信は起きない。広告本数は ad_group_ad リソースを別クエリで引き、広告グループIDごとに件数を数えて突き合わせる。

キーワード側のGAQLはこう書ける。ブックマークして、自分のアカウント構成に合わせて書き換えて使ってほしい。

SELECT campaign.id, campaign.status, campaign.primary_status, ad_group.id, ad_group.status, ad_group_criterion.criterion_id, ad_group_criterion.keyword.text, ad_group_criterion.keyword.match_type, ad_group_criterion.status FROM ad_group_criterion WHERE ad_group_criterion.type = 'KEYWORD' AND ad_group_criterion.negative = false

ここで WHERE 句に status の条件を書かないのが要点だ。ENABLED だけ取得すると、「なぜ止まっているか」を後から説明できなくなる。全件取得してPython側で判定し、除外理由をログに残す設計にしておくと、提案を出さなかった根拠も追える。

campaign.primary_status は ELIGIBLE / PAUSED / LIMITED / MISCONFIGURED / NOT_ELIGIBLE などの値を返し、配信できていない理由の手がかりになる(Google Ads Query Language 公式ドキュメント)。予算上限による LIMITED は「配信はしているが抑制されている」状態であり、停止とは区別して扱う。

APIの初期設定でつまずいている段階なら、先にGoogle広告APIをPythonで動かす手順と初回の壁を読んでおくと早い。クエリの書き方そのものはGAQLの書き方を実際に使ってわかった5つのコツにまとめてある。この判定ロジックを組み込んだ運用スクリプトの完全版はnoteの実践ガイドで公開している。

一致タイプを1語に丸めない:集計キーは(KW, match_type)

もう一つの落とし穴が一致タイプだ。実際にあったのは、同じキーワード文字列でフレーズ一致は停止、完全一致は稼働中という状態だった。キーワード文字列だけで集計すると、この2行が1行にまとまる。結果、稼働中の完全一致まで「停止済み」と判定されてしまう。

対策は単純で、集計キーを必ずタプル (keyword_text, match_type) にすることだ。文字列だけをキーにした辞書を作った時点で、この事故は避けられない。ad_group_criterion.keyword.match_type は EXACT / PHRASE / BROAD を返すので、正規化せずそのまま保持する。

さらに厳密にやるなら、広告グループIDまで含めた3つ組をキーにする。同じキーワードが複数の広告グループに存在するアカウントでは、広告グループ単位で状態が分かれるためだ。

長い集計窓の消化額を「今の無駄」と読み替えない

90日や180日の実績で無駄打ちを探すのは定石だが、その窓には停止前の消化が含まれる。「消化が大きい」ことと「いま無駄が出ている」ことは別の事実だ。

実装での解決は、長期窓の集計に必ず直近窓の数値を併記すること。具体的には、90日の消化額と並べて直近7日のインプレッションと消化額を出す。直近7日がゼロなら、それはすでに止まっている証拠であり、提案対象から外す。

この2列を並べるだけで、提案リストの精度は目に見えて変わった。導入前は「停止済みの項目が提案に混ざる」ことが常態だったが、導入後は判定段階で機械的に落ちるようになった。人が目視でダブルチェックする工程を丸ごと削れたのが実利だ。

コピペで使える停止提案チェックリスト

提案を出力する前に通す関門を、コードとは別に文章のチェックリストとして持っておくと再現しやすい。保存して、自分の運用ルールに合わせて書き換えて使ってほしい。

  • (1)campaign / ad_group / ad_group_criterion の3階層すべてが ENABLED か
  • (2)その広告グループに有効な広告が1本以上あるか
  • (3)campaign.primary_status が ELIGIBLE または LIMITED か
  • (4)集計キーに match_type を含めているか
  • (5)長期窓の消化額に直近7日の実績を併記しているか
  • (6)除外した候補について、除外理由をログに残しているか

この6項目を通過しなかった候補は、提案リストに載せない。判定を関数として切り出し、提案を生成するスクリプトは必ずその関数を経由させる。関数を通さずに出した提案は無効、というルールをコード側で強制するのが確実だ。

広告が配信されない原因は設定以外にも審査や支払いなどがある。原因の全体像はGoogle広告ヘルプの「広告が表示されない原因」が網羅している。

[PR] 判定結果をクライアント共有用の資料に落とし込む工程まで含めて短縮したいなら、AIスライド作成ツール「イルシル」を試してみるのも手だ。

まとめ

停止提案の誤検知は、判定ロジックの精度ではなく見ている階層の数で決まる。4階層のstatusと広告本数、そして (キーワード, 一致タイプ) の集計キー。この3点を関数として固定してしまえば、同じミスは構造的に起きなくなる。まずは手元のアカウントで、上のGAQLを全件取得モードで一度回してみてほしい。「有効なのに配信されていないキーワード」が何件あるかを数えるだけで、自動化の前提が変わる。

実務でそのまま使いたい人へ

本記事の手法の完全版(実際のコード・テンプレート・つまずき対処つき)は、運営者のnote(note.com/ryo_ai_hack)で公開している。実データに基づく実践ガイドをまとめて読める。

DMM 生成AI CAMP

コメント

このブログの人気の投稿

Claude Skills販売で月5万稼ぐ3ステップ実践

Claude CodeでExcel自動化副業を月5万にする手順

Yahoo広告APIをPythonで自動化して詰まった5つの罠