け日記

最近はPythonでいろいろやってます

Solrの環境をMacに構築する

仕事で検索プラットフォームの Apache Solr を扱うことになったのですが、今までブラックボックスにしてきてしまっていたので、この機会に勉強することにしました。

http://lucene.apache.org/solr/

今回は、Mac上にSolr (7.5.0) の環境を構築し、け日記のエントリを検索できるようにします。チュートリアルと↓の本を参考にしています。

[改訂第3版]Apache Solr入門――オープンソース全文検索エンジン (Software Design plus)

前提条件

Java 8.0以降のランタイムがインストールされている必要があります。

Solrのダウンロードと起動

Solrのダウンロードと起動を行っていきます。Dockerを使った方法もありますが、今回はバイナリをダウンロードしてきて直接起動するようにします。

バイナリをダウンロード・展開します。

$ pwd
/tmp/solr

$ wget http://ftp.jaist.ac.jp/pub/apache/lucene/solr/7.5.0/solr-7.5.0.zip

$ unzip solr-7.5.0.zip

$ cd solr-7.5.0

次に、Solrサーバの起動です。同梱されているbin/solrコマンドで立ち上げます。

  • SolrCloudで立ち上げてます
    • 動的なロードバランシングやZooKeeperによるクラスタ管理を行います
  • 2ノードを立ち上げます
    • ポートは8983 (pid=12771) と7574 (pid=12872) を割り当てます
$ ./bin/solr start -e cloud
Welcome to the SolrCloud example!
...
This interactive session will help you launch a SolrCloud cluster on your local workstation.
To begin, how many Solr nodes would you like to run in your local cluster? (specify 1-4 nodes) [2]:
2
Ok, let's start up 2 Solr nodes for your example SolrCloud cluster.
Please enter the port for node1 [8983]:
8983
Please enter the port for node2 [7574]:
7574
Creating Solr home directory /tmp/solr/solr-7.5.0/example/cloud/node1/solr
Cloning /tmp/solr/solr-7.5.0/example/cloud/node1 into
   /tmp/solr/solr-7.5.0/example/cloud/node2

Starting up Solr on port 8983 using command:
"/tmp/solr/solr-7.5.0/bin/solr" start -cloud -p 8983 -s "/tmp/solr/solr-7.5.0/example/cloud/node1/solr"
...
Started Solr server on port 8983 (pid=12771). Happy searching!
Starting up Solr on port 7574 using command:
"/tmp/solr/solr-7.5.0/bin/solr" start -cloud -p 7574 -s "/tmp/solr/solr-7.5.0/example/cloud/node2/solr" -z localhost:9983
...
Started Solr server on port 7574 (pid=12872). Happy searching!

INFO  - 2018-11-24 12:46:41.796; org.apache.solr.common.cloud.ConnectionManager; zkClient has connected
INFO  - 2018-11-24 12:46:41.816; org.apache.solr.common.cloud.ZkStateReader; Updated live nodes from ZooKeeper... (0) -> (2)
INFO  - 2018-11-24 12:46:41.843; org.apache.solr.client.solrj.impl.ZkClientClusterStateProvider; Cluster at localhost:9983 ready

...続く...

次に、け日記用のコレクションを作ります。

  • コレクションは、インデックスを作って検索するドキュメント集合の単位です
    • 後ほど検索クエリが出てきますが、パスにここで設定したコレクション名kenikkiが付きます
  • シャード数は2で設定
    • シャードはコレクションの論理的なパーティションで、ドキュメントのインデックス作成時にどのシャードに属するのかが決められます
    • クエリは各シャードにルーティングしてそれぞれで検索されます
  • レプリカ数も2で設定
    • レプリカはシャードごとに設定し、各シャードは1つ以上のレプリカを持ちます
    • レプリカの1つはleaderに指名され、leaderはインデックスの更新を他のreplicaに伝搬します
Now let's create a new collection for indexing documents in your 2-node cluster.
Please provide a name for your new collection: [gettingstarted]
kenikki
How many shards would you like to split kenikki into? [2]
2
How many replicas per shard would you like to create? [2]
2
Please choose a configuration for the kenikki collection, available options are:
_default or sample_techproducts_configs [_default]
_default
Created collection 'kenikki' with 2 shard(s), 2 replica(s) with config-set 'kenikki'

Enabling auto soft-commits with maxTime 3 secs using the Config API

POSTing request to Config API: http://localhost:8983/solr/kenikki/config
{"set-property":{"updateHandler.autoSoftCommit.maxTime":"3000"}}
Successfully set-property updateHandler.autoSoftCommit.maxTime to 3000

SolrCloud example running, please visit: http://localhost:8983/solr

これでSolrサーバが起動します。最後に表示されたhttp://localhost:8983/solrにブラウザからアクセスすると、ダッシュボードが表示されます。

f:id:ohke:20181124125344p:plain

管理画面からCloudタブを開くと、作成したコレクション"kenikki"に2本のシャードとそれぞれに2つのレプリカが紐付いていることがわかります。

f:id:ohke:20181124130654p:plain

なおコレクションを消すときは以下のコマンドです。

$ bin/solr delete -c kenikki

またサーバを停止するコマンドは以下です。

$ bin/solr stop -all

け日記のエントリページのダウンロード

け日記から過去の全エントリをHTMLファイルでダウンロードしておきます。このHTMLファイルをSolrで検索できるようにします。

import requests
import re
import urllib.request
import time

list_urls = [
    'https://ohke.hateblo.jp/archive/2015',
    'https://ohke.hateblo.jp/archive/2016',
    'https://ohke.hateblo.jp/archive/2017?page=1',
    'https://ohke.hateblo.jp/archive/2017?page=2',
    'https://ohke.hateblo.jp/archive/2018?page=1',
    'https://ohke.hateblo.jp/archive/2018?page=2'
]

downloaded = set()  # ダウンロード済みのURL

for list_url in list_urls:
    # リストページからエントリのURLを抽出 (ダウンロード済みのURLは除外)
    list_html = requests.get(list_url).text
    entry_urls = set(re.findall(r'https://ohke.hateblo.jp/entry/[0-9]{4}/[0-9]{2}/[0-9]{2}/[0-9]{6}', list_html)) - downloaded
    
    # エントリページ (HTMLファイル) をダウンロードして保存
    for entry_url in entry_urls:
        urllib.request.urlretrieve(entry_url, '/tmp/solr/documents/'+ entry_url[30:].replace('/', '-') + '.html')
        downloaded.add(entry_url)
        
        time.sleep(5)  # アクセスレート制限

ダウンロードされたファイルは/tmp/solr/documents/配下に保存してます。

$ ls /tmp/solr/documents
2015-06-18-231834.html
2015-12-07-000504.html
...
2018-11-17-230000.html

ドキュメントのインデックス

上でダウンロードしたエントリのファイルを、ドキュメントに追加します。

solrのパッケージに含まれるbin/postを実行することでインデックスされます。HTMLファイル以外にもxmlやjson、pdfやwordにも対応しているようです。

$ pwd
/tmp/solr/solr-7.5.0

$ ./bin/post -c kenikki /tmp/solr/documents/*
java -classpath /tmp/solr/solr-7.5.0/dist/solr-core-7.5.0.jar -Dauto=yes -Dc=kenikki -Ddata=files org.apache.solr.util.SimplePostTool /tmp/solr/documents/2015-06-18-231834.html /tmp/solr/documents/2015-12-07-000504.html ... /tmp/solr/documents/2018-11-17-230000.html
SimplePostTool version 5.0.0
Posting files to [base] url http://localhost:8983/solr/kenikki/update...
Entering auto mode. File endings considered are xml,json,jsonl,csv,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,odp,ods,ott,otp,ots,rtf,htm,html,txt,log
POSTing file 2015-06-18-231834.html (text/html) to [base]/extract
POSTing file 2015-12-07-000504.html (text/html) to [base]/extract
...
POSTing file 2018-11-17-230000.html (text/html) to [base]/extract
113 files indexed.
COMMITting Solr index changes to http://localhost:8983/solr/kenikki/update...
Time spent: 0:00:11.996

これで検索の準備が整いました。

検索

インデックスされたドキュメントの検索するためには、Solrのエンドポイントを直接叩く方法と、管理画面から行う方法 (ここではhttp://localhost:8983/solr/#/kenikki/queryから) があります。

まずはクエリに何も指定せずに検索してみます。URLはhttp://localhost:8983/solr/kenikki/select?q=*:*となります。

  • 検索時間は15ミリ秒 (responseHeader.QTime)
  • ヒット件数が113件 (response.numFound)
  • response.docsに検索結果が入ってます
{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":15,
    "params":{
      "q":"*:*",
      "_":"1543032835024"}},
  "response":{"numFound":113,"start":0,"maxScore":1.0,"docs":[
      {
        "id":"/tmp/solr/documents/2015-06-18-231834.html",
        "og_image":["https://cdn.image.st-hatena.com/image/scale/0b8f3ad86af86640c8733aa2f27dd3f0b2c09090/backend=imager;enlarge=0;height=1000;version=1;width=1200/https%3A%2F%2Fcdn.user.blog.st-hatena.com%2Fdefault_entry_og_image%2F99455833%2F1533459624629378"],
        "twitter_app_url_iphone":["hatenablog:///open?uri=https%3A%2F%2Fohke.hateblo.jp%2Fentry%2F2015%2F06%2F18%2F231834"],
        "article_published_time":[1434637114],
        "twitter_card":["summary"],
        "stream_content_type":["text/html"],
        "og_site_name":["け日記"],
        "description":["はじめまして。 このブログを始めた経緯と目的を忘れないようにするために、初投稿としてメモしておきます。 経緯 私自身はいわゆるシステムインテグレータ(SIer)に勤めており、主にシステム開発、部内の開発標準・開発環境の整備を担当しています。 古き良きSIerのエンジニアのご多分に漏れず、ありがちなもやもやを抱えています。 レガシーな技術・・・Java 1.4もCOBOLもバリバリの現役です 社内に閉じた謎ツール・・・DAOを生成するエクセルファイル コミュニケーション能力、マネジメント能力の方が大事です・・・携帯電話、メーラー、エクセルが必須ツール このあたりが原因となって、エンジニアとしての…"],
        "title":["このブログの趣旨 - け日記"],
        "twitter_app_id_iphone":[583299321],
        "og_description":["はじめまして。 このブログを始めた経緯と目的を忘れないようにするために、初投稿としてメモしておきます。 経緯 私自身はいわゆるシステムインテグレータ(SIer)に勤めており、主にシステム開発、部内の開発標準・開発環境の整備を担当しています。 古き良きSIerのエンジニアのご多分に漏れず、ありがちなもやもやを抱えています。 レガシーな技術・・・Java 1.4もCOBOLもバリバリの現役です 社内に閉じた謎ツール・・・DAOを生成するエクセルファイル コミュニケーション能力、マネジメント能力の方が大事です・・・携帯電話、メーラー、エクセルが必須ツール このあたりが原因となって、エンジニアとしての…"],
        "dc_title":["このブログの趣旨 - け日記"],
        "content_encoding":["UTF-8"],
        "content_type":["text/html; charset=UTF-8"],
        "stream_size":[37889],
        "x_parsed_by":["org.apache.tika.parser.DefaultParser",
          "org.apache.tika.parser.html.HtmlParser"],
        "og_type":["article"],
        "twitter_title":["このブログの趣旨 - け日記"],
        "google_site_verification":["j4_wDEBc7hC57lE-AA2X0V_vBF_DIzL1kf9mX-igpZk"],
        "og_title":["このブログの趣旨 - け日記"],
        "resourcename":["/tmp/solr/documents/2015-06-18-231834.html"],
        "article_tag":["雑記"],
        "x_ua_compatible":["IE=7; IE=9; IE=10; IE=11"],
        "twitter_description":["はじめまして。 このブログを始めた経緯と目的を忘れないようにするために、初投稿としてメモしておきます。 経緯 私自身はいわゆるシステムインテグレータ(SIer)に勤めており、主にシステム開発、部内の開発標準・開発環境の整備を担当しています。 古き良きSIerのエンジニアのご多分に漏れず、ありがちなもやもやを抱えています…"],
        "og_url":["https://ohke.hateblo.jp/entry/2015/06/18/231834"],
        "twitter_app_name_iphone":["はてなブログアプリ"],
        "_version_":1617986237913628672},
...

次に、"numpy"を含むエントリ1件を公開日時で降順ソートして検索します。クエリのURLはhttp://localhost:8983/solr/kenikki/select?q=numpy&rows=1&sort=article_published_time%20descとなります。

{
  "responseHeader":{
    "zkConnected":true,
    "status":0,
    "QTime":10,
    "params":{
      "q":"numpy",
      "sort":"article_published_time desc",
      "rows":"1",
      "_":"1543034009297"}},
  "response":{"numFound":28,"start":0,"docs":[
      {
        "id":"/tmp/solr/documents/2018-10-20-235500.html",
        "og_image":["https://cdn.user.blog.st-hatena.com/default_entry_og_image/99455833/1533459624629378"],
        "twitter_app_url_iphone":["hatenablog:///open?uri=https%3A%2F%2Fohke.hateblo.jp%2Fentry%2F2018%2F10%2F20%2F235500"],
        "article_published_time":[1540047300],
        "twitter_card":["summary_large_image"],
        "stream_content_type":["text/html"],
        "og_site_name":["け日記"],
        "description":["単語ベクトル化モデルの一つであるGloVeを試してみます。 GloVe GloVeは単語のベクトル表現を得る手法の一つで、Word2Vecの後発となります。論文はこちらです。 nlp.stanford.edu Word2Vec (skip-gram with negative sampling: SGNS) では各単語から周辺単語を予測するというタスクをニューラルネットワークで解くことによって単語ベクトルを得ますが、GloVeではコーパス全体から得られる単語間の共起行列を持ち込んだ最適化関数 (重み付き最小二乗法) で学習します。 単語iと単語jの共起行列が は、次元削減された単語ベクトル (…"],
        "title":["GloVeで単語ベクトルを得る - け日記"],
...

まとめ

Solrことはじめということで、今回はMacbookにSolrサーバーを立ち上げ、け日記を全文検索できるようにしました。

参考文献

[改訂第3版]Apache Solr入門――オープンソース全文検索エンジン (Software Design plus)

[改訂第3版]Apache Solr入門――オープンソース全文検索エンジン (Software Design plus)

  • 作者: 打田智子,大須賀稔,大杉直也,西潟一生,西本順平,平賀一昭,株式会社ロンウイット,株式会社リクルートテクノロジーズ
  • 出版社/メーカー: 技術評論社
  • 発売日: 2017/04/27
  • メディア: 大型本
  • この商品を含むブログを見る