概要

全文検索エンジンのSolrを使って、Wikipedia(日本語版)の記事を検索する機能をさらっと作ってみる。面倒なことはすっ飛ばして、できるだけ少ない手数を選択。あと、ソースコードはJava。

注意事項として、Solrはけっこうメモリ食う。特にoptimize時とか、大掛かりなソート時とか。

メモリが少ないマシンでは使うと不自由するので避けた方が良いかもしれない。とりあえず手元の4G積んだマシンでは快適に動いている。

@CretedDate 2011/09/04
@Env Solr3.5.0 / lucene-gosen1.2.1
@UpdateDate 2012/02/21 Solr3.5.0に変更したりクエリの誤りを直したり

Solrの導入

まずSolrをダウンロードして解凍する。

ここからダウンロード
http://lucene.apache.org/solr/#getstarted

使い方は上記サイトかWikiに載っている。けど、設定項目がものすごく大量過ぎて全部把握するのはとてもたいへん。

落とした圧縮ファイルを解凍したら、できたディレクトリにcdする。

$ cd apache-solr-3.5.0

配下にexampleというディレクトリがいる。その子にSolrのサーバのサンプルが入っている。

自前で設定を1つ1つ書いていくのは骨が折れるので、今回はexampleディレクトリをコピーして適当に修正して使いまわすことにする。ディレクトリ名は仮にwikipediaとする。

$ cp -r example wikipedia

いろいろと今回は使わないものが入っていて邪魔なので、消してスッキリした気持ちになってみる。

$ cd wikipedia
$ rm -rf example-DIH
$ rm -rf exampledocs
$ rm -rf multicore
$ rm README.txt

少しスッキリした。

solr.xmlの編集

次にsolr.xmlを編集する。core名がcollection1とかいうやる気のない名前になってるので、wikipediaとでも名付けておく。

$ vi solr/solr.xml

<cores adminPath="/admin/cores" defaultCoreName="wikipedia">
  <core name="wikipedia" instanceDir="." />
</cores>

schema.xmlの編集

次にschema.xmlで登録するデータの構成を指定する。

Solrは複数のフィールド(RDBのカラム的な立ち位置)を設定してデータを登録できる。ユニークIDを持たせることもできるので、用途によってはRDBのテーブルに近い感じでデータを扱える。

そのスキーマ情報を決めるのがschema.xml。

既存のschema.xmlはいろんなスキーマの定義方法に関する例文が載っていて、読むと役立つ。けど編集する分には長過ぎるので今回は1から書く。

$ vi solr/conf/schema.xml

<?xml version="1.0" encoding="UTF-8" ?>
<schema name="wikipedia" version="1.4">
  <types>
    <fieldType name="string"  class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
    <fieldType name="tdate" class="solr.TrieDateField" omitNorms="true" precisionStep="6" positionIncrementGap="0"/>
    <fieldType name="text_cjk" class="solr.TextField" positionIncrementGap="100">
      <analyzer>
        <tokenizer class="solr.CJKTokenizerFactory"/>
      </analyzer>
    </fieldType>
  </types>
  <fields>
   <fields>
     <field name="id" type="string" indexed="true" stored="true" required="true" />
     <field name="title" type="text_cjk" indexed="true" stored="true" required="true" />
     <field name="title_annotation" type="string" indexed="true" stored="true" required="false" />
     <field name="text" type="text_cjk" indexed="true" stored="true" required="true" />
     <field name="text_count" type="tint" indexed="true" stored="true" required="true" />
     <field name="last_modified" type="tdate" indexed="true" stored="true"/>
   </fields>
  </fields>
  <uniqueKey>id</uniqueKey>
  <defaultSearchField>text</defaultSearchField>
  <solrQueryParser defaultOperator="AND"/>
</schema>

上記スキーマは、文書のID、タイトル、本文、本文の長さ、最終更新日時などを格納するフィールドを持っている。title_annotationフィールドは(曖昧さ回避)のようなタイトルの後ろに付いてる内容を入れるために作った。

それぞれ数値とか日付とかの型を持っている。titleとtextはCJKAnalyzer(N-gram)を利用して部分一致検索ができるようにしている。title_annotationは完全一致でしか検索できないStrFieldを利用。

ここで利用している情報はすべて、ダウンロード可能なWikipediaの情報の中のjawiki-latest-pages-articles.xmlから取得できる。詳細は後述。

今回は入れてないけどWikipediaからは、所属カテゴリ、ページ間リンク情報、リダイレクト情報、使用してるテンプレートなどの情報も取得することができる。

solrconfig.xmlの編集

次にsolrconfig.xmlmaxFieldLengthを変更する。maxFieldLengthは1つのフィールドで登録できる単語数の上限で、デフォルトは10000になっている。このままだと長い記事は下の方の言葉が索引に入らなくなる。

1つのフィールドの上限を大きくすれば、それだけ使用するメモリも多くなる。上限が読めない(巨大な文書が対象になる可能性があるような)情報については適度な数字を指定したいところだけど、Wikipeda日本語ページなら上限がある程度読めるので、遠慮せずに32bitのMAXを入れておく。

$ vi solr/conf/solrconfig.xml

<maxFieldLength>2147483647</maxFieldLength>

他にも修正したい点はいくつかあるけど、変えなくてもとりあえずは動くから割愛。

Solrサーバを立ち上げる

以下のコマンドを実行すると、Solrのサーバは立ち上がる。Javaがインストールされている必要がある。

$ java -jar start.jar

立ち上がったら、以下のURLを見てみる。

http://localhost:8983/solr/admin/

検索窓があるページが開かれると思う。ここからSolrに入っている文書を検索できる。例えば「*:*」と記述すると全文書を検索する。残念ながらまだデータが入ってないので、検索しても何も出てこないけど。

SCHEMA BROWSER(入ってるデータの統計情報が見れる)とかFULL INTERFACE(検索機能完全版)など、いろいろ役立つ機能も入っている。

Wikipediaの情報を取得する

Wikipediaはクロールが禁止されている。その代わり、全情報が登録されたダンプデータが公開されている。

下記URLのjawiki-latest-pages-articles.xml.bz2というファイルが、日本語Wikipediaの全記事の情報が入ったXML。

http://dumps.wikimedia.org/jawiki/latest/

先日落としたら、解凍後のファイルサイズが5.5Gになっていた。デカイので取り扱い注意。

とりあえず解凍。けっこう時間がかかる。

$ bunzip2 jawiki-latest-pages-articles.xml.bz2

commons-codec使って圧縮ファイルを直接InputStreamに入れた方が良かったかも、と後で思った。

解凍されたファイルは生半可なエディタで見ると簡単に固まるので注意。

ファイルにどういった内容が書かれているかは、下記URL参照。

http://www.mwsoft.jp/programming/munou/wikipedia_data_list.html#pages-articles.xml

XMLのパースとデータ投入

落としてきたXMLを解析して、Solrにデータを放り込む。DOMなんかでやったらあっさりOutOfMemoryになること請け合いなので、SAX系のイベントドリブンな子を使うことになる。

今回はJava6から登場したStAX(一度使ってみたかった)を利用してXMLをパースし、拾った情報をSolrJというライブラリを用いてSolrに登録しようと思う。

動かすにはいろいろjarをクラスパスに入れる必要がある。とりあえず以下を追加したら動いた。

Mavenを使った方が楽かも。その場合はこんな感じのdependency。

        <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-core</artifactId>
            <version>3.5.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-solrj</artifactId>
            <version>3.5.0</version>
        </dependency>

実行するコードはこんな感じ。Solrのサーバを立ち上げておかないと動かない。

PagesArticlesXmlParser.java
XMLをパースしてModelに詰め込んで、100件溜まるたびに登録するみたいなことをしている。

WikipediaModel.java
Wikipediaの情報を格納してsaveする機能を持っている。save機能使ってないけど。

見ての通り、例外処理もしてない適当なコードだけど、とりあえず動くはず。


我が家のAthlon II X4 640(3000 MHz)でメモリ16GB積んだマシンだと、だいたい30分くらい(1833秒)で全データの登録ができた。Cerellon E3400(2400 MHz)でメモリ4GBのマシンでも、36分(2174秒)。

あまり処理速度に気を遣ってないコードだけど、HotSpot様のお陰で実行後に徐々に速度が上がっていくのが見られた。こうしてプログラマは堕落していくんだな。

さっそく遊んでみる

これでWikipediaの情報を好きに検索できるようになったはず。さっそく遊んでみる。

まず、下記URLを開く。
http://localhost:8983/solr/admin/

試しにC言語が含まれるページを検索してみる。

開いたページの[FULL INTERFACE]をクリックして、以下のような条件を入力する。

項目
Solr/Lucene Statementtext:"C言語"
Maximum Rows Returned1000
Fields to Returntitle
Output Typecsv

これは、C言語という文字が含まれるページのタイトルを1000件、CSV形式で返せという意味になる。

結果はこちら

デニス・リッチーとかブライアン・カーニハンといった、いかにもな項目も出てくるし、ハノイの塔とかワンダースワンといったあまり想像しない項目が出てきたりと、見ていて面白い。


もう1個、中つ国(指輪物語に出てくる用語)を検索してみる。

text:"中つ国"

結果はこちら

ちゃんとガンダルフやボロミア、サルマン(3つとも指輪物語の登場人物)などが引っかかる。関連用語を抽出する上でも割と有用っぽい。

これを使えば一時期流行った、Wikipediaの別のページに6回クリックして行けるかを競う処理などを自動化できたりもする。


次に最近更新があった記事を調べてみる。

今回のダンプデータは2011年8月に取得したものなので、last_modifiedが2011年8月以降のものをまず抽出して、更新日降順でソートしてやれば良い。まず、以下の条件で検索する。

項目
Solr/Lucene Statementlast_modified:[2011-08-01T00:00:00Z TO *]
Maximum Rows Returned10
Fields to Returntitle,last_modified
Output Typecsv

次に、ブラウザにURLの末尾に&sort=last_modified+descを追加する。

これで利用しているダンプデータの中で、直近で更新された10件のデータが取得できる。

結果は思ったほど面白い情報が入ってなかったので割愛。


もう1つ、1ヶ月に何件の記事が更新されているのかを見てみる。

項目
Solr/Lucene Statementlast_modified:[2011-07-01T00:00:00Z TO 2011-07-31T23:59:59Z]
Maximum Rows Returned10
Fields to Returntitle,last_modified
Output Typexml

出力タイプをXMLにすると、検索に何件引っかかったかの情報なども見れる。

今回はnumFound="130299"という結果が返ってきた。2011年の7月には13万件ほどの記事に手が入っていたようだ。

ふむ、2011/7/14にドリームキャストの記事が更新されてるな。実際のWikipediaの編集ページを見ると、確かにその日、いくつかの点で加筆が行われていたようだ。さすがドリキャス、まだまだ現役だな。


最後にWikipediaで一番長い記事を調べてみる。このためにtext_countフィールドを入れたのだ。

まずは以下の検索条件を実行。

項目
Solr/Lucene Statementtext_count:[50000 TO *]
Maximum Rows Returned100
Fields to Returntitle,text_count
Output Typecsv

表示されたURLの末尾に、&sort=text_count+descを追加する。これでWikipediaの長い記事ベスト100が出せるはず。

結果はこちら

1位は常用漢字一覧でした。でも、この結果はイマイチ正しくない

今回はWiki記法の分も文字数にカウントしてしまっているので、スタイル指定が多い一覧系のページが上位に来やすくなってしまっている。要検討。

なにげにGNU General Pulbic Licenseが5位に食い込んでいる。確かにかなり長い。

他にできること

さて、ひとしきり遊んでちょっと満足しました。

今後の課題ですが、まず登録するtextはWiki記法の部分を消した方が良いかなぁと。あとはNgramを使っているけど形態素解析も試してみたかったり、カテゴリの情報も使ってみたかったり。

形態素解析はlucene-gosenやKuromoji、Igo/Gomokuなどいろんなライブラリがある。カテゴリ情報は例のダウンロードページのjawiki-latest-categorylinks.sqlなどから取れる。

また、現状だと日付情報がlast_modifiedしかないけど、created_at的な時間情報も欲しいところ。これはjawiki-latest-page.sqlのpage_touchedから取れたはず。

MoreLikeThisを使って、この記事に似ている記事を出せという命令などもしてみたい。

などなど、もう少し情報を充実させることも可能。まぁ、ボチボチ遊んでみます。

追記

2012/2/21 MoreLikeThisで類似ページを出してみた