やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

ElasticLunr.jsで全文検索する(日本語+英語でAND検索)

 日本語と英語でAND検索できた。

成果物

GitHubPagesエラー版 * code(GitHubPagesでエラー) * なぜかGitHubPagesが作られずエラーになる * submoduleまわりに問題があるらしい * submoduleをやめてcloneした * git clone --recursive https://github.com/ytyaru/lunr-languages * .gitディレクトリ削除

概要

 ElasticLunr.jsLunr.jsの改良版。インデックスされるデータ量が少なく済む。

本体 言語ライブラリ fork
Lunr.js MihaiValentin/lunr-languages -
ElasticLunr.js weixsong/lunr-languages ytyaru/lunr-languages

 このうち言語ライブラリを改造して日本語と英語に対応した。

日本語ライブラリの問題

 英語と混在させて検索できない。これは技術系ブログにおいて致命的。

 Lunr.jsで全文検索してみた結果、this.use(lunr.multiLanguage('en', 'ja'));では日本語が有効にならない問題があった。そのため単一言語でしか検索できない。

 これはElasticLunr.jsでも同じだった。それどころか多言語化ライブラリweixsong/lunr-languagesはまともにメンテナンスもされていない始末。言語コードjaが正しいのにjpとなっていたり、tinyseg.jsがrequire.jsでインポートできるようになっていなかったり。

 そこで、weixsong/lunr-languagesをフォークしてytyaru/lunr-languagesを作った。

ytyaru/lunr-languages

 weixsong/lunr-languages/lunr.jp.jsをみてみる。ほかの言語lunr.de.jsと比較してみると、trimmerがない。なのでtrimmerを実装する。Lunr.js用のMihaiValentin/lunr-languages/lunr.ja.jsをパクる。

 すると以下のようなコードを追加することになった。

      this.pipeline.add(
        lunr.ja.trimmer,
        lunr.ja.stopWordFilter,
        lunr.ja.stemmer
      );
      lunr.tokenizer = lunr.ja.tokenizer;
    /* lunr trimmer function */
    lunr.ja.wordCharacters = "一二三四五六七八九十百千万億兆一-龠々〆ヵヶぁ-んァ-ヴーア-ン゙a-zA-Za-zA-Z0-90-9";
    lunr.ja.trimmer = lunr.trimmerSupport.generateTrimmer(lunr.ja.wordCharacters);
    lunr.Pipeline.registerFunction(lunr.ja.trimmer, 'trimmer-ja');

 細かいことはさっぱりわからないが、なんか動いたのでよしとする。

使ってみる

 require.jsを使ってほかのjsファイルをインポートする。

index.html

<script data-main="main.js" src="./lib/require/require.2.3.6.min.js"></script>

 検索したい文書をobjectでつくる。任意のキーと値で。

    var documents = [
    {
    id: 0,
    title: "Oracle released its latest database Oracle 12g",
    body: "Yestaday Oracle has released its new database Oracle 12g, this would make more money for this company and lead to a nice profit report of annual year."
    },
    ...
    ];

 elasticlunrを設定する。日本語の検索エンジンlunr.ja.jsを使う。multiLanguage('en', 'ja')したかったが、有効にならない。やむなくlunr.ja.jsのほうで英語も検索できるようにすることで対応する。

main.js

require(['lib/elasticlunr/elasticlunr.0.9.6.min.js', 
         'lib/elasticlunr/lunr-languages/tinyseg.js', 
         'lib/elasticlunr/lunr-languages/min/lunr.stemmer.support.min.js', 
         'lib/elasticlunr/lunr-languages/lunr.multi.js', 
         'lib/elasticlunr/lunr-languages/lunr.ja.js'
        ], function(elasticlunr, tinyseg, stemmerSupport, multi, ja) {
    stemmerSupport(lunr);
    tinyseg(lunr);
    ja(lunr);
//  multi(lunr);
    var index = elasticlunr(function() {
        this.use(elasticlunr.ja);
//      this.use(elasticlunr.multiLanguage('en', 'ja'));
        this.setRef('id');
        this.addField('title');
        this.addField('body');
        this.saveDocument(false);
        for (const doc of documents) { this.addDoc(doc); }
    });

 指定したキーワードで全文検索する。対象はタイトルと本文。すべてのキーワードを含む文書のみ検索対象とする。タイトルと本文のうちいずれかひとつでもすべてのキーワードを含んでいたら対象とする。

result = index.search(keyword, {
    fields: {
        title: {boost: 2, bool: 'AND'},
        body: {boost: 1, bool: 'AND'}
    }, bool: 'OR'
});

 Lunr.jsではできていた以下のことができなかった。AND,OR検索を指定できれば問題ない。

result = index.search(`*${keyword}*`, { // 部分一致検索にすることでムリヤリ英語混在でも検索できるようにする

課題

  • インデックス作成は事前に済ませておきたい
  • 修正した記事ファイルのみインデックスを作り直したい
  • 指定の記事ファイルからインデックスを作りたい
  • インデックスを高速に作りたい
実装 メリット デメリット
browser-js すでに実装できた ファイルから読み取れない。毎回インデックス作成するので文書量が増えたら遅すぎて死ぬ。
node-js 移植が簡単なはず ファイル読取できなかった。できても実行速度が遅いはず。
rust 速いはず 日本語用trimmerなどをrust言語で実装せねばならない。

 静的サイトジェネレータzolaを参考にすべきか。するとelasticlunr-rslinderaがキモっぽい。そのあたりを調べよう。

所感

 先は長い。ローカルで動く日本語+英語の検索ボックスがほしいだけなのに。インデックスの自動作成まで考えるとめちゃくちゃ大変そう。

対象環境

$ uname -a
Linux raspberrypi 5.4.83-v7l+ #1379 SMP Mon Dec 14 13:11:54 GMT 2020 armv7l GNU/Linux