青空文庫のデータ構造について

## はじめに

青空文庫は1997年の開設以来、着々と蔵書を増やし続けています。これを書いている2017年12月18日現在で収録作品数は14503。著者・訳者の数ももうすぐ1000になります。昨今のビッグデータから比べると量的にはそれほどでもないですが、そこに収録されている文学作品は貴重な資産であり、20年間という期間に渡って青空文庫を運営されて来た方々と作品の入力・校正に力を注いできた「工作員」の方々の努力の賜物です。

この蓄積された情報は青空文庫のウェブサイト(http://www.aozora.gr.jp) から配布されています。作品は作品名、作家名それぞれ50音別に分類されていて、HTML形式および青空文庫注記形式というルビ付のテキスト形式でダウンロードできます。非常にシンプルでわかりやすいサイトですが、検索の機能はGoogleを利用したもの以外は提供されていません。また、作品のメタデータ（著者や作品の底本に関する情報）は図書カードという形で表示されますがHTML形式であるため、他のシステムで利用するのは少し難しくなっています。青空文庫の内部では作品や著者の情報はデータベースに格納されているはずですが、残念ながらそれに対するアクセス手段は今のところ提供されていません。

そこで、作品や著者の情報をより簡易な方法で取得する方法を提供したいと考えて作り始めたのが [Pubserver](https://github.com/aozorahack/pubserver)というシステムです。これは、青空文庫の作品・著者情報を取得するためのAPIを提供するサーバーで、現在 www.aozorahack.net 上で試験的に稼働しています。利用方法に関しては、上記のPubserverのリポジトリをご覧ください。

Pubserverの設計や内部構成についてもいずれ書きたいと考えていますが、この投稿ではAPIサーバで配布している青空文庫の情報をどのように準備しているのかを説明したいと思います。

## 元になるデータ
青空文庫は基本的にはHTML形式で情報を配布していると書きましたが、実はそれ以外の形式でも情報を配布しています。それが、[「公開中　作家リスト：全て」](http://www.aozora.gr.jp/index_pages/person_all.html) からダウンロードできるzip圧縮されたCSVファイルです。作品や著者のメタデータがCSV（comma-separated values: 表をカンマで区切られた値で表す）形式で記述されていて、プログラムで扱うのに都合が良くなっています。Pubserverでは、このCSVファイルを[MongoDB](https://www.mongodb.com/)というデータベースに取り込んで利用しています。

配布されているCSVファイルには幾つかバリエーションがありますが、Pubserverでは　[list_person_all_extended_utf8.zip](http://www.aozora.gr.jp/index_pages/list_person_all_extended_utf8.zip) を利用しているので、これについて説明します。

青空文庫の蔵書の情報なのでもちろん中身は日本語なのですが、ファイル名から明らかなように文字コードは[UTF-8](https://ja.wikipedia.org/wiki/UTF-8)です。青空文庫のテキストデータは[Shift_JIS](https://ja.wikipedia.org/wiki/Shift_JIS)なのですが、UTF-8の方がプログラム上での扱いが容易であるためにこのバリエーションを選んでいます。

このファイルを解凍して中身を見ると全体で15458行あることがわかります(2017年12月18日現在)。1行に1エントリで、1行目はヘッダ行なので合計15457エントリあることになります。収録作品数(15403)よりも多いのは、一つの作品で著者・訳者が複数人いる場合には複数エントリになるためです。

ヘッダ行には各コラム(列)にどのような情報が載せられているかが書かれています。これを種別に分類すると以下のようになります。

**作品情報**

- 作品ID
- 作品名
- 作品名読み
- ソート用読み
- 副題
- 副題読み
- 原題
- 初出
- 分類番号
- 文字遣い種別
- 作品著作権フラグ
- 公開日
- 最終更新日
- 図書カードURL

**作家・訳者情報**

- 人物ID
- 姓
- 名
- 姓読み
- 名読み
- 姓読みソート用
- 名読みソート用
- 姓ローマ字
- 名ローマ字
- 役割フラグ
- 生年月日
- 没年月日
- 人物著作権フラグ

**底本情報**

- 底本名1
- 底本出版社名1
- 底本初版発行年1
- 入力に使用した版1
- 校正に使用した版1
- 底本の親本名1
- 底本の親本出版社名1
- 底本の親本初版発行年1
- 底本名2
- 底本出版社名2
- 底本初版発行年2
- 入力に使用した版2
- 校正に使用した版2
- 底本の親本名2
- 底本の親本出版社名2
- 底本の親本初版発行年2

**工作員情報**

- 入力者
- 校正者

**ファイル情報**

- テキストファイルURL
- テキストファイル最終更新日
- テキストファイル符号化方式
- テキストファイル文字集合
- テキストファイル修正回数
- XHTML/HTMLファイルURL
- XHTML/HTMLファイル最終更新日
- XHTML/HTMLファイル符号化方式
- XHTML/HTMLファイル文字集合
- XHTML/HTMLファイル修正回数

## 取り込み側DBの構成
上記でも述べたように取り込み側にはMongoDBを使用している。MongoDBはNoSQLデータベースの一種で、「ドキュメント」と呼ばれるJSONで構造化されたデータをそのまま格納できる。そしてそのドキュメントの集合を「コレクション」と呼び、その単位で検索などが可能になっている。

今回、青空文庫の情報を取り込む上で以下のコレクションを用意した。

- `books` ... 作品の情報
- `persons` ... 著者・訳者の情報
- `workers` ... 工作員の情報

これらに対してそれぞれ、以下のようなデータ構造で情報を格納している。

**作品情報 (books)**

```json
{
    "_id" : ObjectId("5a1ff1ee7f3cb61d37437158"),
    "book_id" : 789,
    "title" : "吾輩は猫である",
    "title_yomi" : "わがはいはねこである",
    "title_sort" : "わかはいはねこてある",
    "first_appearance" : "「ホトトギス」1905（明治38）年1月、2月、4月、6月、7月、10月、1906（明治39）年1月、3月、4月、8月",
    "ndc_code" : "NDC 913",
    "font_kana_type" : "新字新仮名",
    "copyright" : false,
    "release_date" : ISODate("1999-09-21T00:00:00.000Z"),
    "last_modified" : ISODate("2017-10-09T00:00:00.000Z"),
    "card_url" : "http://www.aozora.gr.jp/cards/000148/card789.html",
    "base_book_1" : "夏目漱石全集1",
    "base_book_1_publisher" : "ちくま文庫、筑摩書房",
    "base_book_1_1st_edition" : "1987（昭和62）年9月29日",
    "base_book_1_edition_input" : "1987（昭和62）年9月29日",
    "base_book_1_edition_proofing" : "1994（平成6）年9月30日第4刷",
    "base_book_1_parent" : "筑摩全集類聚版夏目漱石全集",
    "base_book_1_parent_publisher" : "筑摩書房",
    "base_book_1_parent_1st_edition" : "1971（昭和46）年4月～1972（昭和47）年1月",
    "input" : "柴田卓治",
    "proofing" : "田尻幹二、高橋真也、しず、瀬戸さえ子、おのしげひこ、渡部峰子",
    "text_url" : "http://www.aozora.gr.jp/cards/000148/files/789_ruby_5639.zip",
    "text_last_modified" : ISODate("2015-02-03T00:00:00.000Z"),
    "text_encoding" : "ShiftJIS",
    "text_charset" : "JIS X 0208",
    "text_updated" : 14,
    "html_url" : "http://www.aozora.gr.jp/cards/000148/files/789_14547.html",
    "html_last_modified" : ISODate("2015-02-03T00:00:00.000Z"),
    "html_encoding" : "ShiftJIS",
    "html_charset" : "JIS X 0208",
    "html_updated" : 8,
    "authors" : [ 
        {
            "person_id" : 148,
            "last_name" : "夏目",
            "first_name" : "漱石"
        }
    ]
}
```

**著者・訳者情報 (persons)**

```json
{
    "_id" : ObjectId("5a34aea7109fcd5b5977d252"),
    "person_id" : 148,
    "last_name" : "夏目",
    "first_name" : "漱石",
    "last_name_yomi" : "なつめ",
    "first_name_yomi" : "そうせき",
    "last_name_sort" : "なつめ",
    "first_name_sort" : "そうせき",
    "last_name_roman" : "Natsume",
    "first_name_roman" : "Soseki",
    "date_of_birth" : ISODate("1867-02-09T00:00:00.000Z"),
    "date_of_death" : ISODate("1916-12-09T00:00:00.000Z"),
    "author_copyright" : false
}
```

**工作員情報 (persons)**

```json
{
    "_id" : ObjectId("5a21ec107f3cb61d37442012"),
    "id" : 1,
    "name" : "伊藤孝昭"
}
```

`_id`はMongoDBが勝手に割り振るIDで、作品情報、著者・訳者情報は上記のCSVファイルから情報を取り出してDBに格納しています。工作員の情報はCSVには名前しか入っていないので、別途HTMLページから情報をスクレイピングしています。その具体的な方法については次の節で。

## DBへの情報の取り込み

DBへの情報取り込みをするスクリプトはPubserverのリポジトリに一緒に入れていたのですが、今回、書き直しを行い、改めて別のリポジトリにしました ([aozorahack/db_importer](https://github.com/aozorahack/db_importer))。言語はJavaScriptでNode.js上で動かす想定です。

**csv2db.js**
上で述べたCSVファイルをダウンロードし、ZIP解凍し、中身を読んで、JSONの形式に変換したのちにMongoDBに投入します。投入前にDBに格納されている作品の最新release_dateを取ってきて、それよりも新しいものだけを選択的にDB投入する仕掛けになっています。

**pid2db.js**
工作員のID情報は[ここ](http://reception.aozora.gr.jp/widlist.php?page=1&pagerow=-1)のHTMLページにしか載っていないので、これをダウンロード、HTML解析してデータを取り出しています。単純なテーブル構成なので比較的簡単に取り出すことができ、それをjsonの形式に整えてMongoDBに投入しています。

## 最後に
この投稿ではPubserverで利用しているデータベースのデータ構造とその構築方法について説明してみました。Pubserverに関する説明も次に書きたいと思います。