Wagtail CMSのElasticsearchバックエンドで日本語の全文検索

Wagtail CMSで日本語の全文検索に対応する方法をまとめます。

実行環境

Elasticsearchのインストール

www.digitalocean.com

$ curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
$ echo "deb https://artifacts.elastic.co/packages/7.x/apt stable main" | sudo tee -a /etc/apt/sources.list.d/elastic-7.x.list
$ sudo apt update
$ sudo apt install -y elasticsearch
$ echo "network.host: localhost" | sudo tee -a /etc/elasticsearch/elasticsearch.yml
$ sudo systemctl start elasticsearch
$ sudo systemctl enable elasticsearch

Elasticsearchがポート9200で動作していることを確認します。
バージョンは7.17.1でした。

$ curl -X GET 'http://localhost:9200'
{
  "name" : "...",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "...",
  "version" : {
    "number" : "7.17.1",
    "build_flavor" : "default",
    "build_type" : "deb",
    "build_hash" : "...",
    "build_date" : "...",
    "build_snapshot" : false,
    "lucene_version" : "8.11.1",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

ICUとkuromojiのインストール

日本語文字列を正規化するICUと日本語の形態素解析器であるkuromojiを追加します。

$ sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-icu
$ sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji
$ sudo systemctl restart elasticsearch

Wagtailのバックエンド設定

Backends — Wagtail Documentation 2.16.1 documentation

Elasticsearch7用のPythonクライアントをインストールします。

$ pip install "elasticsearch>=7.0.0,<8.0.0" 

settings.WAGTAILSEARCH_BACKENDSwagtail.search.backends.elasticsearch7 を使用し、 INDEX_SETTINGS で日本語を扱うAnalyzerを構成します。
char_filter にはICU Normalization Character Filterを、 filter には必要なkuromojiのToken filterを指定します。

WAGTAILSEARCH_BACKENDS = {
    'default': {
        'BACKEND': 'wagtail.search.backends.elasticsearch7',
        'URLS': ['http://localhost:9200'],
        'INDEX': 'wagtail',
        'TIMEOUT': 5,
        'OPTIONS': {},
        'INDEX_SETTINGS': {
            'settings': {
                'analysis': {
                    'analyzer': {
                        'ja_analyzer': {
                            'type': 'custom',
                            'char_filter': ['icu_normalizer'],
                            'tokenizer': 'kuromoji_tokenizer',
                            'filter': [
                                'kuromoji_baseform',
                                'kuromoji_part_of_speech',
                                'ja_stop',
                                'kuromoji_number',
                                'kuromoji_stemmer'
                            ]
                        }
                    }
                }
            }
        }
    }
}

最後にupdate-indexコマンドでインデクスをリビルドします。

$ ./manage.py update_index

動作確認

部分一致検索が有効なPage.titleで日本語文字列の検索ができることを確認します。

>>> from my_app.models import MyPage
>>> from wagtail.search.backends import get_search_backend
>>> s = get_search_backend()
>>> MyPage.objects.get(title='関西国際空港')
<MyPage: 関西国際空港>

>>> s.search('関西', MyPage)
<SearchResults [<MyPage: 関西国際空港>]>
>>> s.search('国際', MyPage)
<SearchResults [<MyPage: 関西国際空港>]>
>>> s.search('空', MyPage)
<SearchResults [<MyPage: 関西国際空港>]>
>>> s.search('ほげ', MyPage)
<SearchResults []>

参考