青空文庫 Advent Calendar 2017の5日目の記事です。4日目は@isariさんの【初心者向け】青空文庫形式からはじめる正規表現でした。
私(@myokoym)が運営しているAozorasearchというWebページを紹介します。ちなみに青空文庫の中の人ではありません。
青空文庫とは
1日目の記事である@_pawa_さんの青空文庫のごく簡単な紹介をご覧ください。
Aozorasearchを作ったきっかけ(青空文庫の検索事情)
青空文庫から作品を探す方法はいくつかあり、大きく分けるとディレクトリ検索と全文検索の2種類に分けることができます。
青空文庫とディレクトリ検索
まず、いわゆるディレクトリ検索1は以下の3通りあります。
- 作家名から探す
- 作品名から探す
- 分野(NDC2)から探す
作家名や作品名、分野のうちのどれかひとつの条件で探したいときは上記が使えます。が、私がやりたかったのは、複数の条件を組み合わせたり、条件で絞り込んだ上で本文を全文検索したりといったことだったので、他のやり方を探すことにしました。
青空文庫と全文検索
青空文庫には全文検索の仕組みも用意されていて、トップページの右上のWeb検索フォームからGoogle等のサイト内検索機能を使った検索ができるようになっています。しかし、青空文庫に特化した検索(分野(NDC)での絞り込みなど)はできません。
サードパーティの検索ツールもいくつかあるようなのですが、自分が思い描いている検索ができるものはなさそうだったので、自分で作ってみることにしました。
Aozorasearchの内部構成
Aozorasearchの内部的な構成を紹介します。込み入った話が多いので、興味があるところだけ読んでください。
Aozorasearchは主に以下の5つで構成されています。
- 青空文庫のデータ(GitHubで管理されているもの)
- 全文検索エンジン兼ストレージ(Groonga)
- コア(コマンドラインで実行できるRubyスクリプト)
- Webアプリケーション(Sinatra)
- 運用サーバー(VPS)
青空文庫のデータ(GitHubで管理されているもの)
青空文庫のデータはGitHubで管理されています。毎日、日本時間の午前7:00に前日分の変更がコミットされるようです。
Aozorasearchでは、上のリポジトリをGitのsubmoduleとして扱っています。(リポジトリのサイズが11GB程度あるので、ダウンロードの際は覚悟してください。)
全文検索エンジン兼ストレージ(Groonga)
全文検索エンジンとしてGroonga3を使っています。全文検索エンジンを使うことで、インデックスによる高速な検索が可能になります4。また、著者や分野(NDC)、仮名遣い等での絞り込みはGroongaのドリルダウン機能によって実現しています。
Groongaはストレージ機能も持っているため、インデックス用のRDB等を別途用意する必要はありません5。
コア(コマンドラインで実行できるRubyスクリプト)
コアはオブジェクト指向スクリプト言語 Rubyで書かれています。
Aozorasearchはデータのロードや検索などのコア機能をWebアプリとは切り離して作成しています。そのため、コマンドラインから実行したり、ライブラリとして使ったりできます。
ソースコードはGitHubで公開しています。(GitHub上のsubmoduleやGemfile.lockはあまり更新していないので古いです。)
主なコマンドは以下です。
- https://github.com/myokoym/aozorasearch/blob/master/bin/aozorasearch
- https://github.com/myokoym/aozorasearch/blob/master/lib/aozorasearch/command.rb
loadコマンド
青空文庫のデータから全文検索用インデックスを作成するコマンドです。
主に以下のLoaderクラスを使っています。作家別作品一覧拡充版(aozorabunko/index_pages/list_person_all_extended_utf8.zip
)のCSVを1行ずつ読んで、本文とメタデータをインデックスに追加します6。
Groongaのテーブル構成は以下のGroongaDatabaseクラスに書かれています。
トークナイザーにはbigramを使っていて、形態素解析は行っていません。(うろ覚えですが、MeCabといった形態素解析用ライブラリや辞書が必要になってインストールのハードルが上がることと、古い文章が多いのであまり正確に解析できないかもしれないことが理由だった気がします。)
一応、タイトルと本文で重み付けを変えています。この辺をもっと調整したほうがよいというご意見があれば教えてください。
searchコマンド
全文検索を行うコマンドです。主に以下のGroongaSearcherクラスを使っています。
なお、Webサーバーで動かすときはコマンドを経由せずに上記のGroongaSearcherを直接使っています。
startコマンド
開発中にローカル環境で動作確認するためWebサーバーを起動するコマンドです。運用時はstartコマンドで起動するWebサーバーではなく、運用サーバーで動いているWebサーバーとWebアプリをRackインターフェイスでつなげて動かしています。
Webアプリケーション(Sinatra)
AozorasearchはWebアプリケーションとしても動作します。Rackのインターフェイスに乗せることを想定して、config.ruを含めています。
Webアプリ部分のコードは以下です。
Webアプリケーションフレームワークは、Railsではなく軽量のSinatraを使っています。Sinatraだけでは少し不便なところもあるので、Padrinoのヘルパー(link_toなど)も使えるようにしてあります7。
運用サーバー(VPS)
http://myokoym.net/ は某メガネっ娘のVPSで運用しています。cronが動いていて、Aozorasearch更新用のジョブは毎朝7:098に走ります。以下はcrontabの内容です。(環境依存のパスは「...」で省略しています。)
9 7 * * * cd .../aozorasearch; git submodule foreach git pull origin master; AOZORASEARCH_HOME=.../.aozorasearch .../ruby -I lib bin/aozorasearch load --diff $(date -d "yesterday" "+\%Y-\%m-\%d") >/dev/null 2>&1; touch tmp/restart.txt; curl http://myokoym.net/aozorasearch/ >/dev/null 2>&1
submodule更新→インデックス更新→Webアプリ再起動予約(touch)→Webアプリ再起動(curl)という流れです。touch後の初回アクセス時に再起動するのですが、初回アクセスが遅くなってしまうのを防ぐためにここでcurlして再起動しています。(一般的なやり方かどうかは不明です。9)
パッケージ管理システムの自動更新の仕組み(yum-cron)を導入しているので、Groonga等のパッケージがリリースされたら(大抵はリリースアナウンスが出る前に)自動で更新されます。
Aozorasearchの使い方
ドキュメントがなくてもなんとなく使えるように作っているつもりです。もしわかりづらい部分があれば、コメントやTwitter等でご質問ください。
FAQ
Q. キーワード検索せずに絞り込みたいんだけど?
A. キーワードを何も入力せずに検索ボタンを押してから絞り込んでください。
Q. 著者で絞り込みたいんだけど?
A. 著者名でページ内検索して、著者名のリンクをクリックして絞り込んでください。
Q. 絞り込みのリンクが見当たらないんだけど?
A. ウィンドウの幅が狭いときは検索結果の下に表示されるようになっています。続けて絞り込みたいときはウィンドウの幅を広げてみてください。
Q. 絞り込みを解除したいんだけど?
A. 「絞り込みを解除」にチェックを入れてから再検索してください。
Q. 複数条件での絞り込み中に1つの条件だけ解除したいんだけど?
A. ~~現在、そのような機能はありません。すべて解除してから再度絞り込んでください10。~~2019年8月に対応しました。「[x]」マークが付いているリンクをクリックすると、絞り込み条件を個別に解除できます。
Q. 絞り込み条件をページタイトルに含めてほしいんだけど?
A. ~~pull requestお待ちしています。~~2018年頃に対応済みです。2019年8月には、著者IDやNDCを、数字ではなく著者名や分類名を使うように改善しました。
Q. デザインがシンプル過ぎるんだけど?
A. 仕様です。開発リソースが足りないので、最低限のコードでどの環境でもそこそこ使いやすくなるように設計しています。
Q. JavaScript 使ってる?
A. ブックマーク機能で使用しています。JavaScriptが無効でもブックマーク以外の検索機能は使用できます。
Q. 更新情報はどこで知ればいいの?
A. Twitterアカウント(@aozorasearch)で不定期につぶやいています。
Aozorasearchの活用例
以下はAozorasearchの活用例です。他にもこんな使い方があるよという例を紹介してくださる方がいらっしゃいましたら、コメントや編集リクエスト等で教えてもらえるとうれしいです。
分野(NDC)で絞り込みつつ全文検索したい
- [「NDC 500 技術・工学」で絞り込み]
(http://myokoym.net/aozorasearch/search?ndc1=500&word=) - 「NDC 914 日本文学 評論 エッセイ 随筆」で絞り込みつつ「東京」で全文検索
ここから更に著者等で絞り込むことも可能です。(著者ごとの作品数が表示されているので絞り込みやすいはずです。)
児童書で絞り込みつつ全文検索したい
分野(NDC)とほぼ同じ手順です。
特定の言葉が使われている作品の著者を一覧で見たい
キーワード検索すると、著者で絞り込むためのリンクが作品数順に一覧表示されます。
特定の年代に生まれた著者の用例がほしい
青空文庫は著作権の保護期間が過ぎて著作権が消滅した作品(少なくとも50年以上前に書かれた作品)が大半なので、その前提でお使いください。また、著作権が切れていない作品もある(図書カードのページの背景がピンク色になっている)ので、利用の際はご注意ください11 12。
おわりに
最後までお読みいただきありがとうございました。それでは引き続き青空文庫 Advent Calendar 2017をお楽しみください。
-
「Yahoo!カテゴリ」みたいなイメージ。ちなみに来年の3月でサービス終了予定とのことです。(日本のヤフーが1996年開始のディレクトリ検索を終了へ、時代の変遷を象徴するニュースだ | TechCrunch Japan) ↩
-
NDCとは、本の分類法の一種です。図書館の本の背中の下の方に貼ってある謎のシールに書かれている3桁の数字と言えばわかるかもしれません。 ↩
-
未来検索ブラジルやクリアコードを中心に開発されている国産のオープンソース全文検索エンジン。ぐるなびなど、全国規模のサービスでも使われています。 ↩
-
Aozorasearchでは使っていませんが、MySQLやPostgreSQLと一緒に使うこともできます。(参考: MySQLとPostgreSQLと日本語全文検索3:MroongaとPGroongaの導入方法例 #mypgft - ククログ(2016-09-29)) ↩
-
NDCの分類番号と分類名の対応はJSONで管理しています(これは著作物ではないと思っています)。小数点以下には対応していませんが、要望があれば対応を検討します。 ↩
-
本来はapp.rbに
register Padrino::Helpers
と書く必要があるのですが、helpers Kaminari::Helpers::SinatraHelpers
の中で同じものがregisterされているので省略しています。(冗長でも書いたほうがわかりやすいかなとは思っています。あれ、requireは両方書いているから統一したほうがいいか……。) 使っているライブラリ(gem)は https://github.com/myokoym/aozorasearch/blob/master/aozorasearch.gemspec で一覧できます。 ↩ -
青空文庫のリポジトリが確実に更新されていそうな適当に決めた時間。 ↩
-
passenger-config restart-app
というコマンドで即再起動できるらしいので、後で検証しようと思います。 ↩ -
るりまサーチのように☓ボタンで解除できるようにすることは検討しています。pull requestお待ちしています。 ↩
-
インデックスはグレーゾーンですが、Googleの検索結果にも表示されているのでいいことにしています。著作権者や青空文庫の中の人から要請があったら削除しようと思っています。(参考: Googleの書籍全文検索サービス「Googleブックス」は著作権違反なのかそうではないのか? - GIGAZINE) ↩