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