青空文庫のデータでテキストマイニング(の準備)

アドベントカレンダー

ACALLアドベントカレンダー7日目、エンジニアリングマネージャー尾上です。
ACALLではスクラムマスターのようなことをしてみたり、チームビルディングに取り組んだりしています。
最近はめっきりコードに向き合っていないので、久しぶりにコード、もといデータと向き合ってみました。

青空文庫の全データが取れる

知らなかった……“青空文庫”の全データは“GitHub”から一括ダウンロードできる!

少し古いのですが、こんな記事を今更読みまして。

上述のツイートでも触れられているように、大量のテキストを自然言語処理などで
分析して有用な情報を抽出する“テキストマイニング”などには役立ちそう。

いいやん!楽しそう!
ということで、青空文庫の全データを入手してテキストマイニングできるようにしてみました。

そもそも青空文庫って?

青空文庫は、著作権が切れた作品を収蔵しているインターネット上の図書館です。
ブラウザ上では下記のように1冊ずつ表示して読むことができます。




ただ、これをコピペで1冊ずつ集めていくのは大変ですよね。
なので、Githubからまるごと落としてきて、テキストマイニングに使える形にすることをゴールにします。

Githubから落とす

それではさっそく、青空文庫の全データをGithubからcloneしましょう。
ただし、めちゃくちゃ重たいので、普通にcloneすると、ネットワーク接続エラーで失敗したりします。
cloneする際、「–depth 1」をつけて、履歴を落とさないようにすれば多少は軽いので、こちらがオススメです。

#最新のみ取得
git clone --depth 1 https://github.com/aozorabunko/aozorabunko.git

テキストファイルのみ取り出し

リポジトリの中には1冊が下記のような形でテキストファイルになっています。

1つずつファイルを集めるのは大変ですが、ファイル取り出しについては先人がいましたので、
ありがたくコマンドを使わせてもらいます。

【Tips】GitHubで公開されている「青空文庫」の内容をまるごとローカルに保存する方法 | ソフトアンテナブログ
# ルートディレクトリ移動
cd aozorabunko-master
# textフォルダ作成
mdkir text
# 拡張子zipをtextフォルダにコピー
find cards -name '*.zip' -exec cp {} text \;
# textフォルダ移動
cd text
# 解凍
unzip '*.zip'

解凍された内容にはpngなど画像(おそらく挿絵など)も含まれています。
データとして必要なのはテキストファイルのみなので、 解凍されたファイルのうち、
拡張子がtxtのものを別フォルダに移動させます。

mkdir matome
cp *.txt matome

テキストファイルの全ファイルを結合します。
また、元ファイルはShift-JISでやや使いづらいのでUTF-8変換しておきます。

cd matome
find . -name "*.txt" -exec cat {} >>matome.log +
# UTR-8変換
nkf -w --overwrite matome.txt

このときに、まとめ先のファイル名の拡張子をtxtにしていると、再帰的にファイル結合が行われてしまうので
結合処理が終わりません。延々と結合しつづけるため、ディスク容量を食いつぶしてしまいます。
私はこのミスで132Gのテキストファイルを作ってしまい、PCが止まる寸前でした。気をつけましょう。

全て結合すると562.2MBのファイルができあがりました。

DBに入れる

562MBのテキストファイルは扱いやすいとは言えないので、DBに登録します。
ACALLで使われているのはPostgresなので、今回もPostgresを使います。

--生データ用テーブル
CREATE TABLE rawdata(
id serial not null,
data text not null
);

1行を1レコードと登録するようにINSERT文を生成し、INSERTしていきます。
Mac標準で使えるsedコマンドはBSD版sedで使い勝手が悪いので、GNU版sedをインストールして対応します。

#GNU版sedインストール
brew install gnu-sed
exec $SHELL -l
#事前処理としてファイル内のシングルクォートを全角にする
gsed -i "" -e "s/\'/’/g" matome.txt
#SQL作成
gsed -i -r "1i s/^(.*)$/insert into rawdata (data) values ('\1');/g" matome.txt
cp matome.txt matome.sql
#SQL実行
psql -d postgres -U d.onoe -f matome.sql

これで青空文庫の全行がPostgresに入りました。

不要データを削除

ここまできたらあと一息です。
とりあえず全てをDBに入れているので、空行だったり注釈だったり本の付帯情報が入っていて、
テキストマイニングするにはゴミが多い状態になっています。

なので、SQLでガンガン削除していきます。

--データ整備
--先頭の全角スペース詰め。結果が0件になるまでやる
update rawdata set data=REGEXP_REPLACE(data,'^ ','','g') where data ~ '^ ';
--[#〜]で書かれた箇所は青空文庫の注釈なので消しておく
update rawdata set data = REGEXP_REPLACE(data,'[#[^]]*]','','g') where data ~ '[#[^]]*]'

--不要データ
--空行を削除
delete from rawdata where data ~ '^(\r|\n|\r\n)$'
--ハイフン区切りを削除
delete from rawdata where data ~ '^-*(\r|\n|\r\n)$'
--青空文庫共通の注記削除
delete from rawdata where data ~ 'このファイルは、インターネットの図書館、青空文庫(http://www.aozora.gr.jp/)で作られました。入力、校正、制作にあたったのは、ボランティアの皆さんです。'
delete from rawdata where data ~ '【テキスト中に現れる記号について】'
delete from rawdata where data ~ '《》:ルビ'
delete from rawdata where data ~ '|:ルビの付く文字列の始まりを特定する記号'
delete from rawdata where data ~ '[#]:入力者注 主に外字の説明や、傍点の位置の指定'
delete from rawdata where data ~ '青空文庫作成ファイル:'
--各種注記行を削除
delete from rawdata where data ~ '^[#.*]'
delete from rawdata where data ~ '^(例)[#.*]'
delete from rawdata where data ~ '^底本:.*';
delete from rawdata where data ~ '^底本の親本:.*';
delete from rawdata where data ~ '^初出:.*';
delete from rawdata where data ~ '^入力:.*';
delete from rawdata where data ~ '^校正:.*';
delete from rawdata where data ~ '[日刷版](作成|発行|公開|修正)(\r|\n|\r\n)$'
delete from rawdata where data ~ '^(例)'
delete from rawdata where data ~ '/\:二倍の踊り字(「く」を縦に長くしたような形の繰り返し記号)'
--URLは文庫の時代的にありえないので削除
delete from rawdata where data ~ '^http'

数が多いので詳細な説明は割愛しますが、データの中身を見ながら色々調整した結果、
上記を消せば、下記の通りほぼきれいな本文データになりました。


注記などは比較的パターンも分かりやすく判断しやすかったですが、「11月20日発行」とだけ入っている
ような行は、 実際の作品中にも存在している気がして、消すかどうかかなり迷いました。消しましたけど。
もしかすると誤って本文が消されてしまったパターンもあるかもしれませんが、
テキストマイニングに使う場合に重要なのは精度よりデータ量ですし、
まあ1件2件は目をつぶりましょう。(いいのか)

完成とおまけ

改行単位ではありますが、青空文庫の全行データが入りました。
この時点でレコード数として2,013,396レコードあります。
テキストマイニングするにしてはそこそこいいデータ量ではないでしょうか!
蛇足ですが、このデータを改行単位ではなく文章区切りで使いたい場合は、
下記のようなSQLで別テーブルに登録することで実現できます。

--文章のテーブル
create table sentence (
id serial not null,
data text not null
)

--句点、括弧で区切ってINSERT
INSERT INTO sentence(data)
select regexp_split_to_table(data,'[「」。]') from rawdata

--空行が生まれてるので削除しておく
delete from sentence where data ~ '^(\r|\n|\r\n)$';
delete from sentence where data ='';

文章に区切ると5,065,136レコードでした。
こちらもなかなかいいデータ量じゃないかなと思います。
ただ、本文データと一緒に書籍名と年代もわかるような形でデータ格納しておけば、
より分析に使いやすかったのではないかなーと思いました。反省。

最後に

今回はデータを作るだけで時間切れになってしまいました。
こうやってまとめると、あっさりしてるように見えるのですが、 データ量が多いので、
テキストファイルの扱いやSQLを作るところの処理はかなり苦戦してます。
全データを1レコードで登録しようとして、Postgresのメモリ制限にひっかかるなど。(当たり前か)
今回、せっかくデータを作成したので次は色んな分析にチャレンジしてみたいと思います。

実際にACALLのDBに入っているデータはとても貴重なもので、顧客の価値へつながるものが
たくさん含まれています。今はまだまだできていないですが、データ活用の可能性は
広がっているので、興味をお持ちの方は是非ACALLにお越しください!
ちょっと話聞いてみたいなーぐらいでも全然かまいませんので!お気軽にどうぞ!

それでは!

アドベントカレンダー
この記事を書いた人
onoe

2007年よりIT業界入り。マークアップエンジニアからスタートし、サーバーサイドのプログラマ、保守運用チームのマネージャーを経て、2018年まで開発部門の責任者として従事。
2019年にACALL株式会社にジョイン。エンジニアリングマネージャーとしてチームビルディングに絶賛取組中!

onoeをフォローする
ACALL BLOG
タイトルとURLをコピーしました