【完全版】除外キーワードの抽出方法:Nグラム分析で“ムダ配信”をゼロにする実践ガイド

Google Ads

検索語句レポートを眺めて“無料”、“求人”、“PDF”、“使い方”……こうした意図がズレている検索語句が延々と費用を溶かす——。

その出血を最短で止める方法が「Nグラム分析」による除外キーワード抽出です。本記事は、上位記事の構成を踏まえつつ、運用現場でそのまま使える手順・基準・テンプレ・自動化まで一気通貫でまとめました。Google 広告/Yahoo!検索広告のどちらにも応用可能。週次運用に組み込めば、CPAの安定化・CTR/品質改善・学習クリーンアップが加速します。


1. なぜ除外が効くのか(効果と原理)

  • 無駄クリック削減:コンバージョンにつながらない意図(情報収集/就職/学術/競合比較/地域外など)を先回りで遮断
  • 学習の純度向上:配信アルゴリズムが“良い検索語句”へ学習集中
  • CTR・品質スコア改善:不適合なインプレッションを減らす=見かけのCTRも上がりやすい
  • 配信予算の再配分:限られた費用を“獲れる語”へ移す

2. Nグラム分析とは(1-gram/2-gram/3-gram)

  • N-gram=テキストを連続する N 個の語に分割した集合(例:「歯ブラシ 使い方 比較」→ 1-gram:「歯ブラシ」「使い方」「比較」/2-gram:「歯ブラシ 使い方」「使い方 比較」)
  • 狙い:検索語句を単語・連語にほどいて集計し、意図ズレの“共通因子”(無料・安い・中古・求人・とは・pdf・英語等)を発見
  • 日本語の注意:日本語はスペースが無い検索が多いため、形態素解析で語を切ると精度が上がります(後述のスクリプト例あり)。

3. データの準備(出力項目と範囲)

対象レポート:検索語句(Search terms)
最低限の列

  • 検索語句(クエリ)
  • クリック、表示回数、費用、コンバージョン、コンバージョン値
  • キャンペーン/広告グループ/マッチタイプ(分析の切り口に使う)
    期間:まずは過去30日(ボリューム不足なら60〜90日)。季節性が強い商材は直近×前年同期間も比較。

4. スプレッドシートでのNグラム生成(ノーコード手順)

4.1 正規化(表記ゆれ吸収)

1列追加して、以下のような置換を施し小文字化・全/半角統一・記号除去を実施。
例:

=LOWER(
  TRIM(
    SUBSTITUTE(
      SUBSTITUTE(
        SUBSTITUTE(A2," "," "),
      "+","+"),
    "‐","-")
  )
)

4.2 1-gram(単語)を一気に“縦持ち”へ

Googleスプレッドシートで全行を一列にフラット化

=TOCOL(SPLIT(TEXTJOIN(" ",TRUE, B2:B), " "), 1)
  • B2:B は正規化後の列。
  • 空白を区切りに全クエリを結合→分割→縦ベクトル化

頻出語トップを一瞬で出す:

=QUERY(TOCOL(SPLIT(TEXTJOIN(" ",TRUE, B2:B), " "),1),
 "select Col1, count(Col1)
  where Col1 is not null
  group by Col1
  order by count(Col1) desc
  label count(Col1) 'freq'")

4.3 2-gram/3-gram(連語)

スプレッド関数のみで“スライド窓”を表現するのは煩雑なので、Apps Scriptや軽いPythonを併用するのが実務的(後述サンプル)。
※どうしても関数でやるなら、SPLITTAKE/DROP/WRAPROWS を組み合わせて連結。


5. しきい値設計:どれを“除外候補”とみなすか

おすすめの判定指標(AND/ORで組合せ)

  • 費用×0CV費用 >= Y かつ CV数 = 0(例:Y=¥1,000〜¥3,000)
  • クリック×0CVクリック >= X かつ CV数 = 0(例:X=10〜20)
  • 出現回数N-gram出現回数 >= 3〜5(アカウント横断なら 5〜10)
  • 悪性CTR:CTRだけ高いのに0CV/逆に低すぎる(広告/LP意図ズレ疑い)
  • CVRの極端な低さCVR <= ターゲットの1/3
  • カテゴリ判定:下記“典型パターン”に該当

典型の意図ズレカテゴリ

  • 情報探索:「とは」「意味」「使い方」「やり方」「比較」「評判」「口コミ」「ランキング」「おすすめ」「メリット」「デメリット」「安全性」「危険」
  • 無料/非商用:「無料」「フリー」「サンプル」「テンプレ」「オープンソース」「ダウンロード」「pdf」「画像」
  • 求人/就職:「求人」「募集」「年収」「給料」「社員」「アルバイト」
  • 学術/学生:「論文」「研究」「レポート」「卒論」「課題」
  • 競合名、他社ブランド(要ポリシー判断)
  • 地域外/対象外:「大阪」「海外」「通販不可エリア」「中古」「修理」「返品」
  • ターゲット違い:「子供」「赤ちゃん」「介護」「業務用」「法人」「B2B/B2C逆」
  • 言語違い:「english」「中文」など(商材次第)

6. 除外の配置とマッチタイプ設計

  • 共有の除外リスト(アカウント横断の恒常的ワード):例)「無料」「求人」「pdf」「画像」「とは」など
  • キャンペーン固有(商材・地域・訴求に依存する語)
  • マッチタイプ
    • 完全一致:ピンポイントで強く止める
    • フレーズ一致:連語や語尾揺れを広くブロック
    • (媒体仕様に依存)部分一致の除外相当の挙動は「広すぎブロック」になりやすいので慎重に
  • ブランド/指名語の保護:ブランド名+“トンマナ語”を誤って除外しない(例:「ブランド名 口コミ」が実は高CVR等)

7. 週次オペレーションに落とす(運用フロー)

  1. データ取得(前週 or 直近30日)
  2. 正規化→Nグラム集計(1-gram→2-gram→3-gram)
  3. しきい値フィルタ(費用X・クリックY・出現回数Zなど)
  4. 人工チェック(ブランド保護/ポリシー/ビジネス的妥当性)
  5. 除外反映(共有リスト/キャンペーン固有)
  6. 変更履歴に記録(日時・語・配置・理由・担当)
  7. 影響確認(翌週のCPA/CTR/検索語句の質)

8. 半自動〜全自動化レシピ(そのまま使える雛形)

8.1 Google スプレッドシート+Apps Script(Nグラム生成)

  • 行ごとの検索語句を形態素解析して 1-gram/2-gram/3-gram を発行し、別シートに集計。
/** シートのA列: 検索語句, B〜: クリック/費用/CV 等
 *  出力先: シート "ngrams" に [ngram, n, 出現回数] などを書き出し
 *  形態素解析は簡易的に「スペース分割→fallback」で実装(日本語は後述Python推奨)
 */
function buildNgrams() {
  const ss = SpreadsheetApp.getActive();
  const src = ss.getSheetByName('raw');
  const dst = ss.getSheetByName('ngrams') || ss.insertSheet('ngrams');
  dst.clear();
  const rows = src.getRange(2,1,src.getLastRow()-1,1).getValues().map(r => (r[0]||'').toString().trim().toLowerCase());
  const map = new Map(); // key: ngram, value: {n:1|2|3, cnt}
  const N = [1,2,3];
  rows.forEach(q => {
    if(!q) return;
    // 簡易:スペース分割(未分割の日本語は後述Pythonで)
    const toks = q.split(/\s+/).filter(Boolean);
    N.forEach(n=>{
      for(let i=0; i<=toks.length-n; i++){
        const ng = toks.slice(i,i+n).join(' ');
        const key = `${n}|${ng}`;
        const v = map.get(key) || {n, ng, cnt:0};
        v.cnt++;
        map.set(key, v);
      }
    });
  });
  const out = [['ngram','n','count']];
  [...map.values()].sort((a,b)=>b.cnt-a.cnt).forEach(v=> out.push([v.ng, v.n, v.cnt]));
  dst.getRange(1,1,out.length,out[0].length).setValues(out);
}

8.2 Google Ads Scripts(検索語句の取得→候補抽出→除外反映)

/** 直近30日の検索語句をGAQLで取得 → シートへ → 別関数でNグラム集計 → しきい値で除外候補を生成 → 共有リスト/キャンペーンへ反映 */
function exportSearchTerms() {
  const query = `
    SELECT
      search_term_view.search_term,
      metrics.clicks,
      metrics.cost_micros,
      metrics.conversions,
      campaign.id,
      ad_group.id
    FROM search_term_view
    WHERE segments.date DURING LAST_30_DAYS
  `;
  const rows = AdsApp.search(query);
  const ss = SpreadsheetApp.getActive();
  const sh = ss.getSheetByName('raw') || ss.insertSheet('raw'); sh.clear();
  sh.appendRow(['search_term','clicks','cost','conv','campaign_id','ad_group_id']);
  while (rows.hasNext()) {
    const r = rows.next();
    const cost = r.metrics.costMicros / 1e6;
    sh.appendRow([
      r.searchTermView.searchTerm,
      r.metrics.clicks,
      cost,
      r.metrics.conversions,
      r.campaign.id,
      r.adGroup.id
    ]);
  }
}

/** しきい値で“危険Nグラム”を抽出して除外(要:ngramsシートに ngram,count,clicks,cost,conversions 等がある前提) */
function pushNegatives() {
  const ss = SpreadsheetApp.getActive();
  const sh = ss.getSheetByName('candidates'); // ここにフィルタ済み候補行を用意
  const values = sh.getRange(2,1,sh.getLastRow()-1,4).getValues(); // [ngram, scope, matchtype, reason]
  const account = AdsApp.currentAccount();
  values.forEach(([term, scope, mtype, reason])=>{
    if(!term) return;
    if(scope === 'shared') {
      // 共有除外リストに追加(事前に"NEG_SHARED"というリストを作成しておく)
      const lists = AdsApp.negativeKeywordLists().withCondition('Name = "NEG_SHARED"').get();
      if(lists.hasNext()) lists.next().addNegativeKeyword(term); 
    } else {
      // キャンペーンID指定スコープ
      const campId = scope;
      const it = AdsApp.campaigns().withIds([campId]).get();
      if(it.hasNext()) {
        const c = it.next();
        c.createNegativeKeyword(term); // マッチタイプは媒体仕様に依存(必要なら語を"[]"や""で包む等の運用ルールを)
      }
    }
  });
}

※ 実環境に合わせて:アカウントの命名規約共有除外リスト許可単語ホワイトリストを先に整備すると安全です。

8.3 Python(日本語の形態素解析で精度UP)

# pip install fugashi
from fugashi import Tagger
tagger = Tagger()

def tokenize(text: str):
    return [w.surface for w in tagger(text or '') if w.surface.strip()]

def ngrams(tokens, n=2):
    return [' '.join(tokens[i:i+n]) for i in range(len(tokens)-n+1)]

# 例:
q = "電動歯ブラシ 使い方 比較"
toks = tokenize(q)                 # -> ['電動', '歯', 'ブラシ', '使い方', '比較'] など辞書に依存
bigrams = ngrams(toks, 2)
trigrams = ngrams(toks, 3)
  • 形態素で切ってから集計すると、2-gram/3-gramの“意図”がよりはっきり出ます。

9. まずはコレ:除外“候補”語リスト(出発点)

最終判断はビジネス文脈で人工レビューしてください。

  • 無料/割引系:無料/フリー/割引/クーポン/安い/格安/最安/中古
  • 情報探索:とは/意味/やり方/使い方/仕組み/比較/レビュー/評判/口コミ/おすすめ/ランキング/ブログ
  • ドキュメント:pdf/画像/テンプレ/サンプル/例文/マニュアル
  • 採用:求人/募集/アルバイト/パート/年収/給料
  • アフター/周辺:修理/返品/キャンセル/問い合わせ/電話番号/住所
  • 地域外:海外/県名(対象外エリア)
  • ターゲット外:子供/赤ちゃん/学生/研究/学術
  • 言語:english/中文/韓国語(商圏により)

10. よくある失敗と回避策

  • 強すぎる除外で“獲れる尾”を切る完全一致除外を濫用しない。フレーズ除外ホワイトリストで安全運転
  • ブランド語の誤除外:ブランド+ネガ語が実は高CVRのケース(例:「ブランド名 口コミ」)→事前にブランド保護表
  • PMaxへの影響を軽視:除外制約が強い面・弱い面がある。検索語句の質ブランド保護は別途対処(ブランドセーフ衣装)
  • 学習リセット:大規模除外は段階投入し、配信の再学習を観察
  • 英数記号・全半角揺れ:正規化を習慣化(小文字化/記号統一/かなカナ統一)

11. KPIの見方(短期と中期)

  • 短期(1〜2週):無駄費用・0CVクリックの削減、検索語句の“質”改善
  • 中期(3〜6週):CPA安定、CVR上昇、学習の純度向上、有効CPC低下
  • レポートの推奨切り口:語種(1/2/3-gram)× デバイス × 時間帯 × 地域 × キャンペーンタイプ

12. “そのまま使える”チェックリスト

導入時

  • 共有除外リストを新設(NEG_SHARED など)
  • ブランド保護ホワイトリスト定義
  • 正規化ルールの合意(小文字化・記号・全半角)
  • しきい値(費用・クリック・出現回数)を数値で決める

週次運用

  • 直近30日の検索語句を抽出
  • Nグラム(1/2/3)で集計
  • しきい値で候補抽出→人工レビュー
  • 除外反映(共有/キャンペーン)
  • 変更履歴と影響(CPA/CTR/CVR)を記録

13. 付録:BigQueryでの簡易Nグラム集計(スペース区切り想定)

-- `project.dataset.search_terms` : columns (query STRING, clicks INT64, cost FLOAT64, conv FLOAT64)
WITH base AS (
  SELECT
    LOWER(REGEXP_REPLACE(query, r'\s+', ' ')) AS q,
    clicks, cost, conv
  FROM `project.dataset.search_terms`
),
toks AS (
  SELECT
    q,
    SPLIT(q, ' ') AS arr,
    clicks, cost, conv
  FROM base
),
unigram AS (
  SELECT t, 
         SUM(clicks) AS clicks, SUM(cost) AS cost, SUM(conv) AS conv, COUNT(*) AS freq
  FROM toks, UNNEST(arr) AS t
  GROUP BY t
),
bigram AS (
  SELECT
    ARRAY_TO_STRING( [arr[OFFSET(i)], arr[OFFSET(i+1)]], ' ' ) AS t2,
    SUM(clicks) AS clicks, SUM(cost) AS cost, SUM(conv) AS conv, COUNT(*) AS freq
  FROM toks, UNNEST(GENERATE_ARRAY(0, ARRAY_LENGTH(arr)-2)) AS i
  GROUP BY t2
),
trigram AS (
  SELECT
    ARRAY_TO_STRING( [arr[OFFSET(i)], arr[OFFSET(i+1)], arr[OFFSET(i+2)]], ' ' ) AS t3,
    SUM(clicks) AS clicks, SUM(cost) AS cost, SUM(conv) AS conv, COUNT(*) AS freq
  FROM toks, UNNEST(GENERATE_ARRAY(0, ARRAY_LENGTH(arr)-3)) AS i
  GROUP BY t3
)
SELECT '1-gram' AS n, t AS ngram, clicks, cost, conv, freq FROM unigram
UNION ALL
SELECT '2-gram', t2, clicks, cost, conv, freq FROM bigram
UNION ALL
SELECT '3-gram', t3, clicks, cost, conv, freq FROM trigram
ORDER BY n, freq DESC;

日本語のスペース無し検索は上記だけでは不十分。MeCab などで事前にトークン配列にして格納すると精度は一気に上がります。


14. まとめ(勝てる運用のコア)

  • Nグラム分析=“意図ズレの共通因子”を見つける顕微鏡
  • 明確なしきい値人間の最終判断をセットに。
  • 共有除外×キャンペーン固有の二段構えで安全かつ強力に。
  • スクリプト化で毎週の作業を10分以内へ。
    この流れを習慣化すれば、検索語句の質がみるみる改善し、CPAの“底”が固まります。今日から運用に組み込んでいきましょう。