この記事では、タダで競走馬データベースを作成する方法を説明します。
無料作成には、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データベースに保存することができます。保存されたデータは、競馬の分析や予測モデルの構築などに活用できます。
スクレイピングを行う際は、サーバーへの負荷を考慮したスクレイピングを心がけることが重要です。
なお、スクレイピングに関しては国も普通にやっています。