[文章生成]スクレイピングで青空文庫からデータを取得してみよう:作って試そう! ディープラーニング工作室(1/2 ページ)
機械学習を使って文章の自動生成を行う準備として、青空文庫から小説のデータを取得して、本文のテキストを1文ずつリストに格納してみましょう。
今回の目的
前回までは画像処理についていろいろと試してきました。今回からは数回に分けて、自然言語処理(Natural Language Processing)について学んでいく予定です。ここ数年、機械学習の世界においてはTransformerやGPT-x、BERTなどなど、さまざまな技術が生み出されて、自然言語処理の分野が活況を呈しています。その適用領域も翻訳、文章の要約、感情分析、チャットボットなどなど、幅広いものです。
そうした中で、取りあえず今回からは青空文庫から著作権の切れた作品を学習データとして、文章生成を行うことを目的として、自然言語処理にまつわるさまざまな要素を学んでいくつもりです。
今回は青空文庫から小説のデータを取得する処理を順を追って見て、それらを関数にまとめることにします。なお、今回のコードはこのノートブックで公開しています。
小説のデータを1つだけ抽出するコード
データの取得にはPython入門でも扱ったBeautiful Soup 4を使います。詳しいことは前掲のリンクを参照していただくとして、まずは梶井基次郎の小説を幾つか取得するコードを完成させていきましょう。
以下は「梶井 基次郎」の著作一覧です。
ここでは例として少し短めの作品である『桜の樹の下には』(新字新仮名)のデータを取り出してみることにしましょう。このリンクをクリックすると、次のようなページが表示されます。
取得可能なファイルの種類には3つありますが、ここでは[ファイル種別]が[XHTMLファイル]となっているファイルを取得することにしました。このリンク(427_19793.html)をクリックすると以下のように作品が表示されます(ZIPファイルをダウンロードした場合にはルビを含んだプレーンテキストを手に入れられますが、筆者の趣味でXHTMLファイルをここでは使うことにしました)。
ここでは作品名や著者名なども表示されていますし、何よりルビが丁寧に振ってあります。が、これらの情報については後ほど取り除いてしまいます。必要なのは、小説の本文テキストだけです。
というわけで、urllib.requestモジュールが提供するurlopen関数を使って、このページの内容を取得して、これをBeautiful Soup 4に入力すれば、さまざまな操作が可能なオブジェクトが手に入ります。
ここまでの処理を実際に行うのが、以下のコードです。
from bs4 import BeautifulSoup
from urllib import request
url = 'https://www.aozora.gr.jp/cards/000074/files/427_19793.html'
response = request.urlopen(url)
soup = BeautifulSoup(response)
response.close()
print(soup)
最後に「print(soup)」という行があるので、このコードを実行すると、取得したWebページの内容(XHTML)が以下のように表示されます。
ご覧の通り、本文テキストだけではなく、さまざまなタグが含まれています。そこで、まずは<div>タグ(class属性が"main_text")となっている部分だけを取り出しましょう。
main_text = soup.find('div', class_='main_text')
print(main_text)
これを実行すると、次のようになります。
これで小説の本文テキストだけが得られましたが、気になるのは山ほど入っているルビ関連のタグです。手作業でこれらを削除していくのは大変ですが、実はfindメソッドで取得した本文テキスト(main_textオブジェクト)はBeautiful Soup 4のTagオブジェクトとなっていて、このオブジェクトにはdecomposeメソッドが用意されています。このメソッドは特定のタグとその内容を削除するのに使えます。そこで、上で取り出したmain_textでルビ関連のタグ(の一部)を削除してみましょう。
tags_to_delete = main_text.find_all(['rp', 'rt'])
for tag in tags_to_delete:
tag.decompose()
print(main_text)
ここでは<rp>タグと<rt>タグの2つだけを削除の対象としています。それ以外はもちろん残ってしまうのですが、<rb>タグの内容は削除してしまっては困るもの(ルビを振る文字そのもの)ですから、これはそういうものだと思いましょう。<ruby>タグも同様で、これを削除してしまうとルビだけではなく、本文テキストの一部まで削除してしまいます。
実行結果は次のようになります。
今述べたように、<ruby>タグと<rb>タグは依然として残っていますし、その他のタグもまだ残っています。これらはどうすればよいでしょう。Beautiful Soup 4のTagオブジェクトには「get_textメソッド」という便利なメソッドがあります。これは人が読めるようなテキストを抜き出すのに使えます(戻り値はBeautiful Soup 4のオブジェクトではなく、単なる文字列です)。実際に使ってみましょう。
main_text = main_text.get_text()
print(main_text)
これを実行した結果は以下の通りです。
うん。キレイになりましたね(見た目は)。ということは、<rp>タグと<rt>タグもこの方法で消してしまえばよかったように思えます。実際に(タグを削除する前のmain_textから)これらのタグもget_textメソッドで削除してみた結果を以下に示します。
タグは消えますが、ルビの情報である「(したい)」などがテキスト中に含まれていることに注目してください。これらは全角かっこ「()」に囲まれたひらがなですから、正規表現を使って「main_text = re.sub('([\u3041-\u309F]+)', '', main_text)」のようなことをすることで削除可能です。が、本文テキストとしてこのような文字の並びが登場する可能性はゼロとはいいきれません。そこで、<rp>タグと<rt>タグという情報を手がかりとして、削除してもよいものを前もって処理しておくことにしました。
ところで、先ほどの文章に「(見た目は)」とあるのに気が付いた方もいらっしゃるかもしれません。見た目とはどういうことでしょう。これはprint関数にmain_textを渡すのではなく、「main_text」とだけセルに入力して、Google Colab上で評価してみると分かります。
「\r」「\n」「\u3000」などの文字がmain_textオブジェクトに埋め込まれているのが分かります(最後の「\u3000」は全角空白文字のコードポイント)。これは文字列のreplaceメソッドを使って削除してしまいましょう。
main_text = main_text.replace('\r', '').replace('\n', '').replace('\u3000', '')
main_text
実行結果は以下の通りです。
最後にエクスクラメーションマーク「!」と句点「。」の直後に改行を含めるようにします。これで1文ごとに改行されるようになります。といっても、そうしたいのではなく、最後にこれをsplitlinesメソッドで個々の文を要素とするリストを作成しておくためです(次回以降の処理で役立つような気がしたのでそうしています)。
import re
main_text = re.sub('([!。])', r'\1\n', main_text) # 。と!で改行
text_list = main_text.splitlines()
print(text_list)
Copyright© Digital Advantage Corp. All Rights Reserved.