← ブログに戻る

検索ボリューム調べ方: サジェスト787件実測

前回はAmazonの話でした。KDPのキーワード枠をBingの検索ボリュームで選んで、1枠丸ごと死んでいたという記録です。Bingが測っているのはウェブ検索の需要で、Amazonの購買検索とは母集団が違った、という結論でした。

今回はそのウェブ検索のほうです。Bingなら「その語が月に何回検索されたか」を無料で、しかも整数で取れます。ではGoogleはどうやって測るのか。

結論を先に書くと、Googleの検索ボリュームを無料で取る手段はありません。ただし「その語が実際に打たれているか」だけなら、サジェストAPIで判定できます。ボリュームは分かりませんが、ゼロかどうかは分かります。

問題は、その判定がどれくらい当たるかです。自分で運用しているサイト2つのSearch Consoleから実在が確認できたクエリ787件を取り出し、1件ずつサジェストを叩いて判定を当てました。この記事はその実測と、使える範囲の線引きです。

Keyword Plannerは広告費を払っていないと数字を丸める

Googleキーワードプランナーは、広告を出稿していないか出稿額の小さいアカウントだと、検索ボリュームが範囲表示になります。返ってくるのは10、100、1,000〜1万、1万〜10万といった対数スケールの幅です(Google広告ヘルプ)。出稿している広告主を基準にした仕様なので、出稿しない側が精度を求めても仕方がありません。

1,000〜1万の幅に入ってしまうと、1,200と9,000の区別がつきません。記事を1本書くかどうかを決めるのに、7倍の開きがある数字は使えません。

Google Trendsは相対値です。100を最大とした指数なので、2つの語を比べることはできても「月に何回打たれているか」は分かりません。

つまりGoogle公式のルートは、金を払わない限り検索回数そのものには届きません。

調べる先ごとに返ってくるものを並べた表。Keyword Plannerは1,000〜1万の対数レンジ、Google Trendsは相対指数72、Bing GetKeywordStatsは98回/月という整数、Googleサジェストは候補文字列の配列。回数を数えられるのはBingだけ

この記事の残りは、この表の下2行の話です。

Bing Webmaster Toolsは月間の検索回数を無料で返す

意外に知られていませんが、Bing Webmaster ToolsのAPIにはGetKeywordStatsがあります。サイトを登録してAPIキーを取れば、無料で月別の検索回数が整数で返ってきます。

import requests

url = "https://ssl.bing.com/webmaster/api.svc/json/GetKeywordStats"
r = requests.get(url, params={
    "apikey": API_KEY,
    "q": "ルームフレグランス",
    "country": "jp",
    "language": "ja-JP",
})
# [{"Query": "ルームフレグランス", "Impressions": 79, "Date": "/Date(...)/"}, ...]

返ってくるのは月ごとのインプレッションです。日本ではGoogleのシェアが圧倒的なのでBingの絶対値をそのまま使うことはできませんが、語どうしの比較には十分使えますし、24ヶ月分がまとめて返るので季節による上下も同時に見えます。

私はkeyword-volume.pyというラッパーを書いて、観測月数と部分一致の量から3段階で判定させています。

判定       完全一致/月       部分一致/月    月数  キーワード
 ◎           79           98    24  ルームフレグランス

ここまでは順調でした。

2語になった瞬間にゼロが返る

同じツールで、実際に自分のサイトが検索流入を取っている語を測ってみたときに気づきました。

判定       完全一致/月       部分一致/月    月数  キーワード
 ◎           79           98    24  ルームフレグランス
 ×            0            0     0  猫 ルームフレグランス
 ×            0            0     0  ルームフレグランス おすすめ

「猫 ルームフレグランス」がゼロです。この語は私のサイトのSearch Consoleで、111日間に表示1,132回・クリック82回を記録しています。人が確実に打っている語が、Bingでは観測されない。

「猫 ルームフレグランス おすすめ」に至っては表示729回・クリック145回で、サイト全体で最もクリックを取っている語のひとつです。これもBingではゼロでした。

Bing GetKeywordStats の結果と Search Console 実績の対照表。「ルームフレグランス」は部分一致98だが、「猫 ルームフレグランス」は0でありながら実際には表示1,132回・クリック82回を記録している

1語なら返る。2語になると落ちる。日本語の複合語がBingのキーワードデータベースに乗っていないようです。英語だと3語でも返ることがあるので、言語の問題だと考えています。

ゼロが返ったとき、それは「需要がない」ではなく「Bingが知らない」なのかもしれません。 この区別ができないと、実際に人が打っている語を捨てることになります。

Googleサジェストなら「打たれているか」だけは分かる

Googleの検索窓に文字を打つと候補が出ます。あれはAPIとして叩けます。認証もキーも要りません。

import urllib.request, urllib.parse, json

def suggest(q, hl="ja", gl="jp"):
    url = "https://suggestqueries.google.com/complete/search?" + urllib.parse.urlencode(
        {"q": q, "client": "firefox", "hl": hl, "gl": gl})
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
    return json.loads(urllib.request.urlopen(req, timeout=20).read().decode("utf-8"))[1]

返るのは候補文字列の配列だけで、検索回数はどこにも入っていません。月に何回打たれているかは分かりません。

叩くときの注意も書いておきます。このAPIは公式に文書化されたものではないので、レート制限も仕様変更も予告なく起きます。私は1.3秒から2.0秒のあいだでランダムに間隔を空け、失敗したら3秒・7秒と待って3回まで再試行する形にしました。787件を叩いて失敗はゼロでしたが、これは間隔を空けたからであって、詰めて叩けば429が返るはずです。さらに重要なのは、429やHTMLが返ったときに「候補ゼロ」と同じ扱いにしないことです。取得できなかったことと需要がないことは別なので、私は成功・形式異常・通信失敗を別のステータスとして記録し、異常だったものは集計から外しました。ここを一緒くたにすると、Googleに弾かれた瞬間に全部の語が「需要なし」に化けます。

ただしひとつだけ読み取れる情報があります。入力した語そのものが候補配列に含まれるかどうかです。前回の記事でエコーと呼んだものです。

>>> suggest("猫 ルームフレグランス")
['猫 ルームフレグランス', '猫 ルームフレグランス おすすめ', '猫 ルームフレグランス 安全', ...]
#  ^^^^^^^^^^^^^^^^^^^^ 入力した語が返ってきている = エコー

サジェストは実際の検索ログから作られていて打たれていない語はそもそも候補に出てこないので、「エコーが返るならその語は打たれている」という向きの推論はきちんと成り立ちます。

問題は逆です。エコーが返らなかったとき、それは需要がないことの証明になるのか。

正しく測るには正例だけを使う

最初、私は間違った測り方をしました。「記事を書いたのに表示ゼロだった語」を集めて、それがエコーなしになるかを見たのです。

これは循環しています。私が確かめたかったのは「エコーが返らない語には需要がない」という主張です。確かめるには、本当に需要がなかった語のリストが要ります。ところが私はそのリストを、「記事を書いたのに表示ゼロだった語」で作りました。

表示ゼロの原因は需要がないこととは限りません。インデックスされていない、順位が圏外、検索意図が記事とずれている、Search Consoleの集計から落ちている。どれも同じように表示ゼロを作ります。

つまり証明したい結論を、検証データを作る段階で先に仮定していたわけです。これでは何を測っても、最初に置いた仮定が返ってくるだけです。

外部レビューでこれを指摘されて、測る量そのものを変えました。

「エコーなし = 需要なし」は証明できません。「打たれていない語」の正解リストが原理的に手に入らないからです。

代わりに測れるものがあります。実在が確認された語を、この判定が何パーセント落とすか。 却下ゲートで本当に怖いのはこちらです。有望なテーマを永久に捨てるのと、無駄な記事を1本書くのとでは、損失の大きさが違います。

正例だけあれば測れるので、負例の正解リストは要りません。

実在が確認された787クエリ

Search Consoleに出ているクエリは、記事の出来とは無関係に人が実際に打ったことが確定しています。これを正例にします。

自分が作成にかかわっているサイト2つから取りました。

サイト領域蓄積ユニーククエリ英数字のみの語
mypcrig.comPC/GPU選定111日1,19766%
kaoriq.comフレグランス111日31241%

mypcrigは1,197件あるので層に分けて抽出し、kaoriqは312件を全数測りました。合計787件のサジェストを叩いて、エコーが返るかを見ています。取得失敗はゼロでした。

判定ルールは3つ用意しました。

  • R1: 入力語と完全一致する候補があるか
  • R3: 候補のどれかが入力語の全単語を含んでいれば通す(語順違いや語尾の差を許容)
  • R4: エコーがなく、かつ候補が1件も返らなかったときだけ却下

結果

ルールmypcrig(1,197件が母集団)kaoriq(312件・全数)
R1 完全一致7.1%(95%CI 4.2〜10.0)9.3%(6.5〜13.0)
R3 全単語含有5.5%(3.0〜8.0)8.3%(5.8〜11.9)
R4 候補ゼロのみ3.9%(1.7〜6.1)8.0%(5.5〜11.6)

数字は偽陰性率です。実在が確認されている語を、判定が誤って却下した割合を表します。

5パーセントから8パーセント。20語に1語から、12語に1語を落とします。

ひとつだけ、はっきり良い結果もありました。mypcrigでクリックが発生した168件のクエリは、R3で1件も落ちませんでした。落ちるのは「表示はあるがクリックが取れていない語」に構造的に偏っています。

ただしこれは全数調査なので、「この111日間・このサイトでは0件だった」以上のことは言えません。将来や他サイトへの保証ではないので、そこは分けて扱う必要があります。

何が落ちるか

率より、落ちたものの中身のほうが実務的です。

指名検索は必ず落ちます。

表示171 クリック27  kaoriq

自分のサイト名です。無名なのでサジェストに出るわけがありません。ブランド名で検索してくれている人がいるのに、判定は「需要なし」と言います。

質問文は落ちます。

表示586  引っ越し祝いに人気のホームフレグランスは?
表示117  macbookはファンレスですか、それともアクティブ冷却がありますか?

AI検索経由の流入だと思われます。人が検索窓に打ち込む形の文ではないのでサジェストには出てきませんが、表示としては現に何百回も出ており、この形の流入は今後増えるはずなのに判定する側が追いついていません。

表記のゆれで落ちます。

表示258  ルームフレグランス 五千円 シトラス     ← 漢数字
表示 54  ルームフレグランス 5,000円 ブランド    ← カンマ区切り
表示 22  tok s / token sec / token per sec  ← 正しくは tok/s

意味は同じですが文字列が違うので、サジェストは別の語として扱います。正規化してから叩けば救えるものもありますが、正規化器を書いていないなら落ちます。

本物の取りこぼしもあります。

表示135 67日間出現  pcケース ファン 配置 エアフロー
表示 17            pcケース エアフロー 重視 おすすめ
表示 12            pcケース 通気性 評価

3件とも候補ゼロでした。しかも3件は同じテーマです。クエリとしては3件でも、失うのはテーマ1本です。67日間出続けている実在需要を、判定は丸ごと落としました。

Bingが「猫 ルームフレグランス」を知らなかったのと同じ現象が、Googleサジェストでも起きています。日本語の4語以上の自然文は、どちらのAPIでも共通の盲点でした。

言語パラメータを間違えると1.6倍ずれる

kaoriqの8.3%を分解したとき、原因が予想と違いました。

母数偽陰性率
日本語クエリ181件6.6%
英語クエリ131件10.7%

kaoriqは多言語サイトで、英語記事のクエリも入っています。それをhl=ja&gl=jpで叩いていました。日本語だけで測れば6.6%で、mypcrigの5.5%と大きくは違いません。

最大の取りこぼしだったbedroom scents for sleep(表示1,354回・44日間出現)も英語クエリでした。日本語設定で英語の語を判定していたので、出るはずがありません。

多言語サイトでこれを実装するなら、クエリの言語にhlglを合わせる必要があります。合わせないと偽陰性が1.6倍に膨らみます。

測り方で自分が外したところ

参考までに、この検証で私が踏んだ落とし穴を3つ書いておきます。同じことをやる人は同じ順番で踏むと思います。

1件も落ちなかったから安全、とは言えません。

最初は14件で試して、偽陰性ゼロという結果でした。ゼロなら完璧に見えます。ですが統計にはrule of threeという目安があって、n回試して0回失敗したとき、失敗率の95パーセント上限はおよそ3/nです。n=14なら約20パーセント。n=6の層なら50パーセントです。ゼロは、少ない試行では何の証拠にもなりません。

グループごとに測る量を変えたなら、そのまま足してはいけません。

mypcrigのクエリは1,197件あります。全部にサジェストを叩くと時間がかかるので、性質ごとにグループを作って、グループごとに測る割合を変えました。

  • クリックが発生している168件 → 全部測った
  • クリックがない233件 → そのうち100件だけ測った

このあと私は「落ちた件数 ÷ 測った件数」で率を出しました。ここが間違いです。

100件だけ測ったグループの1件と、168件全部を測ったグループの1件を、同じ重さで数えてしまっています。前者は本当は233件あるので、100件中10件が落ちたならグループ全体では23件くらい落ちているはずで、測らなかった133件のぶんがまるごと計算から消えていました。やり直したら4.5パーセントだった数字が5.5パーセントになり、低く出ていたぶんだけ「思ったより安全だ」という逆向きの結論に近づいていたことになります。

3割ちかく違いました。

グループの切り方も間違えていました。

「クリックあり」「10日以上出た」「5日未満」の3つに分けたのですが、この3つを足しても510件にしかなりません。1,197件のうち689件、つまり全体の6割近くがどのグループにも入っていませんでした。「5日から9日のあいだに出た、クリックのない語」が丸ごと抜けていたのです。

グループに分けたら、まず全部の件数を足して元の総数と合うか確かめる。これだけで防げました。

落ちたものを後から分類して「これは実害がない」と言い出すのは危険です。

落ちた語を眺めていると、明らかにノイズなものが混ざっています。検索演算子つきのクエリ、AIへの指示文の断片、意味不明な文字列。これらを「却下して正解」に分類すると、偽陰性率は5.6パーセントから2.4パーセントまで下がります。

ですがこれは事後の主観です。正規化器も分類器も実装していないなら、実際のパイプラインではそれらは区別なく落ちます。主要な数字は、事前に決めた機械的なルールで出したものにするべきです。 分類は付録です。

結論: 却下には使えない、優先度になら使える

787クエリ実測の結論。却下には使えないが優先度になら使える。実在する語を落とす割合は5〜8パーセントで、これを許容して自動却下はできない。サジェストに出ない語は後回しにするが捨てない。判定が外れても変わるのは記事の順番だけ

5〜8パーセントの偽陰性を許容して自動却下をかけるのは、私には割に合いません。テーマ単位で数えればもっと大きくなりますし、指名検索と質問文が構造的に落ちるのは実運用で困ります。

一方で、優先度シグナルとしてなら今日から使えます。

  • サジェストに出ない語は後回しにする。ただし捨てない
  • 却下ではないので、判定が間違っていても記事の順番が変わるだけ
  • 後から「後回しにした語が実際どうだったか」を追える形でログに残せる

ボリュームが取れないことを受け入れるなら、「その語が実際に打たれているか」だけは無料で分かります。それは何も分からないよりはずっと良い。実際、私が今回落とした語の中身を見ると、AI検索経由の質問文や検索演算子つきのクエリが混ざっていて、これらは記事のテーマとしては最初から候補に入れるべきではないものでした。判定の精度が5パーセント足りないことより、そもそも何を候補として拾っているかのほうが効くかもしれません。

測り方の教訓のほうが、実は汎用性があるかもしれません。ゼロ件は証拠ではない。グループごとに測る量を変えたらそのまま足さない。後から分類しない。 この3つは、判定ロジックが何であっても効きます。

AIに選ばれる店をつくる 関連書籍 AIに選ばれる店をつくる MEO代行に月3万円払う前に — Claude/ChatGPTで自分の店をAIに選ばせる実践ガイド 書籍ページを見る →
この記事が答えている課題 課題の地図を見る →