本の季節感を可視化してみる【D3.js】【Pythonによるスクレイピング】
目次
はじめに
以前の記事で、読書メーターという読書記録サービスからデータを収集して分析するということをやってみました。roomba.hatenablog.com
この記事では、その応用として「本の季節感を可視化」してみます。より具体的には、Pythonによる「読書メーター」のスクレイピングによって「ある本が何月に読まれることが多いか」を調べ、D3.jsというデータ可視化Javascriptライブラリを用いてブラウザ上にグラフを描画します。
これにより、『クリスマス・キャロル』をクリスマスに読む人は多いのか、『桜の森の満開の下』はやっぱり4月に読む人が多いのか、といったことが分かるようになります。つまり「本の季節感を可視化」できるわけですね。
読書メーターのスクレイピング
概要
読書メーターのリンクを以下に貼り付けておきます。
読書メーター - あなたの読書量をグラフで記録・管理
読書メーターでは、本を読み終わったら「読んだ本」として登録し、感想を記したり他のユーザの感想を読んだりすることができるようになっています。
ある特定の本のページを開くと、各ユーザが「読んだ本」に登録した日付を感想とともに見ることができるため、その日付データを集めていきましょう。
Pythonによる実装
実装上のポイント
読書メーター内の本のページは以下のようなURLとなっています。これはログインしなくても開くことができます。
http://bookmeter.com/b/【本のID】
ここで、【本のID】はどうやらISBN-10になっているみたいです。
このURLにアクセスし、HTMLをのぞいてみると、以下のように各ユーザの読了日が記されています。この場合は2015年8月2日に読み終えたユーザーがいるということです。
<span class="value-title" title="2015-08-02"></span>
したがって、PythonのHTMLParserによってこの部分を取り出していけばOKです。
なお、その本を読んだユーザが多い場合、複数ページにまたがって上記の解析を行う必要があります。例えば2ページ目のURLは以下のようになっているので、「次のページ」がなくなるまで順番に解析していきます。
http://bookmeter.com/bl/【本のID】?p=2
ソースコード
以下のように始めます。HTML解析のためのHTMLParserと、読書メーターにアクセスしてHTMLを取得するためのurllib2をimportする必要があります。
#! /usr/bin/python # -*- coding: utf-8 -*- from HTMLParser import HTMLParser import urllib2
HTMLの解析は以下の関数によって行います。
# ある本のHTMLを受け取って、読了日のリストを返す関数 # 引数:HTMLのstring, 返り値:読了した日付(str)のリスト def parse_book(book_html): date_list = []# ここに収める class WordParser(HTMLParser):# HTMLパーサ def __init__(self): HTMLParser.__init__(self) def handle_starttag(self, tag, attrs): if tag == 'span': if len(attrs) == 2 and len(attrs[0]) == 2: if attrs[0][0] == "class" and attrs[0][1] == "value-title": if attrs[1][1][0] == "2": date_list.append(attrs[1][1]) wp = WordParser() wp.feed(book_html) wp.close() return date_list
やっていることは、与えられたhtmlから以下のような日付を見つけてゆき、そのリストを返すという感じです。
<span class="value-title" title="2015-08-02"></span>
本のIDをもとに読書メーターにアクセスし、読まれた日付のリストを返す関数が以下のget_date()です。先ほどのparse_book関数を用いています。
# ある本のIDを受け取って、その本が読まれた日付のリストを返す関数 # 引数:本のID(str), 返り値:日付(str)のリスト def get_date(book_id): date_list = [] # ここに日付を収める count = 1 # その本の感想を表示するページが複数に渡る場合、その数 # HTMLを取得 book_url = "http://bookmeter.com/b/" + book_id response = urllib2.urlopen(book_url) book_html = unicode(response.read(), 'utf-8') date_list += parse_book(book_html)# 読了日リストを取得 # 感想が多数の場合、複数のページに渡ってHTMLを取得・解析する while u">次へ" in book_html: # なぜか2ページ目以降は/b/が/bl/になる book_url = "http://bookmeter.com/bl/" + book_id + "?p=" + str(count+1) response = urllib2.urlopen(book_url) book_html = unicode(response.read(), 'utf-8') date_list += parse_book(book_html)# date_listに追加 count += 1 return date_list
さいごに、main関数は以下のようにします。sample_idのコメントアウトを変えることで対象とする本を変えることができます。もちろんこれ以外のsample_idでも大丈夫です。
# Main関数 if __name__ == '__main__': sample_id = str(4102030093) # クリスマス・キャロルのID(サンプル) # sample_id = str(4101010161) # 二百十日・野分 # sample_id = str(4101010099) # 草枕 # sample_id = str(4061960423) # 桜の森の満開の下 # sample_id = str(4101001014) # 雪国 # (1) 本のIDから日付のリストを取得 date_list = get_date(sample_id) # (2) 月別読了数をcsvファイルに書き出す f = open("date_hist" + sample_id + ".csv", "w") f.write("month,number" + "\n") for i in range(12): f2.write(str(i+1) + "," + str(len(filter(lambda x: int(x[1]) == i+1, [x.split('-') for x in date_list]))) + "\n") f.close()
D3.jsによるグラフの描画
実装
ほとんど以下のサンプルのまんまです。
Donut Chart
csvファイルはWeb上にアップロードしておき、プログラム中ではそのURLを記します。例えば
http://jsrun.it/assets/C/i/T/d/CiTdz
などです。
ソースコードは以下で見ることができます。
本の季節感を可視化する - jsdo.it - Share JavaScript, HTML5 and CSS
結果
以下のように、いくつかの本について円グラフを描画することができました。
http://jsrun.it/roomba/sdMt
順番に見ていきます。
クリスマス・キャロル(新潮文庫)
やはりというか、圧倒的に12月に読む人が多いですね。
おわりに
こんな感じの分析を沢山の本に対して行えば、「12月にオススメの本」みたいな機能が実現できると思います。読書メーターさんがそういう機能を実装してくれないかな…。
プログラムは自由に使用してください。今回のスクレイピング(って言うんですよね?)ではほとんど負荷をかけないと思いますが、アレンジしたプログラムを書いてみるときには短時間に大量のアクセスをしないような配慮が必要です。
ちょっとプログラムの説明が雑になっている気がするので、気軽にコメントなどでご質問ください。
インタラクティブ・データビジュアライゼーション ―D3.jsによるデータの可視化
- 作者: Scott Murray,長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2014/02/19
- メディア: 大型本
- この商品を含むブログ (3件) を見る