10行Pythonで作るシンプルな全文検索アプリ
Pythonでちょっとした検索機能を作りたいことがあります。
たとえば、
- 手元の文章を検索したい
- JSONデータをキーワードで探したい
- 「京都を含み、カテゴリが会社」のような条件検索をしたい
- 検索サーバーを立てるほどではない
というケースです。
nlp4j-local-search を使うと、Pythonから簡単にローカル全文検索を利用できます。
まずは、実際に検索してみます。
インストール
pip install nlp4j-local-search==0.7.4.0
最新版はこちらです。
https://pypi.org/project/nlp4j-local-search/0.7.4.0/
約10行で検索してみる
from nlp4j_local_search import SearchEngine
with SearchEngine("ja") as engine:
engine.add("1", "京都は日本の歴史ある都市です", fields={"category": "city"})
engine.add("2", "京都市には任天堂の本社があります", fields={"category": "company"})
engine.add("3", "大阪は関西の大都市です", fields={"category": "city"})
engine.commit()
for r in engine.search("京都"):
print(r.id, r.body)
これだけで、登録した文章から「京都」を含む文書を検索できます。
イメージとしては、
1 京都は日本の歴史ある都市です
2 京都市には任天堂の本社があります
のような結果が得られます。
何をしているのか
流れは非常に単純です。
まず検索エンジンを作ります。
with SearchEngine("ja") as engine:
ja は日本語を意味します。
次に文書を登録します。
engine.add("1", "京都は日本の歴史ある都市です")
第1引数が文書ID、第2引数が検索対象となる文章です。
登録が終わったら、
engine.commit()
して、検索します。
engine.search("京都")
基本的には、
add
↓
commit
↓
search
だけです。
カテゴリなどの属性も付けられる
文章だけでなく、カテゴリなどのフィールドも持たせることができます。
engine.add(
"1",
"京都は日本の歴史ある都市です",
fields={"category": "city"},
)
これなら、
本文: 京都は日本の歴史ある都市です
category: city
というデータとして検索できます。
たとえば、
engine.search("category:city")
とすると、カテゴリが city の文書だけを検索できます。
キーワード検索と条件検索を組み合わせる
さらに、
engine.search("京都 AND category:company")
と書くこともできます。
今回のデータなら、
京都市には任天堂の本社があります
が該当します。
つまり、
「京都」を含む
AND
category が company
という検索です。
Pythonのコードとして見ると、
results = engine.search("京都 AND category:company")
だけです。
OR検索もできる
engine.search("京都 OR 大阪")
とすると、「京都」または「大阪」を含む文書を検索できます。
たとえば、
for r in engine.search("京都 OR 大阪"):
print(r.body)
とすれば、今回登録した3件すべてが検索対象になります。
JSONをそのまま登録することもできる
実際のアプリでは、文章だけではなくJSON形式のデータを扱うことも多いと思います。
その場合は add_json() が使えます。
engine.add_json({
"id": "1",
"body": "京都は日本の歴史ある都市です",
"category": "city",
"country": "Japan",
})
たとえば、
engine.search("category:city AND country:Japan")
のような検索ができます。
JSONLなどのデータを検索対象にしたい場合にも使いやすい形です。
実はApache Luceneで検索している
ここまで、検索エンジンについて特に意識せず、
engine.search("京都")
と書いてきました。
内部では Apache Lucene という全文検索ライブラリを使っています。
Luceneは本格的な検索システムでも利用される検索ライブラリですが、Pythonから直接使おうとすると少し扱いにくいところがあります。
nlp4j-local-search は、その部分をPythonから簡単に扱えるようにしています。
イメージとしては、
Python
↓
nlp4j-local-search
↓
Apache Lucene
という構成です。
Pythonユーザーは、最初からLuceneのJava APIを意識する必要はありません。
検索サーバーを用意しなくてよい
全文検索というと、
- Elasticsearch
- OpenSearch
- Solr
などの検索サーバーを思い浮かべるかもしれません。
もちろん、大規模なサービスではそうした検索サーバーが適しています。
一方で、
- 数千〜数十万件程度のデータを手元で検索したい
- Jupyter Notebookでデータを調べたい
- CSVやJSONLを検索できるようにしたい
- NLPやRAGの前処理として検索したい
- とりあえず検索機能を試したい
という場合には、検索サーバーを構築すること自体が少し重い場合があります。
nlp4j-local-search では、Pythonプログラムの中で検索インデックスを作って、そのまま検索できます。
検索結果にはスコアも付く
検索結果にはIDと本文だけでなく、検索スコアも含まれます。
for r in engine.search("京都"):
print(r.id, r.body, r.score)
たとえば、
2 京都は日本の都市です。 0.1805...
3 京都市には任天堂の本社があります 0.1621...
のように、検索結果の順位付けにも利用できます。
もう少し高度な検索もできる
検索文字列には、さらにいろいろな条件を指定できます。
engine.search("京都 AND 任天堂")
engine.search("京都 OR 大阪")
engine.search("category:city")
engine.search("京都 AND category:company")
さらに、ワイルドカードやフレーズ検索なども利用できます。
engine.search("京都*")
engine.search('"歴史ある都市"')
最初は単純に、
engine.search("京都")
だけ覚えておいて、必要になったら検索条件を増やしていく使い方でもよいと思います。
ベクトル検索もできる
nlp4j-local-search は通常のキーワード検索だけでなく、ベクトル検索にも対応しています。
たとえば、
with SearchEngine("ja", vector_dimension=2) as engine:
engine.add("1", [1.0, 0.0])
engine.add("2", [0.0, 1.0])
engine.commit()
results = engine.search_vector([0.9, 0.1])
のように利用できます。
そのため、
キーワード検索
+
フィールド検索
+
ベクトル検索
を同じローカル検索エンジンの中で扱うこともできます。
ただし、まず検索機能を試してみたいだけなら、ベクトル検索を意識する必要はありません。
まとめ
Pythonで検索機能を作るというと、少し大がかりな印象があります。
しかし nlp4j-local-search なら、
from nlp4j_local_search import SearchEngine
with SearchEngine("ja") as engine:
engine.add("1", "京都は日本の歴史ある都市です")
engine.add("2", "京都市には任天堂の本社があります")
engine.commit()
for r in engine.search("京都"):
print(r.body)
くらいのコードから始められます。
最初は、
search("京都")
という単純なキーワード検索。
必要になったら、
search("京都 AND category:company")
のような条件検索。
さらに必要なら、集計やベクトル検索へ進む。
「Pythonの手元のデータを、とりあえず検索できるようにしたい」
というところから始められる、小さな検索エンジンです。