2026年8月18日
2026年8月18日
WordPressにElasticsearch全文検索を実装してUX・SEOを改善する方法
はじめに
WordPressのデフォルト検索はMySQLのLIKE検索に依存しており、大規模サイトではパフォーマンスの低下や検索精度の問題が顕著になります。Elasticsearchを導入することで日本語形態素解析・ファセット検索・検索サジェストなどの高度な機能を実現し、UXとSEO(直帰率低下)の両面で改善が期待できます。
症状・原因
- 投稿数が増えるにつれてサイト内検索の速度が著しく低下している
- 検索結果の精度が低く、ユーザーが目的のコンテンツに辿り着けない
- ファセット(絞り込み)検索を実装したいが、WordPressの標準機能では困難
解決手順
ステップ1:ElasticsearchとElasticPressプラグインをインストールする
Dockerを使ったElasticsearch環境とElasticPressプラグインの設定です。
# Docker ComposeでElasticsearchを起動する
cat > docker-compose.yml << 'EOF'
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false # 開発環境のみ(本番はSSL必須)
- ES_JAVA_OPTS=-Xms512m -Xmx512m
ports:
- "9200:9200"
volumes:
- es-data:/usr/share/elasticsearch/data
volumes:
es-data:
EOF
docker-compose up -d
# Elasticsearchの動作確認
curl -s http://localhost:9200/_cluster/health?pretty | grep '"status"'
# 日本語形態素解析プラグイン(analysis-kuromoji)をインストールする
docker exec -it <container_id> \
bin/elasticsearch-plugin install analysis-kuromoji
ステップ2:ElasticPressのインデックス設定とカスタムマッピングを構成する
ep_index_post_kill フィルターで除外投稿を制御し、カスタムマッピングを適用します。
<?php
// functions.php に追加 ─ ElasticPress インデックス制御
// 特定条件の投稿をインデックスから除外する
add_filter( 'ep_index_post_kill', function( $kill, $post ) {
// 下書き・プライベート投稿はインデックスしない
if ( in_array( $post->post_status, [ 'draft', 'private' ], true ) ) {
return true; // trueを返すとインデックスをスキップする
}
// noindex メタがある投稿もスキップする
if ( get_post_meta( $post->ID, '_noindex', true ) ) {
return true;
}
return $kill;
}, 10, 2 );
// カスタムフィールドをインデックス対象に追加する
add_filter( 'ep_prepare_meta_allowed_keys', function( $keys ) {
// 商品価格・著者名・カスタムカテゴリをElasticsearchに格納する
$custom_keys = [ 'price', 'author_name', 'custom_category', 'product_sku' ];
return array_merge( $keys, $custom_keys );
} );
// 日本語向けにkuromojiアナライザーを使ったカスタムマッピング
add_filter( 'ep_config_mapping', function( $mapping ) {
// title フィールドに日本語アナライザーを適用する
$mapping['mappings']['properties']['post_title'] = [
'type' => 'text',
'analyzer' => 'kuromoji', // 日本語形態素解析
'fields' => [
'raw' => [ 'type' => 'keyword' ], // ソート・集計用の未解析フィールド
],
];
return $mapping;
} );
ステップ3:ep_formatted_argsフィルターで検索クエリをカスタマイズする
Elasticsearchへ送信するクエリをフィルターでチューニングします。
<?php
// functions.php に追加 ─ 検索クエリのカスタマイズ
add_filter( 'ep_formatted_args', function( $formatted_args, $args ) {
if ( ! isset( $args['s'] ) || empty( $args['s'] ) ) {
return $formatted_args;
}
// multi_matchクエリでtitleにより高いboostを付与する
$formatted_args['query'] = [
'bool' => [
'should' => [
[
'multi_match' => [
'query' => $args['s'],
'fields' => [
'post_title^3', // タイトルは3倍のスコアブースト
'post_content^1',
'post_excerpt^2', // 抜粋は2倍のスコアブースト
'terms.name^1.5', // タクソノミー名も検索対象に含める
],
'type' => 'best_fields',
'analyzer' => 'kuromoji', // 日本語形態素解析を使用する
],
],
],
'filter' => [
[ 'term' => [ 'post_status' => 'publish' ] ], // 公開済みのみ
],
],
];
return $formatted_args;
}, 10, 2 );
ステップ4:カテゴリ・タグ・価格のファセット検索を実装する
Elasticsearchのアグリゲーション機能でファセット(絞り込み)検索を実装します。
<?php
// functions.php に追加 ─ ファセット検索のアグリゲーション設定
add_filter( 'ep_formatted_args', function( $formatted_args, $args ) {
// アグリゲーション(ファセット集計)を追加する
$formatted_args['aggs'] = [
// カテゴリ別の投稿数を集計する
'categories' => [
'terms' => [
'field' => 'terms.category.slug.raw',
'size' => 20, // 上位20カテゴリを取得する
],
],
// タグ別の投稿数を集計する
'tags' => [
'terms' => [
'field' => 'terms.post_tag.slug.raw',
'size' => 30,
],
],
// 価格帯ごとのレンジ集計(ECサイト向け)
'price_ranges' => [
'range' => [
'field' => 'meta.price.double',
'ranges' => [
[ 'to' => 1000 ],
[ 'from' => 1000, 'to' => 5000 ],
[ 'from' => 5000, 'to' => 10000 ],
[ 'from' => 10000 ], // 1万円以上
],
],
],
];
return $formatted_args;
}, 10, 2 );
// URLパラメーターからファセット条件を取得してクエリに反映する
add_filter( 'ep_formatted_args', function( $formatted_args, $args ) {
$selected_category = sanitize_text_field( $_GET['fcat'] ?? '' );
if ( ! empty( $selected_category ) ) {
// 選択されたカテゴリでフィルタリングする(post_filterを使うとfacet数を維持できる)
$formatted_args['post_filter'] = [
'term' => [ 'terms.category.slug.raw' => $selected_category ],
];
}
return $formatted_args;
}, 20, 2 );
ステップ5:検索クエリとクリックをログに記録して検索品質を改善する
検索クエリとクリックデータを収集し、検索アナリティクスとして活用します。
<?php
// functions.php に追加 ─ 検索クエリのロギング
// カスタムテーブルを作成する(プラグイン有効化時に一度だけ実行)
function create_search_log_table() {
global $wpdb;
$table = $wpdb->prefix . 'search_logs';
$charset = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS {$table} (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
query VARCHAR(255) NOT NULL,
result_count INT DEFAULT 0,
clicked_post_id BIGINT UNSIGNED DEFAULT NULL,
searched_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_query (query),
INDEX idx_searched_at (searched_at)
) {$charset};";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
}
// 検索実行時にクエリをログに記録する
add_action( 'pre_get_posts', function( $query ) {
if ( ! $query->is_main_query() || ! $query->is_search() ) {
return;
}
$search_query = sanitize_text_field( $query->get( 's' ) );
if ( empty( $search_query ) ) {
return;
}
global $wpdb;
// 検索クエリをDBに非同期で記録する(パフォーマンスへの影響を最小化)
$wpdb->insert( $wpdb->prefix . 'search_logs', [
'query' => $search_query,
'searched_at' => current_time( 'mysql' ),
] );
} );
// 検索クリックをAJAXで記録するエンドポイント
add_action( 'wp_ajax_nopriv_log_search_click', function() {
$post_id = (int) ( $_POST['post_id'] ?? 0 );
$log_id = (int) ( $_POST['log_id'] ?? 0 );
if ( $post_id && $log_id ) {
global $wpdb;
// クリックされた投稿IDを検索ログに記録する
$wpdb->update(
$wpdb->prefix . 'search_logs',
[ 'clicked_post_id' => $post_id ],
[ 'id' => $log_id ],
[ '%d' ], [ '%d' ]
);
}
wp_send_json_success();
} );
注意事項
- インデックス再構築: 大量投稿の初回インデックス時はWP-CLIコマンド
wp elasticpress index --setupをバックグラウンドで実行すること - 日本語解析:
analysis-kuromojiプラグインを使用しない場合、日本語の形態素解析が行われず検索精度が大幅に低下する - Elasticsearchのメモリ: 最低2GBのJVMヒープを割り当てること。小メモリ環境ではOpenSearch Serverlessを検討する
まとめ
ElasticPressを使ったElasticsearch連携により、WordPressの検索精度とパフォーマンスを大幅に向上できます。検索アナリティクスを活用して継続的に検索品質を改善することが長期的なSEO・UX向上につながります。関連記事:WordPressのパフォーマンスをGrafana・Prometheusで監視する方法