【PythonとSQLiteの活用】無料の競走馬データベース作成

【PythonとSQLiteの活用】無料の競走馬データベース作成 プログラミング

この記事では、タダで競走馬データベースを作成する方法を説明します。
無料作成には、PythonとSQLiteの活用が前提となります。

必要なライブラリ

スクリプトを実行するには、以下のライブラリが必要です。

  • requests
  • BeautifulSoup
  • pandas
  • sqlite3

これらのライブラリは、pip installコマンドを使ってインストールできます。
その際、以下の記事が参考となります。

主要な機能

以下が、競走馬データベースを作成するスクリプトになります。

import requests
from bs4 import BeautifulSoup
import re
import math
import pandas as pd
import sqlite3
import time


def insert_or_update_data(df, conn):
    """データフレームの各行をチェックし、データベースに挿入または更新します"""
    cursor = conn.cursor()
    for index, row in df.iterrows():
        cursor.execute('SELECT horse_id FROM horses WHERE horse_id = ?', (row['horse_id'],))
        data = cursor.fetchone()
        if data:
            # レコードが存在する場合、必要に応じて更新処理を行う
            cursor.execute('''
                UPDATE horses SET 
                horse_name=?, gender=?, birth_year=?, stable=?, father=?, mother=?, 
                maternal_grandfather=?, owner=?, breeder=?, total_prize_money_10thou_yen=?
                WHERE horse_id=?
            ''', (row['horse_name'], row['gender'], row['birth_year'], row['stable'], row['father'], row['mother'],
                  row['maternal_grandfather'], row['owner'], row['breeder'], row['total_prize_money_10thou_yen'],
                  row['horse_id']))
        else:
            # レコードが存在しない場合、新たに挿入
            cursor.execute('''
                INSERT INTO horses (horse_id, horse_name, gender, birth_year, stable, father, mother, 
                maternal_grandfather, owner, breeder, total_prize_money_10thou_yen) 
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ''', (row['horse_id'], row['horse_name'], row['gender'], row['birth_year'], row['stable'], row['father'],
                  row['mother'],
                  row['maternal_grandfather'], row['owner'], row['breeder'], row['total_prize_money_10thou_yen']))
    conn.commit()


def get_total_pages(url):
    # requestsを使って、ウェブページを取得する
    response = requests.get(url)

    # WebページのHTMLをパースするためにBeautifulSoupを使う
    soup = BeautifulSoup(response.text, 'html.parser')

    # 'pager'クラスを持つdivを見つける
    pager_div = soup.find('div', class_='pager')

    # divのテキストから数字を抽出するために正規表現を使用する
    numbers = re.findall(r'\d+', pager_div.text.replace(',', ''))  # カンマを削除して数字のみを抽出

    # 数値が見つからない場合、エラーメッセージを表示して処理を終了する
    if not numbers:
        return None  # ここで処理を終了

    # 全件数を取得し、ページ総数を計算する
    total_items = int(numbers[0])  # 「8,804」に相当する数値を整数型に変換
    items_per_page = 20
    total_pages = math.ceil(total_items / items_per_page)  # 1ページあたりのアイテム数で割り、切り上げてページ総数を計算

    return total_pages


def main():
    # 列名を英語に変換するためのマッピング辞書を定義
    column_mapping = {
        '馬ID': 'horse_id',
        '馬名': 'horse_name',
        '性': 'gender',
        '生年': 'birth_year',
        '厩舎': 'stable',
        '父': 'father',
        '母': 'mother',
        '母父': 'maternal_grandfather',
        '馬主': 'owner',
        '生産者': 'breeder',
        '総賞金(万円)': 'total_prize_money_10thou_yen'
    }

    # ページ数を取得
    base_url = 'https://db.netkeiba.com/?pid=horse_list&word=&match=partial_match&sire=&keito=&mare=&bms=&trainer=&owner=&breeder=&under_age=2&over_age=none&prize_min=&prize_max=&sort=prize&list=20&act=1&shozoku%5B1%5D=1&shozoku%5B2%5D=2'
    total_pages = get_total_pages(base_url)

    # 戻り値をチェックする
    if total_pages is None:
        # エラー処理
        print("ページ数を取得できませんでした。URLを確認してください。")
        return

    # DB初期化・接続
    db_name = 'keiba.db'
    conn = connect_database(db_name)
    cursor = conn.cursor()
    create_table(cursor)

    # 各ページにアクセスするためのURLを生成し、アクセスする
    for page_number in range(1, total_pages + 1):
        page_url = f"{base_url}&page={page_number}"
        print(page_url)

        # データフレーム取得
        df = fetch_page_data(page_url)
        # データフレームの列名を英語に変換
        df = df.rename(columns=column_mapping)
        # データ登録
        insert_or_update_data(df, conn)

        time.sleep(1)

    conn.close()


def fetch_page_data(page_url):
    response = requests.get(page_url)
    response.encoding = 'euc-jp'  # エンコーディングをEUC-JPに設定
    soup = BeautifulSoup(response.text, 'html.parser')
    table = soup.find('table', class_='nk_tb_common race_table_01')

    # テーブルヘッダーを抽出
    headers = ['馬ID'] + [th.get_text(strip=True) for th in table.find('tr').find_all('th')]

    # データ行と馬IDを抽出
    rows = []
    for tr in table.find_all('tr')[1:]:  # ヘッダー行をスキップ
        # 馬名のリンクからIDを抽出
        horse_link = tr.find('a', href=True)
        horse_id = horse_link['href'].split('/')[-2] if horse_link else None

        cols = [td.get_text(strip=True) for td in tr.find_all('td')]
        rows.append([horse_id] + cols)  # IDを先頭に追加

    # pandas DataFrameに変換
    df = pd.DataFrame(rows, columns=headers)
    # 列名が空の列を探し、そのインデックスを取得
    empty_columns = [col for col in df.columns if col == '']
    # 空の列名を持つ列を削除
    df.drop(columns=empty_columns, inplace=True)

    return df


def connect_database(filename):
    """データベースに接続(またはデータベースファイルを作成)"""
    return sqlite3.connect(filename)


def create_table(cursor):
    """馬のデータを格納するテーブルを作成"""
    cursor.execute('DROP TABLE IF EXISTS horses')
    cursor.execute('''
        CREATE TABLE horses (
            horse_id TEXT PRIMARY KEY,
            horse_name TEXT,
            gender TEXT,
            birth_year TEXT,
            stable TEXT,
            father TEXT,
            mother TEXT,
            maternal_grandfather TEXT,
            owner TEXT,
            breeder TEXT,
            total_prize_money_10thou_yen REAL
        )
    ''')


if __name__ == '__main__':
    main()

スクリプトには以下の主要な関数があります。

  • get_total_pages(url): 指定されたURLから全ページ数を取得。
  • fetch_page_data(page_url): 指定されたURLから競走馬のデータを取得し、pandasデータフレームに変換。
  • insert_or_update_data(df, conn): pandasデータフレームの各行をチェックし、SQLiteデータベースに挿入または更新。
  • connect_database(filename): SQLiteデータベースに接続。
  • create_table(cursor): 競走馬のデータを格納するテーブルを作成。

データの取得とデータベースへの保存

スクリプトは、最初にbase_urlを使って全ページ数を取得します。
このbase_urlは、netkeibaにおける「競走馬検索」で作成したモノです。

検索時の条件は、以下となります。

  • 現役馬
  • 中央所属(関東・関西)

次に、SQLiteデータベースに接続し、horsesテーブルを作成(または既存のテーブルを削除)となります。

各ページのURLを生成し、fetch_page_data(page_url)を使ってデータを取得します。
取得したデータの列名は、column_mapping辞書を使って英語に変換されます。
日本語のままだと、プログラム上で扱いにくいです。

データは、insert_or_update_data(df, conn)を使ってデータベースに挿入または更新されます。
次のページに移動する前に、time.sleep(1)を使って1秒のウェイトを入れます。

これは、連続的なリクエストによってサーバーに過剰な負荷がかかることを防ぐために重要です。

データベーススキーマ

horsesテーブルには、馬ID、馬名、性別、生年、厩舎、父馬、母馬、母父、馬主、生産者、総賞金(万円)のカラムがあります。

基本的には、netkeiba上の表示結果そのままです。
ただし、「馬ID」だけは別途加えています。

 https://db.netkeiba.com/horse/2012102013/

「2012102013」が、馬IDとなります。
データベースには、以下のように登録されることになります。

まとめ

このスクリプトを使うことで、netkeiba.comから競走馬のデータを効率的に取得し、SQLiteデータベースに保存することができます。保存されたデータは、競馬の分析や予測モデルの構築などに活用できます。

スクレイピングを行う際は、サーバーへの負荷を考慮したスクレイピングを心がけることが重要です。
なお、スクレイピングに関しては国も普通にやっています。

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