青空文庫をNDC等で絞り込みつつ全文検索できるWebサイト「Aozorasearch」の紹介

[青空文庫 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/aozora)の5日目の記事です。4日目は@isariさんの[【初心者向け】青空文庫形式からはじめる正規表現](https://qiita.com/isari/items/b9356ddb731423f6846e)でした。

私（[@myokoym](https://twitter.com/myokoym)）が運営している[Aozorasearch](http://myokoym.net/aozorasearch/)というWebページを紹介します。ちなみに青空文庫の中の人ではありません。

## 青空文庫とは

1日目の記事である@_pawa_さんの[青空文庫のごく簡単な紹介](https://qiita.com/_pawa_/items/78b64c2df497efa42784)をご覧ください。

## Aozorasearchを作ったきっかけ（青空文庫の検索事情）

[青空文庫](http://www.aozora.gr.jp/)から作品を探す方法はいくつかあり、大きく分けるとディレクトリ検索と全文検索の2種類に分けることができます。

### 青空文庫とディレクトリ検索

まず、いわゆるディレクトリ検索[^1]は以下の3通りあります。

[^1]: 「Yahoo!カテゴリ」みたいなイメージ。ちなみに来年の3月でサービス終了予定とのことです。（[日本のヤフーが1996年開始のディレクトリ検索を終了へ、時代の変遷を象徴するニュースだ | TechCrunch Japan](http://jp.techcrunch.com/2017/06/29/yahoo-to-shut-down-directory-search/)）

* 作家名から探す
  * http://www.aozora.gr.jp/index_pages/person_all.html
* 作品名から探す
  * http://www.aozora.gr.jp/index_pages/index_top.html
* 分野（NDC[^2]）から探す
  * http://yozora.main.jp/

[^2]: NDCとは、本の分類法の一種です。図書館の本の背中の下の方に貼ってある謎のシールに書かれている3桁の数字と言えばわかるかもしれません。

作家名や作品名、分野のうちのどれかひとつの条件で探したいときは上記が使えます。が、私がやりたかったのは、複数の条件を組み合わせたり、条件で絞り込んだ上で本文を全文検索したりといったことだったので、他のやり方を探すことにしました。

### 青空文庫と全文検索

青空文庫には全文検索の仕組みも用意されていて、トップページの右上のWeb検索フォームからGoogle等のサイト内検索機能を使った検索ができるようになっています。しかし、青空文庫に特化した検索（分野（NDC）での絞り込みなど）はできません。

サードパーティの検索ツールもいくつかあるようなのですが、自分が思い描いている検索ができるものはなさそうだったので、自分で作ってみることにしました。

## Aozorasearchの内部構成

Aozorasearchの内部的な構成を紹介します。込み入った話が多いので、興味があるところだけ読んでください。

Aozorasearchは主に以下の5つで構成されています。

* 青空文庫のデータ（GitHubで管理されているもの）
* 全文検索エンジン兼ストレージ（[Groonga](http://groonga.org/ja/)）
* コア（コマンドラインで実行できるRubyスクリプト）
* Webアプリケーション（Sinatra）
* 運用サーバー（VPS）

### 青空文庫のデータ（GitHubで管理されているもの）

青空文庫のデータはGitHubで管理されています。毎日、日本時間の午前7:00に前日分の変更がコミットされるようです。

https://github.com/aozorabunko/aozorabunko/

Aozorasearchでは、上のリポジトリをGitのsubmoduleとして扱っています。（リポジトリのサイズが11GB程度あるので、ダウンロードの際は覚悟してください。）

### 全文検索エンジン兼ストレージ（Groonga）

全文検索エンジンとしてGroonga[^3]を使っています。全文検索エンジンを使うことで、インデックスによる高速な検索が可能になります[^4]。また、著者や分野（NDC）、仮名遣い等での絞り込みはGroongaのドリルダウン機能によって実現しています。

[^3]: [未来検索ブラジル](http://razil.jp/)や[クリアコード](https://www.clear-code.com/)を中心に開発されている国産のオープンソース全文検索エンジン。[ぐるなび](https://www.gnavi.co.jp/)など、全国規模のサービスでも使われています。

[^4]: 全文検索の基本的なしくみについては、[拙作のスライド](https://slide.rabbit-shocker.org/authors/myokoym/sapporoonga-20150614-2/)がわかりやすい……かもしれません。

Groongaはストレージ機能も持っているため、インデックス用のRDB等を別途用意する必要はありません[^5]。

[^5]: Aozorasearchでは使っていませんが、MySQLやPostgreSQLと一緒に使うこともできます。（参考: [MySQLとPostgreSQLと日本語全文検索3：MroongaとPGroongaの導入方法例 #mypgft - ククログ(2016-09-29)](http://www.clear-code.com/blog/2016/9/29.html)）

### コア（コマンドラインで実行できるRubyスクリプト）

コアは[オブジェクト指向スクリプト言語 Ruby](https://www.ruby-lang.org/ja/)で書かれています。

Aozorasearchはデータのロードや検索などのコア機能をWebアプリとは切り離して作成しています。そのため、コマンドラインから実行したり、ライブラリとして使ったりできます。

ソースコードはGitHubで公開しています。（GitHub上のsubmoduleやGemfile.lockはあまり更新していないので古いです。）

* https://github.com/myokoym/aozorasearch

主なコマンドは以下です。

* 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]。

[^6]: NDCの分類番号と分類名の対応は[JSONで管理](https://github.com/myokoym/aozorasearch/blob/master/data/ndc-simple.json)しています（これは著作物ではないと思っています）。小数点以下には対応していませんが、要望があれば対応を検討します。

* https://github.com/myokoym/aozorasearch/blob/master/lib/aozorasearch/loader.rb

Groongaのテーブル構成は以下のGroongaDatabaseクラスに書かれています。

* https://github.com/myokoym/aozorasearch/blob/master/lib/aozorasearch/groonga_database.rb

トークナイザーにはbigramを使っていて、形態素解析は行っていません。（うろ覚えですが、MeCabといった形態素解析用ライブラリや辞書が必要になってインストールのハードルが上がることと、古い文章が多いのであまり正確に解析できないかもしれないことが理由だった気がします。）

一応、タイトルと本文で重み付けを変えています。この辺をもっと調整したほうがよいというご意見があれば教えてください。

#### searchコマンド

全文検索を行うコマンドです。主に以下のGroongaSearcherクラスを使っています。

* https://github.com/myokoym/aozorasearch/blob/master/lib/aozorasearch/groonga_searcher.rb

なお、Webサーバーで動かすときはコマンドを経由せずに上記のGroongaSearcherを直接使っています。

#### startコマンド

開発中にローカル環境で動作確認するためWebサーバーを起動するコマンドです。運用時はstartコマンドで起動するWebサーバーではなく、運用サーバーで動いているWebサーバーとWebアプリをRackインターフェイスでつなげて動かしています。

### Webアプリケーション（Sinatra）

AozorasearchはWebアプリケーションとしても動作します。Rackのインターフェイスに乗せることを想定して、config.ruを含めています。

https://github.com/myokoym/aozorasearch/blob/master/config.ru

Webアプリ部分のコードは以下です。

https://github.com/myokoym/aozorasearch/tree/master/lib/aozorasearch/web

Webアプリケーションフレームワークは、Railsではなく軽量のSinatraを使っています。Sinatraだけでは少し不便なところもあるので、[Padrinoのヘルパー](http://padrinorb.com/guides/application-helpers/overview/)（link_toなど）も使えるようにしてあります[^7]。

[^7]: 本来はapp.rbに`register Padrino::Helpers`と書く必要があるのですが、`helpers Kaminari::Helpers::SinatraHelpers`の中で同じものがregisterされているので省略しています。（冗長でも書いたほうがわかりやすいかなとは思っています。あれ、requireは両方書いているから統一したほうがいいか……。） 使っているライブラリ（gem）は https://github.com/myokoym/aozorasearch/blob/master/aozorasearch.gemspec で一覧できます。

### 運用サーバー（VPS）

http://myokoym.net/ は某メガネっ娘のVPSで運用しています。cronが動いていて、Aozorasearch更新用のジョブは毎朝7:09[^8]に走ります。以下はcrontabの内容です。（環境依存のパスは「...」で省略しています。）

[^8]: 青空文庫のリポジトリが確実に更新されていそうな適当に決めた時間。

```
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]）

[^9]: `passenger-config restart-app`というコマンドで即再起動できるらしいので、後で検証しようと思います。

パッケージ管理システムの自動更新の仕組み（yum-cron）を導入しているので、Groonga等のパッケージがリリースされたら（大抵はリリースアナウンスが出る前に）自動で更新されます。

## Aozorasearchの使い方

ドキュメントがなくてもなんとなく使えるように作っているつもりです。もしわかりづらい部分があれば、コメントやTwitter等でご質問ください。

http://myokoym.net/aozorasearch/

### FAQ

#### Q. キーワード検索せずに絞り込みたいんだけど？

A. キーワードを何も入力せずに検索ボタンを押してから絞り込んでください。

#### Q. 著者で絞り込みたいんだけど？

A. 著者名でページ内検索して、著者名のリンクをクリックして絞り込んでください。

#### Q. 絞り込みのリンクが見当たらないんだけど？

A. ウィンドウの幅が狭いときは検索結果の下に表示されるようになっています。続けて絞り込みたいときはウィンドウの幅を広げてみてください。

#### Q. 絞り込みを解除したいんだけど？

A. 「絞り込みを解除」にチェックを入れてから再検索してください。

#### Q. 複数条件での絞り込み中に1つの条件だけ解除したいんだけど？

A. ~~現在、そのような機能はありません。すべて解除してから再度絞り込んでください[^10]。~~2019年8月に対応しました。「[x]」マークが付いているリンクをクリックすると、絞り込み条件を個別に解除できます。

[^10]: [るりまサーチ](https://docs.ruby-lang.org/ja/search/version:2.4.0/class:Array/)のように☓ボタンで解除できるようにすることは検討しています。[pull request](https://github.com/myokoym/aozorasearch/pulls)お待ちしています。

#### Q. 絞り込み条件をページタイトルに含めてほしいんだけど？

A. ~~[pull request](https://github.com/myokoym/aozorasearch/pulls)お待ちしています。~~2018年頃に対応済みです。2019年8月には、著者IDやNDCを、数字ではなく著者名や分類名を使うように改善しました。

#### Q. デザインがシンプル過ぎるんだけど？

A. 仕様です。開発リソースが足りないので、最低限のコードでどの環境でもそこそこ使いやすくなるように設計しています。

#### Q. JavaScript 使ってる？

A. ブックマーク機能で使用しています。JavaScriptが無効でもブックマーク以外の検索機能は使用できます。

#### Q. 更新情報はどこで知ればいいの？

A. Twitterアカウント（[@aozorasearch](https://twitter.com/aozorasearch)）で不定期につぶやいています。

## Aozorasearchの活用例

以下はAozorasearchの活用例です。他にもこんな使い方があるよという例を紹介してくださる方がいらっしゃいましたら、コメントや編集リクエスト等で教えてもらえるとうれしいです。

### 分野（NDC）で絞り込みつつ全文検索したい

* [「NDC 500 技術・工学」で絞り込み]
(http://myokoym.net/aozorasearch/search?ndc1=500&word=)
* [「NDC 914 日本文学 評論 エッセイ 随筆」で絞り込みつつ「東京」で全文検索](http://myokoym.net/aozorasearch/search?word=%E6%9D%B1%E4%BA%AC&ndc1=900&ndc2=910&ndc3=914)

ここから更に著者等で絞り込むことも可能です。（著者ごとの作品数が表示されているので絞り込みやすいはずです。）

### 児童書で絞り込みつつ全文検索したい

分野（NDC）とほぼ同じ手順です。

* [「児童書」で絞り込み](http://myokoym.net/aozorasearch/search?kids=true&word=)
* [「児童書」「新字新仮名」で絞り込みつつ「読書」で全文検索](http://myokoym.net/aozorasearch/search?kids=true&orthography=%E6%96%B0%E5%AD%97%E6%96%B0%E4%BB%AE%E5%90%8D&word=%E8%AA%AD%E6%9B%B8)

### 特定の言葉が使われている作品の著者を一覧で見たい

キーワード検索すると、著者で絞り込むためのリンクが作品数順に一覧表示されます。

* 「[将棋](http://myokoym.net/aozorasearch/search?word=%E5%B0%86%E6%A3%8B)」
* 「[メガネ](http://myokoym.net/aozorasearch/search?word=%E3%83%A1%E3%82%AC%E3%83%8D)」

### 特定の年代に生まれた著者の用例がほしい

* [「1900年代生まれの作家」で絞り込み](http://myokoym.net/aozorasearch/search?age_group=1900&word=)
* [「紀元前7世紀末年代生まれの作家」で絞り込み](http://myokoym.net/aozorasearch/search?age_group=%E7%B4%80%E5%85%83%E5%89%8D7%E4%B8%96%E7%B4%80%E6%9C%AB&word=)

青空文庫は著作権の保護期間が過ぎて著作権が消滅した作品（少なくとも50年以上前に書かれた作品）が大半なので、その前提でお使いください。また、著作権が切れていない作品もある（図書カードのページの背景がピンク色になっている）ので、利用の際はご注意ください[^11] [^12]。

[^11]: 参考: [青空文庫編 青空文庫収録ファイルの取り扱い規準](http://www.aozora.gr.jp/guide/kijyunn.html#midashi120)

[^12]: インデックスはグレーゾーンですが、Googleの検索結果にも表示されているのでいいことにしています。著作権者や青空文庫の中の人から要請があったら削除しようと思っています。（参考: [Googleの書籍全文検索サービス「Googleブックス」は著作権違反なのかそうではないのか？ - GIGAZINE](https://gigazine.net/news/20160212-google-book-copyright-law/)）


## おわりに

最後までお読みいただきありがとうございました。それでは引き続き[青空文庫 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/aozora)をお楽しみください。