今回は実際にスキーマを定義し検索できるところまでを説明したいと思います。
4.1 使用するデータ¶
使用するデータには青空文庫 で公開されているものを使用します。
ご存知の通り、青空文庫では著作権の切れた作品がテキストデータとして公開されています。また、登録されている作品一覧がCSVデータとして公開されています。
これらのデータを使い青空文庫用の検索スキーマを定義していきたいと思います。
4.2 青空文庫コアの用意¶
スキーマ定義の前に、青空文庫用のコアを用意します。
基本的な設定はサンプルのcollection1をコピーして使います。
$ cd ${solr.solr.home}
$ cp -r collection1 aozora
aozoraコアを作成したら ${solr.solr.home}/solr.xml にコアを追加します。ついでに、デフォルトのコアをcollection1からaozoraにします。
<solr persistent="true">
<cores adminPath="/admin/cores"
defaultCoreName="aozora"
host="${host:}"
hostPort="${jetty.port:}"
hostContext="${hostContext:}"
zkClientTimeout="${zkClientTimeout:15000}">
<core name="aozora" instanceDir="aozora" />
<core name="collection1" instanceDir="collection1" />
</cores>
</solr>
4.3 スキーマの設計¶
青空文庫の作品一覧のページにはいくつかのCSVファイルが用意されているのですが、このうちデータ項目が充実している「公開中 作家別作品一覧拡充版:全て(CSV形式、UTF-8、zip圧縮)」を使用します。
ただ、作品一覧拡充版の全項目を使うのは大変なので、今回は以下の項目を使います。
- 作品ID
- 作品名
- 副題
- 原題
- 作品著作権フラグ
- 図書カードURL
- 人物ID
- 姓
- 名
- 底本名1
- 底本出版社名1
- テキストファイルURL
- XHTML/HTMLファイルURL
これらの項目からフィールドをどのようにするか決めていきます。
今回は 表4.1 のようにしました。
表4.1: 青空文庫スキーマ
作品一覧の項目名 | フィールド名 | 型 | indexed | stored | その他 |
---|---|---|---|---|---|
id | string | true | true | ユニークキー | |
作品ID | bid | string | true | true | |
作品名 | title | text_ja | true | true | |
副題 | subtitle | string | false | true | |
原題 | orgtitle | text_ja | false | true | |
著作権フラグ | copyright | boolean | false | true | |
図書カードURL | card_url | string | false | true | |
人物ID | aid | string | true | true | |
姓 | family_name | string | false | true | |
名 | first_name | string | false | true | |
底本名1 | orgbook | text_ja | false | true | |
底本出版社名1 | publisher | text_ja | true | true | |
テキストファイルURL | text_url | string | false | true | |
HTMLファイルURL | html_url | string | false | true | |
author | text_ja | true | false | multiValued=”true” | |
content | string | false | true | ||
text | text_ja | true | false | multiValued=”true” |
ユニークキーであるidフィールドは、作品ID と 人物ID を “-” で繋げたものとしました。
著者名で検索するためのauthorフィールドを追加しました。authorフィールドはfamily_name、first_nameフィールドをコピーして作ります。
作品データをcontentフィールドに登録します。highlight検索に使いたいので、stored=”true”にしました。
textフィールドはドキュメントを包括的に検索するためのフィールドです。デフォルトで検索したいフィールドをすべてこのフィールドにコピーします。
schema.xml ファイルは以下のようになります。
<?xml version="1.0" encoding="UTF-8" ?>
<schema name="aozora" version="1.5">
<fields>
<field name="id" type="string" indexed="true" stored="true" required="true" />
<field name="bid" type="string" indexed="true" stored="true" required="true" />
<field name="title" type="text_ja" indexed="true" stored="true" required="true" />
<field name="subtitle" type="string" indexed="true" stored="true" />
<field name="orgtitle" type="text_ja" indexed="true" stored="true" />
<field name="copyright" type="boolean" indexed="true" stored="true" />
<field name="card_url" type="string" indexed="false" stored="true" required="ture"/>
<field name="aid" type="string" indexed="true" stored="true" required="true" />
<field name="family_name" type="string" indexed="false" stored="true" />
<field name="first_name" type="string" indexed="false" stored="true" />
<field name="orgbook" type="text_ja" indexed="true" stored="true" />
<field name="publisher" type="text_ja" indexed="true" stored="true" />
<field name="text_url" type="string" indexed="false" stored="true" />
<field name="html_url" type="string" indexed="false" stored="true" />
<field name="author" type="text_ja" indexed="true" stored="false" multiValued="true" />
<field name="content" type="string" indexed="false" stored="true" />
<field name="text" type="text_ja" indexed="true" stored="false" multiValued="true"/>
<field name="_version_" type="long" indexed="true" stored="true"/>
</fields>
<uniqueKey>id</uniqueKey>
<copyField source="first_name" dest="author" />
<copyField source="family_name" dest="author" />
<copyField source="author" dest="text" />
<copyField source="title" dest="text" />
<copyField source="orgtitle" dest="text" />
<copyField source="publisher" dest="text" />
<copyField source="content" dest="text" />
<!-- types はデフォルトのため割愛します -->
</schema>
4.4 データの登録¶
スキーマを定義したら、作品一覧、およびテキストファイルからデータを登録します。
作品一覧から項目を取り出したり、テキストファイルの修正(文字コードの修正)を行うのにスクリプトを用意しました。
用意したスクリプトは以下の通りです。
- aozora2solr.rb
- 公開一覧CSVファイルからSolr用の項目を抜き出し、結果(CSV形式)を標準出力に書き出す。
- download_textfile.rb
- テキストファイルURLからファイルをダウンロードする。入力にはaozora2solr.rbが出力したCSVファイルを使用する。
- make_update_doc.sh
- ダウンロードしたファイルから登録用のXMLファイルを出力する。
リスト4.1: aozora2solr.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'csv'
rows = CSV.read(ARGV[0], { :headers => true, :return_headers => false} )
header = [
"id",
"bid",
"title",
"aid",
"family_name",
"first_name",
"subtitle",
"orgtitle",
"orgbook",
"publisher",
"card_url",
"text_url",
"html_url",
"copyright"
]
CSV { |csv_out|
csv_out << header
rows.each do |row|
newrow = []
newrow << "#{row[0]}-#{row[14]}"
newrow << row[0]
newrow << row[1]
newrow << row[14]
newrow << row[15]
newrow << row[16]
newrow << row[4]
newrow << row[6]
newrow << row[27]
newrow << row[28]
newrow << row[13]
newrow << row[45]
newrow << row[50]
newrow << ((row[10] == "あり") ? "true" : "false")
csv_out << newrow
end
}
リスト4.2: download_textfile.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'csv'
require 'uri'
rows = CSV.read(ARGV[0], { :headers => true, :return_headers => false})
rows.each do |row|
next if row["text_url"].empty?
next if row["copyright"] == "true"
id = row["id"]
url = URI.parse(row["text_url"])
system("curl -s -o #{id}.zip #{url}") if url.host =~ /aozora\.gr\.jp\Z/
end
リスト4.3: make_update_doc.sh
#!/bin/sh
ERROR=error.txt
PROGNAME=$(basename $0)
for i in "$@"
do
if [ ! -e "$i" ]; then
echo "$i: No such file" >&2
continue
fi
ID=${i%%.zip}
OUT="${ID}.xml"
TMP=$(mktemp -t "${PROGNAME}") || exit 1
cat /dev/null > ${OUT}
cat << HERE >> ${OUT}
<add>
<doc>
<field name="id">$ID</field>
<field name="content" update="set"><![CDATA[
HERE
unzip -q -c ${i} | iconv -f "SJIS" -t "UTF-8" >> ${TMP} 2>> ${ERROR}
if [ $? -ne 0 ]; then
echo "failed to make ${OUT}." >> ${ERROR}
rm -f ${OUT} ${TMP}
continue
fi
cat ${TMP} | tr -d '\r' >> ${OUT}
cat << HERE >> ${OUT}
]]></field>
</doc>
</add>
HERE
rm -f ${TMP}
done
最終的に出来上がったXMLファイルをSolr付属のpost.shを使って登録します。
$ post.sh *.xml
変換から登録までの手順をまとめるとこうなります。
$ aozora2solr.rb list_person_all_extended_utf8.csv > aozora.csv
$ download_textfile.rb aozora.csv
$ make_update_doc.sh *.zip
$ curl "http://localhost:8080/solr/aozora/update?commit=true" -H 'Content-Type: application/csv' --data-binary @aozora.csv
$ post.sh *.xml
4.5 検索¶
データを登録したら、検索して値がとれることを確認します。なお、テストのためlist_person_all_extended_utf8.csv全件ではなく、先頭1000件しか登録していません。
フィールド指定無しの”スリーピー”で検索した結果。
$ curl "http://localhost:8080/solr/select?q=%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%94%E3%83%BC&wt=xml&indent=true"
<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
<int name="status">0</int>
<int name="QTime">1</int>
<lst name="params">
<str name="indent">true</str>
<str name="q">スリーピー</str>
<str name="wt">xml</str>
</lst>
</lst>
<result name="response" numFound="1" start="0">
<doc>
<str name="id">046658-001257</str>
<str name="bid">046658</str>
<str name="title">スリーピー・ホローの伝説</str>
<str name="aid">001257</str>
<str name="family_name">アーヴィング</str>
<str name="first_name">ワシントン</str>
<str name="subtitle">故ディードリッヒ・ニッカボッカーの遺稿より</str>
<str name="orgtitle">THE LEGEND OF SLEEPY HOLLOW</str>
<str name="orgbook">スケッチ・ブック</str>
<str name="publisher">新潮文庫、新潮社</str>
<str name="card_url">http://www.aozora.gr.jp/cards/001257/card46658.html</str>
<str name="text_url">http://www.aozora.gr.jp/cards/001257/files/46658_ruby_44679.zip</str>
<str name="html_url">http://www.aozora.gr.jp/cards/001257/files/46658_44767.html</str>
<bool name="copyright">false</bool>
<str name="content">
--(snip)--
</str>
<long name="_version_">1432312155267399680</long></doc>
</result>
</response>
author:”芥川” で検索した結果。
$ curl "http://localhost:8080/solr/select?q=author%3A%E8%8A%A5%E5%B7%9D&rows=5&fl=bid%2Ctitle&wt=xml&indent=true"
<?xml version="1.0" encoding="UTF-8"?>
<response>
<lst name="responseHeader">
<int name="status">0</int>
<int name="QTime">1</int>
<lst name="params">
<str name="fl">bid,title</str>
<str name="indent">true</str>
<str name="q">author:芥川</str>
<str name="wt">xml</str>
<str name="rows">5</str>
</lst>
</lst>
<result name="response" numFound="368" start="0">
<doc>
<str name="bid">000013</str>
<str name="title">十本の針</str></doc>
<doc>
<str name="bid">000014</str>
<str name="title">あばばばば</str></doc>
<doc>
<str name="bid">000015</str>
<str name="title">アグニの神</str></doc>
<doc>
<str name="bid">000016</str>
<str name="title">秋</str></doc>
<doc>
<str name="bid">000017</str>
<str name="title">あの頃の自分の事</str></doc>
</result>
</response>
以上です。
次回は、このデータをもとにクライアントサイトを構築してみたいと思います。