5年位前から読書メーターで読みたい本と読み終わった本と感想の管理をしていたのだが、だんだん非公開にできないのが不満になってきた。これは読書メーターが読書好きが集まるSNS(率直にいってXXXXXだ)を標榜しており5年前の私がそれに気が付かなかったから仕方がないことなのだが、読む本もそこに書く感想も割と赤裸々なものがあり、一切非公開にできないのは、ちょっと。 思想の自由を守れ。
耐えきれなくなったので1年前にブクログに移行した。その際、読みたい本一覧についてはどうしようもないので選別も兼ねて手動でデータを移行した。これは読書メーターがXXXXXなサービスでインポートサービスがあるにも関わらずエクスポートサービスがないから。ちなみにブクログはどちらもある。
150冊くらいデータがある読了本(シリーズは最初の一冊しか登録してなかったり職の本を端折ったりしているから実際は多分5年でもうちょい読んでますよ:読書する人間的自意識主張)に関しては、流石に手作業で移行するのは面倒でそのまま置いておいた。スクレイピングとやらができれば抽出できると知ったけど、Rのその辺りはよくわからなかったので諦めた。1年前の私に解説すると、スクレイピングとはそのパソコンに命令して君の代わりにネット上の情報収集をやらせることでありこれをすると例えば昔書いたスコアアタックモデルのデータ入力パートが省略できるようになったりするよ。
そういう状況だったのだが春に転職してあれがそっち系になったので、pythonの練習をかねてコードを書いた。これでXXXXXサービスとはおさらば。ヨッシャッ!
まず読書メーターのどのページから情報を拾ってくるか決める。一年程前に書かれた記事によると、当時は読んだ本まとめみたいなコードが生成できたらしいのだが、読書メーターの仕様変更で一切できなくなっていた。すごいな。こんなに早く一方的に情報が無価値になることがあるのか。
ログインしたあとの読書管理ページのリストから拾うことにした。これは若干失敗で、読了本とそれ以外(未読本とか)のページの書式が違ったので、読了本にしか使えないコードになっちゃった。まぁいっか……。
#ログイン
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from lxml import html
import time
chrome_options = webdriver.ChromeOptions()
driver = webdriver.Chrome(options=chrome_options)
driver.get(“https://bookmeter.com/login”)
# ID/PASSを入力
id = driver.find_element_by_id(“session_email_address”)
id.send_keys(“YYYYY”)
password = driver.find_element_by_id(“session_password”)
password.send_keys(“ZZZZZ”)
time.sleep(1)
# ログインボタンをクリック
login_button = driver.find_element_by_name(“button”)
login_button.click()
できた~。うれし~。
ブクログにインポートする時に必要なデータを調べる。ISBN(13桁)が読書メーター側のリストに入ってなかったので面倒だなと思ったけど、これは必ずしも必要ないことがわかった。最低限必要なのはASIN(カンフージェネレーション)だけみたいだ。あとはレビューと読了日付を引っ張ってきたいな。
#読了日付リスト
date_list = []
for i in range(1,9):
url = “https://bookmeter.com/users/WWWWW/books/read?display_type=list&page={}”.format(i)
driver.get(url)
date = driver.find_elements_by_class_name(“detail__date”)
for i in date:
date_list.append(i.text)
time.sleep(1)
読了日付はユニークなクラス名があるので難しくなかった。
#ASIN関数
import re
def extract_asin(string):
asin = re.search(pattern=r”(?<=asin\”:\”).+?(?=\”)”, string=string)
return asin.group()
#review関数
def extract_rev(string):
review = re.search(pattern=r”(?<=\”review\”:{\”text\”:\”).+?(?=\”)”,
string=string)
if(review is None):
return “”
else:
return review.group()
#ASINとレビューのリスト
asin_list = []
rev_list = []
for i in range(1,9):
url = “https://bookmeter.com/users/WWWWW/books/read?display_type=list&page={}”.format(i)
driver.get(url)
#モーダルウィンドウ
content = driver.find_element_by_class_name(“detail__edit”)
content2 = content.find_element_by_xpath(“div”)
string = content2.get_attribute(“data-modal”)
content_list = driver.find_elements_by_class_name(“detail__edit”)
for i in range(len(content_list)):
content2 = content_list[i].find_element_by_xpath(“div”)
string = string = content2.get_attribute(“data-modal”)
asin_list.append(extract_asin(string))
rev_list.append(extract_rev(string))
time.sleep(1)
こっちが面倒だった……。モーダルウィンドウとかいうページ上で立ち上がるウィンドウがあるのだが、その要素の指定の仕方がわからなかった。クラスで名指ししようとしてもエラーが出るし……。結局モーダルウィンドウの手前のdivを指定してから、階層を一つ下げてパスで指定した。なんでこれでうまくいくのか一切わからん。詳しい人がいたら教えて下さい。
モーダルウィンドウの中身の取り出し方も最初わからなかった。多分こっちはget_attributeであっていると思う。出てくる文章は200文字くらいのタグ等含む文字列なのであとは正規表現で取り出し。正規表現もよくわからね~。宇宙人の言語か?見た目が記号の乱舞になるので本当に宇宙人の言語みたいに見える。正規表現って100年くらい歴史があるらしいけど、作った人絶対面白がってやったでしょ。
import pandas as pd
import csv
date_list2 = list(map(lambda x: str(pd.to_datetime(x, format=”%Y/%m/%d”)), date_list))
df = pd.DataFrame({“serviceID”: “1”,
“itemID”:asin_list,
“ISBN”:””,
“category”:”-“,
“eval”:””,
“statement”:”読み終わった”,
“review”:rev_list,
“tag”:”読メ”,
“memo”:””,
“regist_date”:date_list2,
“finish_date”:date_list2})
df.to_csv(“out.txt”, encoding=”cp932″, sep=”,”, index=False,
header=False, quoting=csv.QUOTE_ALL)
あとはブクログの形式にあわせてデータフレームで加工。encordingはさくっといったけどquotingとダブルクオーテーション挿入の仕様がいまいちわからずしばし苦戦。
できた~!丸一日かかった~!面白かったからまぁいいか~。