[Web小説を管理するアプリ] No. 03-02 : ハーメルンのブックマークを抽出する

本記事は次の連載の一部です。

No. 03-02では、ハーメルンに登録されているユーザーのブックマーク情報を抽出し、小説のリストを生成します。

準備

ディレクトリ構成は次のとおりです。

.
├── .devcontainer
├── scraping_modules
│   └── get_hameln_bookmarks.py
└── secret_info.json

また、secret_info.jsonに小説家にハーメルンのユーザー情報を入力してください(hameln)

{
    "plattform_users":{
        "narou":{
            "id":"xxx",
            "pass":"xxx"
        },
        "hameln":{
            "id":"xxx",
            "pass":"xxx"
        },
        "kakuyomu":{
            "id":"xxx",
            "pass":"xxx"
        }
    }

}

プログラム

get_hameln_bookmarks.pyを幾つかのブロックで解説します。

ハーメルンの小説情報は、なろうと同じ様に静的なHTMLなので、小説情報に書かれていることはBeautifulsoupで取得します。

また、ブックマークのデータ(栞の話数)はログインする必要があるため、Seleniumで取得します。

import

importはseleniumの使うものと、json、re、datetime、localeを入れました。pprintは表示確認用です

from selenium import webdriver
from selenium.webdriver.common.by import By
import json
from datetime import datetime
import locale
locale.setlocale(locale.LC_TIME, 'ja_JP.UTF-8')
from pprint import pprint
from bs4 import BeautifulSoup
import requests
import time

main

このプログラムは他のプログラムのモジュールとして利用します。そのため、mainは動作確認用となっています。

mainでは、secret_infoを読み込んで、get_bookmarksを呼び出すだけです。

if __name__ == '__main__':
    with open('../secret_info.json', 'r') as f:
        secret_info = json.load(f)
    get_bookmarks(secret_info)

get_bookmarks

get_bookmarksの流れは次のとおりです。

  1. ハーメルンにログインする
  2. お気に入りリストのページへ行く
  3. 小説リストを抽出する(次のページがあったら、それも抽出する)
  4. 全部の小説リストを作成する
def get_bookmarks(secret_info):
    with webdriver.Remote(
        command_executor="http://chrome:4444/wd/hub",
        options=webdriver.ChromeOptions()
    ) as browser:
        browser.implicitly_wait(10)
        browser.get('https://syosetu.org/')

        login_page_button = browser.find_element(by=By.XPATH, value="//input[@value='ログインページへ']")
        login_page_button.click()

        user_info = secret_info['plattform_users']['hameln']

        input_id_elem = browser.find_element(by=By.NAME, value='id')
        input_id_elem.send_keys(user_info['id'])

        input_pass_elem = browser.find_element(by=By.NAME, value='pass')
        input_pass_elem.send_keys(user_info['pass'])

        login_button = browser.find_element(by=By.XPATH, value="//input[@value='ログイン']")
        login_button.submit()

        login_state = browser.find_element(by=By.CLASS_NAME, value='spotlight').text
        print(f'login state : {login_state}')

        browser.get('https://syosetu.org/?mode=favo')

        novel_list = get_list(browser)
        print(f'total novel : {len(novel_list)}')
        return novel_list

ハーメルンのブックマークは、なろうと違ってカテゴリーに分かれていないので、抽出手順は少し楽になります。

また、ここではget_list(browser)という関数を呼び出します。これは小説の情報リストを作り、返す関数です。

get_list

get_listは、表示されているブラウザの小説リストを整形してリストにいれる関数です。

また、next_pageのボタンが押せる場合は押して、ループします。

ループのブレイク条件は、同じ小説をリストに入れた場合です。

def get_list(browser):
    ret = []
    check = set()
    looping = True
    while looping:
        bookmark_items = browser.find_elements(by=By.CLASS_NAME, value='section3')
        for item in bookmark_items:
            novel_elem = item.find_element(by=By.TAG_NAME, value='h3')
            a_list = novel_elem.find_elements(by=By.TAG_NAME, value='a')
            url = a_list[0].get_attribute('href')
            info = get_novel_info(url)
            if info != {}:
                p_list = item.find_elements(by=By.TAG_NAME, value='p')

                ep_and_date_info_list = p_list[1].text.split(' ')
                ep_text = ep_and_date_info_list[0].replace('話', '')
                if '/' in ep_text:
                    siori_ep, least_ep = ep_text.split('/')
                    info['siori_ep'] = siori_ep.replace('栞', '')
                if info['url'] in check:
                    looping = False
                    break
                check.add(info['url'])
                ret.append(info)
                print(info)
                time.sleep(1)

        paging_elem = browser.find_element(by=By.CLASS_NAME, value='paging')
        li_list = paging_elem.find_elements(by=By.TAG_NAME, value='li')
        next_button = li_list[-2]
        next_button.click()
        print('click next page')
    return ret

小説の情報はget_novel_info(url)によって取得できます。また、栞の情報をブックマークから取得して、付け足します。

get_novel_info

get_novel_infoでは、urlから小説情報を抽出します。

ハーメルンの小説情報も静的なHTMLなので、Beautifulsoupを使いました。また、urlから小説IDを抜き出して、小説情報のページへアクセスしました。これは、小説情報urlと小説目次のurlの両方に対応できるようにしたかったためです。

def get_novel_info(url):
    novel_id = url.split('/')[-1]
    if novel_id == '':
        novel_id = url.split('/')[-2]
    print(novel_id)
    detail_url = f'https://syosetu.org/?mode=ss_detail&nid={novel_id}'
    headers = requests.utils.default_headers()
    headers.update({'User-Agent': 'My User Agent 1.0'})
    response = requests.get(detail_url, headers=headers)
    soup = BeautifulSoup(response.content, 'html.parser')
    print(detail_url)
    tables = soup.find('div', class_='section normal')
    if tables == None:
        return {}
    td_list = tables.find_all('td')

    info = {
        'url':f'https://syosetu.org/novel/{novel_id}/',
        'site_type':'hameln',
        'site_id':novel_id,
        'state':None,
        'title':td_list[1].text.strip(),

        'auther_name':td_list[7].text.strip(),
        'auther_url':None,

        'novel_update_date':datetime.strptime(td_list[21].text.strip(), '%Y年%m月%d日(%a) %H:%M'),
        'siori_ep':None,
        'least_ep':int(td_list[17].text.split(' ')[1][:-1]),
        
        'summary':td_list[9].text.strip(),
        'series':None,
        'series_url':None,
        'tags':None,
        'genre':td_list[5].text.strip(),

        'novel_init_date':datetime.strptime(td_list[15].text.strip(), '%Y年%m月%d日(%a) %H:%M'),
        'string_num':int(td_list[35].text.strip().replace(',', '')[:-2]),
        'thoughts_num':int(td_list[31].text.strip().replace(',', '')[:-1]),
        'review_num':None,
        'bookmark_num':int(td_list[25].text.strip().replace(',', '')[:-1]),
        'overall_eval_point':None,
        'eval_point':None
    }

    state = td_list[17].text.split(' ')[0]
    if '短編' in state:
        info['state'] = '短編'
    elif '完結' in state:
        info['state'] = '完結済'
    else:
        info['state'] = '連載中'
    
    if td_list[7].find('a') != None:
        info['auther_url'] = td_list[7].find('a').get('href')

    info['tags'] = td_list[11].text.strip().split(' ') + td_list[13].text.strip().split(' ')

    if td_list[37].text.strip() != '':
        info['overall_eval_point'] = int(td_list[37].text.strip().replace(',', '')[:-2]),

    return info

なろうと違ってハーメルンの小説情報の表データの形状は常に同じなので、if分を使わなくてもよくなりました。

小説の状態を短編、完結済、連載中の三択へと変更します。また、タグは必須タグと普通のタグに分かれているので、これも結合しておきます。

実行

実行してみます。main文は、同じディレクトリで実行されることを想定しているので、ディレクトリを移動します。

cd scraping_modules
python get_hameln_bookmarks.py

実行すると、ログインが正しくされていれば、ログインステータスが表示されます。

その後、ブックマークの読み込みが始まります。

login state : ログイン中

...
{'state': None, 'title': '神の装飾品店', 'url': 'https://syosetu.org/novel/336063/', 'auther': 'Actueater', 'auther_url': None, 'update_date': datetime.datetime(2024, 6, 10, 12, 0), 'siori_ep': '11', 'least_ep': '80'}
{'state': None, 'title': '【書籍化決定】TSクソビッチ少女は寝取られたい', 'url': 'https://syosetu.org/novel/291727/', 'auther': '二本目海老天マン', 'auther_url': 'https://syosetu.org/user/304423/', 'update_date': datetime.datetime(2024, 6, 10, 7, 1), 'siori_ep': None, 'least_ep': '136'}
{'state': None, 'title': 'ディストピアゲーに転生したら行政側だった件について', 'url': 'https://syosetu.org/novel/283626/', 'auther': '我等の優雅なりし様を見るや?', 'auther_url': 'https://syosetu.org/user/366126/', 'update_date': datetime.datetime(2024, 6, 10, 4, 27), 'siori_ep': None, 'least_ep': '33'}
...

すべてのブックマークが読み込み終わると、全ブックマーク数が表示されます。

total novel : 224

最終的には、この辞書のリストが今回得られます。

あとは、各プラットフォームの表記を合わせてからデータベースへと挿入します。


タイトルとURLをコピーしました