Article

検索意図を捉えるコンテンツ作成術:ユーザーの疑問に答える方法

高田晃太郎
検索意図を捉えるコンテンツ作成術:ユーザーの疑問に答える方法

業界の公開データでは、Google検索のクエリ(検索キーワード)のうち毎日およそ15%が過去に観測されていない新規クエリとされ[1]、検索結果1位の平均クリック率(CTR)は調査により差があるもののおよそ28〜40%のレンジで報告されています[2,3]。さらに、複数の調査でゼロクリック(検索結果をクリックせずに離脱)の割合が半数前後〜6割超に到達するケースが示され[4,5,6]、単に上位表示を目指すだけでは流入も成果(CVR: コンバージョン率)も安定しない現実が見えてきます。編集やマーケの勘所だけに頼ると、ユーザーの疑問に答えきれないままコンテンツが増殖し、技術組織の観点では保守負債だけが膨らみます。逆に、検索意図(ユーザーの目的)を計測可能な単位に分解し、実装と運用に落とし込むと、限られた投資でも確率よく勝てるようになります。この記事では、SEOとコンテンツ作成の要である「意図」を軸に、意図の診断、情報設計、実装、計測・改善までをデータドリブンに接続する具体策を、コードとともに提示します。専門用語には簡潔な説明を添え、一般的な読者にも価値が伝わるよう配慮します。

なぜ「意図」が成果を左右するのか:ビジネスとシステムの接点

検索意図は、ユーザーが入力した文字列の背後にある目的の表現です。一般的には(Broderの分類を基に)情報収集・取引・ナビゲーションに大別され、実務では比較検討(商用調査)を独立させる拡張も広く用いられます[7]。現場ではこれをさらにドメイン固有の粒度に再分解する必要があります。例えばSaaSの導入文脈では、概念理解、アーキテクチャ比較、要件適合性の検証、価格とセキュリティの確認、導入手順の可視化といった段階に分かれます。重要なのは、こうした段階が検索クエリの表面だけでは判別しにくい点で、同じ単語列でも残留意図が異なれば必要なUIや回答形式が変わります。説明記事が長文で良くても、ユーザーが手順を探している状況ではスニペットの方が勝つという現象はその典型です。技術組織にとっての要点は、意図を仮説ではなく計測と反証が可能なデータモデルとして扱うことにあります。

意図はKPIツリーの上位概念として設計する

検索意図をKPI(重要業績評価指標)に結び付けると、単なるPV最適化からの卒業が容易になります。例えば「比較検討」意図のページでは、滞在時間やスクロール深度だけではなく、比較表のインタラクション、仕様シートのダウンロード、価格ページへの遷移率などを一次指標とし、リードの適合率や商談化率を二次指標に置きます。これにより、検索順位が動かなくても、意図適合度の向上によってCVRが伸びるという状況を定量で説明できます。実装面では、イベントスキーマに意図カテゴリを含め、計測時点でタグ付けする設計(例:GA4のイベントパラメータにintentを付与)が有効です。

編集判断を再現可能にするためのデータ基盤

編集の経験則を捨てる必要はありませんが、再現可能性を担保するためにログ、SERP(検索結果ページ)、顧客インタビュー、サポートチケットなどのシグナルを同一の意図スキーマに統合します。日々変化するSERPレイアウトやナレッジパネル、PAA(People Also Ask: 質問ボックス)の出現頻度は、意図の強度や期待される回答形式を示唆します。変化が早い領域ほど、観測から意思決定までのレイテンシ(遅延)を短縮する必要があり、そのために自動収集とクラスタリングの仕組み化が不可欠になります。

意図の診断:データから仮説を立てる

ここからは実装です。まずはSearch Console(GSC)のクエリとランディングページ、ポジション、クリック率を取り出し、クエリ群を意味空間でクラスタリングします。次にSERPのリッチ要素を観測し、同じクラスタ内で求められる回答形式を推定します。最後に、社内の行動データやサポートログと突き合わせて信頼度を上げます。これにより、SEOのキーワード調査だけでは見落としがちな「解き方」まで含めた意図理解が進みます。

Google Custom Search APIでSERPの骨格を観測する

スクレイピングは安定性や規約の観点で推奨できないため、API(Custom Search JSON API)を用います。以下は上位結果のタイトルとスニペットを取得し、単純なヒューリスティクスで意図を仮分類する例です。APIキーと検索エンジンID(CX)は環境変数で与え、利用時はクォータと利用規約を確認してください。

import os
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

API_KEY = os.getenv("GCSE_API_KEY")
CX = os.getenv("GCSE_CX")

INTENT_KEYWORDS = {
    "informational": ["とは", "使い方", "仕組み", "意味", "概要"],
    "commercial": ["比較", "おすすめ", "評判", "代替", "レビュー"],
    "transactional": ["価格", "購入", "導入", "無料トライアル", "見積"],
    "navigational": []
}

def guess_intent(query: str, title: str, snippet: str) -> str:
    text = f"{query} {title} {snippet}"
    for intent, cues in INTENT_KEYWORDS.items():
        if any(cue in text for cue in cues):
            return intent
    return "unknown"


def search_serp(query: str, num: int = 10):
    try:
        service = build("customsearch", "v1", developerKey=API_KEY)
        res = service.cse().list(q=query, cx=CX, num=min(num, 10)).execute()
        items = res.get("items", [])
        results = []
        for it in items:
            title = it.get("title", "")
            snippet = it.get("snippet", "")
            link = it.get("link", "")
            intent = guess_intent(query, title, snippet)
            results.append({"title": title, "snippet": snippet, "link": link, "intent": intent})
        return results
    except HttpError as e:
        print(f"HTTP error: {e}")
        return []
    except Exception as e:
        print(f"Unexpected error: {e}")
        return []

if __name__ == "__main__":
    for r in search_serp("SaaS セキュリティ 比較", 5):
        print(r)

この段階では精度よりも、クラスタごとの代表的な表示形式の把握が目的です。PAAや価格パネルの有無は、どの要素をページ上で前面に出すべきかのヒントになります。

Search ConsoleのBigQueryエクスポートからクエリを抽出する

Search ConsoleのBigQueryエクスポートを利用して、日次でクエリを取り込みます[8]。以下はサイト全体のクエリ、ページ、クリック、表示回数を取得するSQLの例です。テーブルスキーマやパーティション設定は公式ドキュメントに従ってください。

-- <YOUR_PROJECT>.<YOUR_DATASET>.searchdata_site_impression というエクスポートテーブルを想定
SELECT
  date,
  query,
  page,
  SUM(clicks) AS clicks,
  SUM(impressions) AS impressions,
  SAFE_DIVIDE(SUM(clicks), SUM(impressions)) AS ctr,
  AVG(position) AS avg_position
FROM `<YOUR_PROJECT>.<YOUR_DATASET>.searchdata_site_impression`
WHERE _TABLE_SUFFIX BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY))
                         AND FORMAT_DATE('%Y%m%d', CURRENT_DATE())
GROUP BY date, query, page;

意図の次元を後段で付与するため、クエリはなるべく生の形で保持しておき、最終的なレポートビューでラベル付けします。エクスポートの粒度やレイテンシに合わせて、運用のバッチサイクルを決めます。

Embeddingでクエリをクラスタリングする

Sentence-Transformersを用いると、類似クエリのクラスタリングが手早く実装できます[9]。Embedding(文章のベクトル化)だけに頼らず、辞書的な規則とベクトルの両方を使い、最後に人手で代表クエリに名前を付けるフローにします。

from typing import List, Dict, Any
import numpy as np
from sklearn.cluster import KMeans
from sentence_transformers import SentenceTransformer

class QueryClusterer:
    def __init__(self, model_name: str = "sentence-transformers/all-MiniLM-L6-v2", n_clusters: int = 20):
        self.model = SentenceTransformer(model_name)
        self.n_clusters = n_clusters
        self.kmeans = KMeans(n_clusters=self.n_clusters, n_init=10, random_state=42)

    def fit_predict(self, queries: List[str]) -> List[int]:
        embeddings = self.model.encode(queries, normalize_embeddings=True, show_progress_bar=False)
        labels = self.kmeans.fit_predict(embeddings)
        return labels

    def centroids(self) -> np.ndarray:
        return self.kmeans.cluster_centers_


def cluster_queries(queries: List[str], k: int = 20) -> Dict[int, Any]:
    qc = QueryClusterer(n_clusters=k)
    labels = qc.fit_predict(queries)
    result: Dict[int, Any] = {}
    for q, l in zip(queries, labels):
        result.setdefault(l, {"queries": []})["queries"].append(q)
    return result

if __name__ == "__main__":
    sample = ["SaaS セキュリティ 比較", "SaaS SSO とは", "SaaS 価格", "ゼロトラスト 仕組み", "IdP 比較"]
    clusters = cluster_queries(sample, k=3)
    for cid, payload in clusters.items():
        print(cid, payload["queries"])    

処理性能は環境に依存します。バッチ実行では、対象クエリ数、モデルのサイズ、CPU/GPUの有無で所要時間が変わるため、実行時に処理件数とレイテンシを記録し、P50/P95を監視するのが現実的です。短時間バッチを繰り返す方が、巨大な一括処理よりも運用は安定します。

Elasticsearchでサイト内検索とログを横断する

外部検索(Google等)と内部検索(サイト内検索)を突き合わせると、意図のずれを早期に検知できます。以下はElasticsearchで直近の内部検索クエリを集計し、顕著に増えたトピックを見つけるクエリ例です。

POST /internal-search-logs/_search
{
  "size": 0,
  "query": {
    "range": {"@timestamp": {"gte": "now-7d"}}
  },
  "aggs": {
    "hot_queries": {
      "significant_terms": {
        "field": "query.keyword",
        "background_filter": {"range": {"@timestamp": {"gte": "now-30d"}}}
      }
    }
  }
}

この結果に外部検索のクラスタを重ねると、どのトピックで外部流入はあるが内部で解決できていないか、またはその逆かが見えてきます。コンテンツ計画はこのギャップの解消順に並べるのが合理的です。

意図に応える情報設計:ページの形を決める

意図の種類によって、ページの形と主要コンポーネントを変えます。概念理解が目的のクエリには、定義、図解、最小限のコード断片やアーキテクチャの全体像を先頭に置きます。比較検討段階では、要件表と判断基準、ベンダーの違いが一目でわかる構成を優先し、本文でその根拠を補強します。取引意図が強いクエリには、価格や導入ハードル、セキュリティとコンプライアンスの枠組みをファーストビュー(ページ冒頭)に配置し、見積やトライアルの導線を遅らせないことが効果的です。ナビゲーション意図には、該当製品やドキュメントへの最短導線を設計します。いずれの場合も、SERPの期待とページのファーストビューを一致させることが、直帰率やスクロール深度に直結します。

FAQスキーマでPAAに答え切る

PAAが頻出するクラスタでは、質問と回答を構造化して提供します。以下はQ&Aの配列からFAQPageのJSON-LD(構造化データ)を生成するPythonの例です。

import json
from typing import List, Dict

def build_faq_jsonld(qa: List[Dict[str, str]]) -> str:
    data = {
        "@context": "https://schema.org",
        "@type": "FAQPage",
        "mainEntity": []
    }
    for item in qa:
        data["mainEntity"].append({
            "@type": "Question",
            "name": item["question"],
            "acceptedAnswer": {
                "@type": "Answer",
                "text": item["answer"]
            }
        })
    return json.dumps(data, ensure_ascii=False)

if __name__ == "__main__":
    qa_pairs = [
        {"question": "SaaSのSSOとは?", "answer": "IdPを用いて単一の認証で複数サービスにアクセスする方式です。"},
        {"question": "ゼロトラストの要点は?", "answer": "境界防御に依存せず、継続的検証を行う設計思想です。"}
    ]
    print(build_faq_jsonld(qa_pairs))

構造化データは万能ではありませんが、検索結果の占有面積とクリック率の改善に寄与しやすい領域です。ページの目的と矛盾しない範囲で適用します。

テンプレートとデザインシステムに意図を埋め込む

最も効果が高いのは、コンポーネントレベルで意図を前提にしたデザインシステムを整えることです。比較クラスタ向けには仕様表、コスト計算ウィジェット、導入ハードル一覧といったモジュールを用意し、概念理解クラスタには定義カード、要点のサマリー、図解ブロックを標準化します。エディタが目的に応じたテンプレートを選ぶだけで、答えるべき情報が自然に揃う状態にすると、運用でのばらつきが減り、リリースサイクルも短縮します。

運用:学習する編集システムに育てる

一度の最適化で終わらせず、学習するループを組み込みます。週次のデータ取り込み、クラスタの更新、ギャップスコアに基づく企画の優先度付け、公開後の計測と反証までをジョブとして繋ぎます。Airflow(ワークフロー/バッチオーケストレーター)を用いた最小構成のDAG(タスクの依存関係グラフ)例を示します。

Airflowで週次パイプラインを構築する

from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.python import PythonOperator


def fetch_gsc(**context):
    # BigQueryから直近データを取り込み、Cloud Storageに保存する処理を想定
    # 例外はAirflowに伝搬させ、再試行ポリシーに委ねる
    pass


def cluster_job(**context):
    # Embeddingとクラスタリングを実行し、代表クエリ名を更新
    pass


def publish_briefs(**context):
    # ギャップスコア順に編集ブリーフをCMSに登録
    pass


default_args = {
    "owner": "content-ops",
    "retries": 1,
    "retry_delay": timedelta(minutes=10),
}

dag = DAG(
    dag_id="intent_pipeline_weekly",
    default_args=default_args,
    schedule_interval="0 3 * * 1",  # 毎週月曜3時
    start_date=datetime(2023, 1, 1),
    catchup=False,
    max_active_runs=1,
)

with dag:
    t1 = PythonOperator(task_id="fetch_gsc", python_callable=fetch_gsc)
    t2 = PythonOperator(task_id="cluster", python_callable=cluster_job)
    t3 = PythonOperator(task_id="publish_briefs", python_callable=publish_briefs)

    t1 >> t2 >> t3

DAGでは、各タスクが処理件数、処理時間、エラー種別をメトリクスとして出力することを推奨します。意図の変化は外生要因に引きずられるため、週次サイクルが実務上の落としどころです。競合性の高いテーマだけ日次に切り出す設計も有効です。

計測と意思決定:順位より意図適合度を追う

モニタリングは単に順位を見るのではなく、意図適合度を示す複合指標を追います。ランディングページのファーストビューとSERPの期待が一致している場合、直帰率の低下、スクロールの深まり、コンテンツ固有イベントの増加が同時に起きます。逆に、順位は良いのにコンバージョンに繋がらない場合、意図ミスマッチの可能性が高く、回答形式の変更、構造化データの再設計、導線の前倒しといった介入が効果を発揮します。技術組織では、ダッシュボードに意図ラベルを一次軸として配置し、ドリルダウンで記事、コンポーネント、段落単位のイベントへ到達できるように設計します。

実装上の落とし穴と回避策

Embeddingのクラスタは便利ですが、モデルのバージョン変更で境界がずれることがあります。安定性を重視する場合、代表クエリと要件の文章をクラスタのアンカーとして保存し、再学習時に距離の変動が閾値を超えたときだけ人手確認を挟むのが安全です。SERP観測は過度に取りすぎると維持が難しくなるため、ビジネスインパクトの大きいクラスタとロングテールの二層で頻度を変え、費用対効果を確保します。KPI設計では、短期のクリック率改善が長期のリード品質を損なう危険があり、特にB2Bではカジュアルな導線を増やしすぎると商談化率が下がることがあるため、ファネル全体で最適化します。必要に応じてA/Bテストや多変量テストで変更の因果を検証します。

ROIと導入効果を見積もる

ROIは、意図適合によるCVR改善と制作コスト削減の両輪で捉えます。例えば比較クラスタでファーストビューを再設計し、クリック率が一定でもCVRが2.0%から2.8%に改善したと仮定すると、同じ流入でも約40%の成約増を説明できます。さらにテンプレート化で制作時間が三割短縮できるとすると、年間のコンテンツ制作人件費の圧縮効果も相まって投資回収は早まります。導入期間は、既存のデータ基盤がある場合で四〜八週間、ゼロからの構築では概ね三ヶ月を想定すると現実的です(状況により変動)。

ベンチマーク設計:数字で運用を守る

最後に、性能と運用安定性の観点から、ベンチマークの作り方を示します。重要なのはモデルやAPIの性能そのものより、編集の意思決定に必要な鮮度を保てるかです。週次バッチで十万クエリ程度を扱う想定なら、エンコードとクラスタリングの処理時間、失敗率、再試行回数を継続的に可視化します。下記は簡易のベンチマーク関数です。

import time
from statistics import median
from sentence_transformers import SentenceTransformer


def benchmark_encode(n: int = 10000):
    model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
    samples = [f"query {i}" for i in range(n)]
    t0 = time.time()
    _ = model.encode(samples, normalize_embeddings=True, show_progress_bar=False)
    elapsed = time.time() - t0
    print({"count": n, "sec": round(elapsed, 2), "qps": round(n / elapsed, 1)})

if __name__ == "__main__":
    benchmark_encode(5000)

得られた処理性能をAirflowのスケジュールと照らし合わせ、P95の負荷時でもSLO(サービスレベル目標)内に収まるかを確認します。データ容量の成長やモデル変更を考慮し、二割程度の余裕を持たせておくと、長期運用が安定します。

開発と編集が共通言語で話すために

検索意図という概念を、抽象論ではなく実装可能なデータモデルに落とすと、開発と編集が同じダッシュボードを見ながら議論できるようになります。クラスタ、意図ラベル、回答形式、KPIの紐付けが揃えば、記事単位の最適化から、サイトやプロダクト全体の体験設計へと視野が広がります。重要なのは、意図はユーザーの生活や業務の変化に追従して常に揺らぐという事実で、その揺らぎを取り込むための仕組みを持つこと自体が、競争優位になります。

まとめ:意図を起点に、答え切る文化をつくる

検索意図を捉える取り組みは、ツールの導入やテクニックの羅列では完結しません。データで意図を診断し、ページの形に翻訳し、測定して反証するという当たり前のサイクルを、編集と開発が共に回せるかが勝負です。今日からできる一歩として、直近四週間のクエリを取り出し、小さなクラスタを一つ選んで、ファーストビューを意図に合わせて作り直してみてください。変化は往々にして静かに始まります。あなたの組織は、どの疑問から先に答えますか。

参考文献

  1. Google Search Blog. Our latest quality improvements to Search. https://blog.google/products/search/our-latest-quality-improvements-search/
  2. SISTRIX. Why almost everything you knew about Google CTR is no longer valid. https://www.sistrix.com/blog/why-almost-everything-you-knew-about-google-ctr-is-no-longer-valid/
  3. First Page Sage. Google Click-Through Rates (CTRs) by Ranking Position. https://firstpagesage.com/reports/google-click-through-rates-ctrs-by-ranking-position/
  4. Search Engine Journal. Zero-Click Searches: How to Get Back Your Lost Traffic. https://www.searchenginejournal.com/zero-click-searches-how-to-get-back-your-lost-traffic/352634/
  5. SparkToro. 2024 Zero-Click Search Study: For every 1,000 US Google searches, only 374 clicks go to the open web. https://sparktoro.com/blog/2024-zero-click-search-study-for-every-1000-us-google-searches-only-374-clicks-go-to-the-open-web-in-the-eu-its-360/
  6. Similarweb Blog. Zero-Click Searches. https://www.similarweb.com/blog/marketing/seo/zero-click-searches/
  7. The Economic Times. Web searches classified into three categories. https://economictimes.indiatimes.com/tech/internet/web-searches-classified-into-three-categories/articleshow/2944573.cms
  8. Google Developers Search Central Blog. BigQuery efficiency tips (Bulk export). https://developers.google.com/search/blog/2023/06/bigquery-efficiency-tips
  9. Sentence-Transformers Documentation. Clustering Examples. https://www.sbert.net/examples/sentence_transformer/applications/clustering/README.html