- 2つのキーワードは、上位10件で共通するURLが十分にあれば同じグループになります。最も一般的なしきい値は共通3〜4件です。
- この手法は語句の似かたではなく、Googleが実際に表示するものに基づきます。表現が似た2つのキーワードでも、同じグループになるとは限りません。
- Semscraper APIなら1ページ目の1,000キーワードで€0.30。あとはこの記事のPythonスクリプトが処理します。
考え方
SERPの類似度によるクラスタリングの出発点はシンプルです。Googleが2つのクエリに同じページを表示するなら、Googleはそれらを同じ検索意図と理解している、ということです。その場合、サイトの1つのページで両方のキーワードで上位を狙えます。逆に、見た目は似ていてもSERPに共通点がない2つのキーワードには、別々のページが必要です。
この手法は語句そのものでグループ化するより信頼できます。文字列の類似度ではなく、Googleの判断に基づくからです。「ランニングシューズ レディース」と「女性用 ジョギング スニーカー」は共通する語がほとんどありませんが、SERPは大きく重なることがよくあります。
手順
- 上位10件を収集:各キーワードについて、適切な国、言語、デバイスで取得します。
- 自然検索結果のURLを抽出:広告、動画などのブロックは無視します。
- キーワードを2つずつ比較:共通するURLはいくつあるか。
- グループ化:しきい値を超えるペアをまとめます。
しきい値の選び方
| 上位10件で共通するURL数 | 効果 |
|---|---|
| 2 | グループが大きくなるが、近い別の検索意図が混ざるおそれがある |
| 3または4 | 最も一般的な設定。細かく分かれすぎず、まとまりのあるグループになる |
| 5以上 | 非常に絞り込まれたグループ。孤立するキーワードが多くなる |
まず3から始め、十数グループを目で確認してから調整しましょう。適切なしきい値はテーマによって異なります。少数の大手サイトが占めるECのSERPは、メディア系のSERPより重なりやすい傾向があります。
厳密なグループと緩やかなグループ
アプローチは2つあります。厳密なクラスタリングでは、グループ内のすべてのキーワードが、軸となるキーワード(通常はグループ内で最も検索されるもの)としきい値以上のURLを共有している必要があります。グループはすっきりし、以下のスクリプトもこの方式です。緩やかなクラスタリングでは、キーワードはグループのいずれか1つと結びついていれば十分です。グループは連鎖的に大きくなり、ある検索意図から別の検索意図へずれていくおそれがあります。
作成または統合するページを決めるなら、ほぼ常に厳密なクラスタリングが適しています。
スクリプト全文
このスクリプトはキーワードをAPIに送り、SERPを取得してからグループを作成します。APIキー、キーワードのリスト、必要に応じてしきい値を書き換えてください。キーワードは検索回数の多い順に並べておくと、各グループの軸がメインのクエリになります。
import time
import requests
API_KEY = "API_KEY"
API = "https://api.semscraper.com/v1/serp"
HEADERS = {"Authorization": "Bearer " + API_KEY}
THRESHOLD = 3
# 検索回数の多い順
keywords = ["ランニングシューズ レディース", "女性用 ジョギング スニーカー", "トレイルランニングシューズ レディース"]
# 1. 収集を作成:上位10件、google.co.jp、デスクトップ
items = [{"search_engine": "google_search", "keyword": k, "device": "desktop",
"location": "jp", "language": "ja", "depth": 1} for k in keywords]
created = requests.post(API, json=items, headers=HEADERS).json()
ids = [row["id"] for row in created["data"]]
# 2. 待機してからSERPを取得、最長10分
serps, attempts = {}, 0
while len(serps) != len(ids) and attempts < 60:
attempts += 1
time.sleep(10)
res = requests.get(API, params={"ids": ",".join(ids), "output": "json"}, headers=HEADERS).json()
for row in res["data"]:
if row["status"] == "done":
urls = set()
for block in row["results"]:
if block["type"] == "organic":
urls.update(item["url"].rstrip("/") for item in block["items"])
serps[row["keyword"]] = urls
# 3. 軸キーワードを中心にした厳密なクラスタリング
clusters, seen = [], set()
for pivot in keywords:
if pivot in seen or pivot not in serps:
continue
group = [pivot]
seen.add(pivot)
for other in keywords:
if other not in seen and other in serps:
if len(serps[pivot] & serps[other]) >= THRESHOLD:
group.append(other)
seen.add(other)
clusters.append(group)
for group in clusters:
print(" | ".join(group))作成時に返されたIDは、カンマ区切りで取得用の呼び出しに渡します。大量に処理する場合は、代わりにコールバックURLを使いましょう。結果の準備ができ次第APIから送信されるため、ポーリングのループが不要になります。
避けたい落とし穴
- 地域やデバイスを混ぜる:同じ条件で収集したSERPを比べましょう。そうしないと、差が検索意図ではなく条件の違いから生まれます。ローカルなテーマなら、すべてのクエリを同じ都市に位置指定します。
- 日付の異なるSERPを比べる:一連のキーワードは同じ日にまとめて収集しましょう。
- URLをそのまま使う:少なくとも末尾のスラッシュ、できればパラメータも正規化し、同一のURLを見逃さないようにします。
- 自然検索以外のブロックを数える:動画、ニュース、広告は時間ごとに変わり、比較を歪めます。
費用
上位10件の比較には検索結果1ページで十分です。Semscraperなら、1,000キーワードで€0.30、10,000キーワードで€3です。登録時の無料1,000リクエストで、実際のキーワードを使って手法を試せます。収集パラメータはGoogle検索APIのページで詳しく説明しています。
