読書メーターをPythonでエクスポート 【スクレイピング】

2020/05/18

【Python】 作ってみた

t f B! P L

きっかけ


自分が持っている書籍の管理として、読書メーターを使いはじめました。
(といっても、ただバーコードで読み込むのが使いたかっただけ)

個人的には、いろいろ項目をカスタマイズしたいため、最終的にGoogleスプレッドシートなりで管理しようとしています。
しかし、公式の機能で書籍情報のエクスポートはできない。
(フォームをのぞいていると、だいぶ昔からエクスポートの要望はあるそうだけど、一向に実装されないそうな)


そんな困ったときには、Pythonが使えるのでスクレイピングを用いて、最終的にCSVとして出力するコードです。

全体のコード

イメージ


手書きですが、操作のイメージを描いてみました。


import requests
import bs4
import time
import pandas as pd

# JSver
from selenium import webdriver
import chromedriver_binary
from selenium.webdriver.chrome.options import Options

# urlからHTMLの中身を取得
def get_soup_url(url):
    res = requests.get(url)
    html = res.text
    soup = bs4.BeautifulSoup(html, 'lxml')
    return soup

# urlからHTMLの中身を取得 JSver
def get_soup_url_js(url):
    options = Options()
    options.set_headless(True)  # Headlessモード
    driver = webdriver.Chrome(chrome_options=options)
    driver.get(one_url)
    html = driver.page_source.encode('utf-8')
    soup = bs4.BeautifulSoup(html, "html.parser")
    return soup

# soupから各種要素をリストに追加
def append_list_element(soup, list_add):
    for book in soup.select('div.detail__title'):
        one_url = "https://bookmeter.com" + book.a.get("href")
        # soup_one = get_soup_url(one_url)
        soup_js = get_soup_url_js(one_url)
        # 要素を抜き出す
        title = soup_js.select('div.header__inner')[0].h1.text
        author = soup_js.select('div.header__inner')[0].ul.text
        page = soup_js.find_all(class_="bm-details-side__item")[-1].text
        amazon_url = soup_js.select_one("li._3jlUehstt7-3W8HFJMgE94._1HTll1phkIA_FlA27ZN9ZM").a["href"]
        img_link = soup_js.select_one('div.group__image').img["src"]

        # リストに追加
        list_add.append([title, author, page, amazon_url, img_link])
        time.sleep(3)

# 次のリンクを見つける
# soupは現在のページのHTML
def get_next_link(soup):
    for i in soup.select("a.bm-pagination__link"):
        if "次" in i:
            next_ = i["href"]
        else:
            pass
    return "https://bookmeter.com" + next_

# 最初のURL
soup = get_soup_url("")
list_element = []
# 1回目
append_list_element(soup, list_element)

# 2回目以降
while True:
    try:
        next_link = get_next_link(soup)
    except UnboundLocalError:
        print("Done!")
        break
    soup = get_soup_url(next_link)
    append_list_element(soup, list_element)

# 保存
df = pd.DataFrame(list_element, columns=["title", "author", "page", "amazon_url", "img_link"])
df.to_csv("path")

早速全体のコードです。


注意点としては、

  • HTMLの中身が変わる可能性もある
  • 対象としているページは、 「積読本」(stacked)です。
    他のフォルダもタグが同じだったら、上手く動くと思います。
    (確認できていません🙏)
  • スクレイピングの際は、サーバーに負荷を掛けないように待機時間を設けましょう

  • 以下、詳しく中身を見ていきます。

    コードの説明

    流れ

  • 対象のアカウントのStockedのリンクを持ってくる
  • Topページから各本のリンクを取得
  • 各本のリンクに遷移し、ほしい要素をとってくる
  • 次のページがあるかどうか確認する
  • あれば繰り返す

  • 関数の説明

    get_soup_url(url)

    urlからHTMLの中身を取得

    def get_soup_url(url):
        res = requests.get(url)
        html = res.text
        soup = bs4.BeautifulSoup(html, 'lxml')
        return soup
    

    get_soup_url_js(url)

    urlからHTMLの中身を取得 JSver

    def get_soup_url_js(url):
        options = Options()
        options.set_headless(True)  # Headlessモード
        driver = webdriver.Chrome(chrome_options=options)
        driver.get(one_url)
        html = driver.page_source.encode('utf-8')
        soup = BeautifulSoup(html, "html.parser")
        return soup
    

    append_list_element(soup, list_add)

    soupから各種要素をリストに追加

    def append_list_element(soup, list_add):
        for book in soup.select('div.detail__title'):
            one_url = "https://bookmeter.com" + book.a.get("href")
            # soup_one = get_soup_url(one_url)
            soup_js = get_soup_url_js(one_url)
            # 要素を抜き出す
            title = soup_js.select('div.header__inner')[0].h1.text
            author = soup_js.select('div.header__inner')[0].ul.text
            page = soup_js.find_all(class_="bm-details-side__item")[-1].text
            amazon_url = soup_js.select_one("li._3jlUehstt7-3W8HFJMgE94._1HTll1phkIA_FlA27ZN9ZM").a["href"]
            img_link = soup_js.select_one('div.group__image').img["src"]
    
            # リストに追加
            list_add.append([title, author, page, amazon_url, img_link])
            time.sleep(3)
    

    get_next_link

    次のリンクを見つける

    def get_next_link(soup):
        for i in soup.select("a.bm-pagination__link"):
            if "次" in i:
                next_ = i["href"]
            else:
                pass
        return "https://bookmeter.com" + next_
    

    その他のポイント

    ChromeのWebdriverのinstall

    参考:[selenium向け] ChromeDriverをpipでインストールする方法(パス通し不要、バージョン指定可能) - Qiita
    JavaScript込みのページのスクレイピングに対応するためには、Seleniumを使用します。


    Webdriverのinstallの手順は、

    • chromeのversionの確認 81.0.4044.138

    • install
    pip install chromedriver-binary==81.0.4044.138
    

    Topページから、各本のページに移る


    各本の個別のページはこのようになっており、 ここから、


  • タイトル
  • 著者
  • ページ数
  • Amazonリンク
  • 画像のリンク
  • を抜き出します。

    append_list_element(soup, list_add)が対応しています。

    pandasでCSVを書き出す


    df = pd.DataFrame(list_element, columns=["title", "author", "page", "amazon_url", "img_link"])
    df.to_csv("path")
    

    抜き出した情報から、CSVを作成します。

    参考


    記事の感想をリアクションでお願いします!

    QooQ