こんにちは。スタジオ・ウミの寺尾です。
1月に入社したばかりの新人です。
弊社CTOの大野から、モジュール開発を依頼されました。今回は開発したモジュールの紹介をします。
概要
皆さんはSearch APIというモジュールをご存じでしょうか
Drupalで高度な検索機能を実装する際に欠かせないモジュールです。
大野から、このSearch APIを拡張する機能の開発を依頼されたとき、正直なところ内容が非常にマニアックだと感じました。
日本語の検索は本当に厄介で、英語と違って表記の揺れが発生しやすく、ちょっとした違いで目的の記事にたどり着けないことがよくあります。
そんな中、大野から「バックエンド側で入力文字列の表記を統一して、検索精度を上げるモジュールを作ってほしい」と依頼されました。
日本語検索が厄介な理由として、日本語特有の文字体系の多様性にあります。例えば、以下のような点が検索精度に影響を与えます。
本モジュールで対応できる表記揺れ
- 全角・半角の違い → 例えば、「ドルーパル」と「ドルーパル」は表記の仕方があって、検索結果が異なります。
- 類似記号の存在 → 例えば、長音記号には「ー」、「➖️」、「―」があります。ユーザーがどの記号を使うかによって検索結果が異なります。
- スペースの有無 → 例えば、「DrupalはCMSです」と「Drupal は CMS です」のように、スペースの有無によって検索結果が異なります。
本モジュールで対応できない表記揺れ
- 異なる表記体系 → 例えば、「煙草」「タバコ」「たばこ」はすべて同じ意味を持つ単語ですが、検索結果が異なります。
- カタカナ表記の揺れ → 例えば、「アクセサリー」と「アクセサリ」は、どちらも一般的に使われますが、検索結果が異なります。
これをどう実装しようと考えたことが、このモジュール開発の始まりでした。
Search API のみでは実現できない課題
Search APIのみでは、日本語特有の課題を解決できません。
これらの課題を解決するためには、追加の正規化の導入が必要です。
検索結果のイメージとしては、以下です。
アルゴリズムとCの間に全角スペースが空いているのに、半角スペースが空いている記事にヒットしているのが分かります。
モジュールの内部的仕様の概要
このモジュールは、Search APIのプロセッサーとして動作します。
プロセッサーとは、インデックス作成や検索クエリ実行の際にデータを加工する仕組みです。
このプロセッサーに日本語を正規化する機能を追加したのが、今回、開発したモジュールになります。
実際に開発してみての振り返り
面白かった点
正規化実装時に知った関数について
「preg_replace」は、正規表現を使う上で基本の関数ですが、「preg_replace_callback」関数は使ったことがありませんでした。
違いは置換時に、別の処理を適用できる点で差別化できます。
今回は、使う機会があまり無かったですが記憶に留めておいて利用シーンが出てきたら、スマートに使いたいです。
また、ループ回数を指定できるという新しい発見もあり面白かったです。
$result = preg_replace_callback('/\d+/', function ($matches) {
return $matches[0] * 2;
}, "価格は1000円です。価格は5000円です。", 1);
echo $result;
// 出力結果「価格は2000円です。価格は5000円です。」
Drupalの柔軟な拡張性
Search APIのプラグインシステムを利用して、簡単にこの機能を組み込めたのはDrupalの魅力を再確認しました。
難しかった点
正規表現の調整
日本語の正規化におけるパターンマッチングは複雑で、適切な正規表現を設計するのに苦労しました。
特に漢字のマッチは対応したことが無く勉強になりました。
また、平仮名やカタカナの正規表現は以下の書き方でも対応できますが、漢字はそのような書き方は用意されていません。
※ 漢字での正規表現はできますが、よく知られている方法だと不完全で一部の漢字で抜けが発生します。
ひらがなの正規表現
[ぁ-ん]
カタカナの正規表現
[ァ-ヴ]
漢字の正規表現(一部の漢字で抜けが発生します。)
[一-龠]
処理時間の最適化
どんなに正確な検索結果を提供できたとしても、提供するまでに時間がかかるなら、ユーザーは離脱してしまいます。
そのため、処理時間を最適化することを意識しました。
具体的に下記です。
改善前
preg_replace(['/~/','/∼/','/∾/','/〜/','/〰/','/~/'], '', '入力文字');
改善後
preg_replace('/[~∼∾〜〰~]/u', '', '入力文字');
配列で渡す改善前の方がコードとしては、分かりやすく感じられます。
ただ、preg_replace関数というのは配列で渡すより文字列で渡す書き方だと処理スピードが1.25倍くらい速いそうです。
まとめ
開発中は、大野からメソッド名の命名から処理スピードなど、様々な点のアドバイスをいただき自分の成長の機会を得ることができました。
また、早い段階で大まかな機能はできていたのですが、アドバイスによってさらに良くなりました。
今後はコードの細部にこだわりながら開発に取り組みます。
ぜひ、このモジュールを試してみてください。
https://www.drupal.org/project/search_api_japanese_normalizer
募集しています
スタジオ・ウミは「Drupal」に特化したサービスを提供する Drupal のエキスパートチーム。
フルリモート&フレックス制だから、働く場所を選ばず時間の使い方も自由です。
そんなワークライフバランスの整った環境で、当ブログに書かれているような
様々な技術を共に学びながら、Drupalサイト開発に携わってみたい方を募集しています。
まずはお話だけでも大歓迎!ぜひお気軽にご連絡ください。