Solrでスキーマの定義とドキュメントの登録を行う
前回の投稿に引き続き、Solrに慣れ親しんでいきます。
今回の投稿では、スキーマの定義、および、JSONを使ったドキュメント登録を行います。引き続き、チュートリアルと↓の本を参考にしています。
Solrサーバの起動とSolrCoreの作成
話を単純にするために、クラスタ (SolrCloud)ではなく、スタンドアロンで起動して、SolrCoreとして"kenikki"を作ります (前回の投稿も参考にしてみてください) 。
$ pwd /tmp/solr/solr-7.5.0 $ ./bin/solr start ... Waiting up to 180 seconds to see Solr running on port 8983 [/] Started Solr server on port 8983 (pid=82558). Happy searching! $ ./bin/solr create_core -c kenikki -d ./server/solr/configsets/_default ... Created new core 'kenikki' $ ls -l ./server/solr/ total 24 -rw-r--r-- 1 onishik wheel 3095 9 18 13:08 README.txt drwxr-xr-x 4 onishik wheel 128 9 18 13:08 configsets drwxr-xr-x 5 onishik wheel 160 12 1 11:34 kenikki -rw-r--r-- 1 onishik wheel 2170 9 18 13:08 solr.xml -rw-r--r-- 1 onishik wheel 1006 9 18 13:08 zoo.cfg
http://localhost:8983/
にアクセスすると、管理画面が表示されます。Core Adminに作成した"kenikki"が追加されていることも確認できます。
スキーマの定義
前回の投稿では、HTMLファイルを全く加工せずにPOSTしていましたので、一見不要そうなフィールドも多々有りました。今回は予めスキーマを定義します。
Solrのスキーマの属性は以下のようなものがあります (詳細はこちら) 。
name
: フィールド名type
: 型 (Solr組み込みの型の一覧)indexed
: trueの場合、クエリで検索可能なフィールドになる (デフォルトはtrue)stored
: trueの場合、クエリの結果に値を含めることができる (デフォルトはtrue)required
: trueの場合、POST時の必須項目となる (デフォルトはfalse)multiValued
: trueの場合、複数の値を持つことができる (デフォルトはfalse)
今回は6つのフィールドを定義します。
published_datetime
は日時のため、pdate型で指定tag
は複数の値を持つ (ex. "Python","機械学習" など) ため、multiValuedをtrueにしてますdescription
は、内容的にはcontent
の一部なので検索のためにインデックスはさせないが、検索結果には表示することを想定して、storedはtrueにしてますcontent
は、逆に検索に使うのでインデックスされている必要があるが、検索結果の表示には使わないことを想定して、storedはfalseにしてます
name | type | indexed | stored | required | multiValued |
---|---|---|---|---|---|
url | string | ○ | ○ | ○ | - |
published_datetime | pdate | ○ | ○ | ○ | - |
title | text_ja | ○ | ○ | ○ | - |
tag | string | ○ | ○ | - | ○ |
description | text_ja | - | ○ | - | - |
content | text_ja | ○ | - | ○ | - |
注意すべきは、string型とtext_ja型です。
- stringは、solr.StrFieldクラスの実装で、完全一致やワイルドカード・正規表現などによる検索ができます
- text_jaは、solr.TextFieldクラスの実装で、日本語に適したアナライザが設定されており、全角・半角などの表記揺れや、複合名詞による検索などにも対応できるようになります
- アナライザでは文字フィルタ (charFilter) 、トークナイザ (tokenizer, 形態素解析器) 、トークンフィルタ(filter) を定義できます
いずれの型もmanaged-schemaに定義されてます。
... <fieldType name="string" class="solr.StrField" sortMissingLast="true" docValues="true"/> ... <fieldType name="text_ja" class="solr.TextField" autoGeneratePhraseQueries="false" positionIncrementGap="100"> <analyzer> <tokenizer class="solr.JapaneseTokenizerFactory" mode="search"/> <filter class="solr.JapaneseBaseFormFilterFactory"/> <filter class="solr.JapanesePartOfSpeechStopFilterFactory" tags="lang/stoptags_ja.txt"/> <filter class="solr.CJKWidthFilterFactory"/> <filter class="solr.StopFilterFactory" words="lang/stopwords_ja.txt" ignoreCase="true"/> <filter class="solr.JapaneseKatakanaStemFilterFactory" minimumLength="4"/> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> </fieldType> ...
スキーマは以下のように http://localhost:8983/solr/kenikki/schema
にPOSTすることで定義できます。
$ curl -X POST -H 'Content-type:application/json' --data-binary '{ "add-field": { "name": "url", "type": "string", "indexed": "true", "stored": "true", "required": "true", "multiValued": "false" }, "add-field": { "name": "published_datetime", "type": "pdate", "indexed": "true", "stored": "true", "required": "true", "multiValued": "false" }, "add-field": { "name": "title", "type": "text_ja", "indexed": "true", "stored": "true", "required": "true", "multiValued": "false" }, "add-field": { "name": "tag", "type": "string", "indexed": "true", "stored": "true", "required": "false", "multiValued": "true" }, "add-field": { "name": "description", "type": "text_ja", "indexed": "false", "stored": "true", "required": "false", "multiValued": "false" }, "add-field": { "name": "content", "type": "text_ja", "indexed": "true", "stored": "false", "required": "true", "multiValued": "false" } }', http://localhost:8983/solr/kenikki/schema
API経由の場合はmanaged-schemaファイルの方に書き込まれます。
... <field name="content" type="text_ja" multiValued="false" indexed="true" required="true" stored="false"/> <field name="description" type="text_ja" multiValued="false" indexed="false" required="false" stored="true"/> <field name="published_datetime" type="pdate" multiValued="false" indexed="true" required="true" stored="true"/> <field name="tag" type="string" multiValued="true" indexed="true" required="false" stored="true"/> <field name="title" type="text_ja" multiValued="false" indexed="true" required="true" stored="true"/> <field name="url" type="string" multiValued="false" indexed="true" required="true" stored="true"/> ...
またAPIへPOSTする以外に、schema.xmlファイル (ここではserver/solr/kenikki/conf/schema.xml
) を作成・編集することでもスキーマを定義できます。
- この場合は、SolrCoreのリフレッシュまたはSolr再起動が必要となります
ドキュメントの作成
上のスキーマに沿って各項目に値を投入するために、前回収集したけ日記のHTMLファイルから、JSONファイルを生成します。
- JSON以外にも、XMLやCSVなどの形式でもOKです
- 1つのHTMLファイルから、1つのJSONファイルを生成します
- 各項目はBeautifulSoupで抽出してます
import os import json from bs4 import BeautifulSoup # pip install beautifulsoup4 for file_name in os.listdir('/tmp/solr/documents/'): # HTMLファイル以外は除外 if not file_name.endswith('.html'): continue # HTML文字列を取得 with open('/tmp/solr/documents/' + file_name, 'r') as f: html = f.read() # BeautifulSoupで解析 soup = BeautifulSoup(html, 'html.parser') # 各項目をHTMLから抽出 entry_dict = { 'url': soup.find('a', class_='entry-title-link bookmark')['href'], 'published_datetime': soup.find('header', class_='entry-header').time['datetime'], 'title': soup.title.string, 'tag': [a.string for a in soup.find_all('a', class_='entry-category-link')], # multiValueなのでリスト 'description': soup.find('meta', property='og:description')['content'], 'content': soup.find('div', class_='entry-content').text } # 1HTMLファイルにつき1JSONファイルでjsonディレクトリ以下に出力 with open('/tmp/solr/documents/json/' + file_name.replace('.html', '.json'), 'w') as f: f.write(json.dumps(entry_dict, ensure_ascii=False))
ドキュメントの登録
生成されたJSONファイルをPOSTで登録します。前回と同じくpostスクリプトを使います。
$ ./bin/post -c kenikki /tmp/solr/documents/json/* ... POSTing file 2018-11-17-230000.json (application/json) to [base]/json/docs 113 files indexed. COMMITting Solr index changes to http://localhost:8983/solr/kenikki/update... Time spent: 0:00:01.370
ドキュメントの検索
いくつかのパターンで検索してみます。
contentを"Pythonリスト"で検索すると、"Pythonリスト"そのものだけではなく、"Python"と"リスト"に分割してそれぞれ含むドキュメントがヒットします。text_jaで定義された形態素解析器によって、柔軟な検索が行われている例です。
http://localhost:8983/solr/kenikki/select?q=content:Pythonリスト
tagをワイルドカード検索するクエリです。tagに"Python"を含む記事がヒットします。
http://localhost:8983/solr/kenikki/select?q=tag:P?th*
published_datetimeで、2018年11月以降の記事を検索するクエリです。フィルタクエリfq
を使うことで範囲検索ができます。
http://localhost:8983/solr/kenikki/select?fq=published_datetime:[2018-11-01T00:00:00Z TO NOW]&q=*:*
まとめ
今回はスキーマを定義して、JSONファイルでドキュメントを登録して、検索できるようにしました。
参考文献
[改訂第3版]Apache Solr入門――オープンソース全文検索エンジン (Software Design plus)
- 作者: 打田智子,大須賀稔,大杉直也,西潟一生,西本順平,平賀一昭,株式会社ロンウイット,株式会社リクルートテクノロジーズ
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/27
- メディア: 大型本
- この商品を含むブログを見る