競馬データのスクレイピング
netkeibaから競馬データをスクレイピングする方法についてまとめます。既に同様の記事は世にたくさん出回ってますが、少し改良して効率化してみました。
1.既出のスクレイピング方法の問題点
競馬データのスクレイピングで検索すると、以下のようなソースコードが多いと思います。
for i in range(1, 11): for j in range(1, 7): for k in range(1, 13): for l in range(1, 13): Base = "https://race.netkeiba.com/race/result.html?race_id=" race_id = "2019" + str(i).zfill(2) + str(j).zfill(2) + str(k).zfill(2) + str(l).zfill(2) url = Base + race_id '''------------------''' '''スクレイピング処理''' '''------------------'''
上記はrace_idを総当たりスクレイピングする方法ですが、残念ながら実際にはrace_idは飛び飛びで、存在しないrace_idがたくさんあります。よって無駄な検索をしていることになります。スクレイピング中にsleep処理を入れると思いますが、無駄な検索をするたびにsleepが入って、全体の処理時間が長引きます。
2.どうやって効率化するか
netkeibaには1日毎の開催レース一覧をまとめている以下のようなページがあります。
レース一覧 | 2023年5月20日 レース情報(JRA) - netkeiba.com
このページからrace_idの一覧を取得することで、実在するrace_idのリストを作成することができます。ここまで来たら後はあとはrace_idをキーにスクレイピングするだけです。
2.1.開催レース一覧ページからrace_idを取得する方法
開催レース一覧のページは動的にrace_idを設定しているようなので、beautifulsoupは使えません。こういうときはseleniumが効果的です。 以下のような実装でrace_idの一覧を取得できます。
import time from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By #selenium driverでブラウザ起動 def get_selenium_driver(url): options = Options() # ヘッドレスモードで実行する場合 options.add_argument("--headless") driver = webdriver.Chrome(options=options) # 取得先URLにアクセス driver.get(url) # コンテンツが描画されるまで待機 time.sleep(5) return driver #指定した開催日に開催されるレースのrace_id一覧を取得 def get_race_ids(kaisai_dates): race_ids = [] for kaisai_date in kaisai_dates: url = 'https://race.netkeiba.com/top/race_list.html?kaisai_date=' + kaisai_date driver = get_selenium_driver(url) elements = driver.find_elements(By.CLASS_NAME,"MyRace_List_Item") for element in elements: race_id = str(element.get_attribute('id')) race_id = race_id.replace("myrace_","") race_ids.append(race_id) return race_ids race_ids = get_race_ids(["20230528"])
上記で2023年5月28日に開催された全レースの全race_idをリストとして取得できます。
2.2.競馬のレース結果のスクレイピングする方法
これは他にサイトがたくさんあるので、今更紹介する必要はないと思いますが、一応まとめておきます。
from bs4 import BeautifulSoup import requests import pandas as pd def fetch_horse_datas(race_id, horse_datas): Base="https://race.netkeiba.com/race/result.html?race_id=" url = Base + race_id kaisai_year = int(race_id[:4]) request = requests.get(url) soup = BeautifulSoup(request.content, 'html.parser') horses = soup.find_all(class_='HorseList') data1 = soup.find(class_='RaceData01') data1 = data1.get_text().replace("/","").split() data2 = soup.find(class_='RaceData02') data2 = data2.select('span') data2 = [t.get_text(strip=True) for t in data2] data3 = soup.find(class_='Refundlink') data3 = str(data3) for horse in horses: horse_data = {} horse_data['start_time'] = data1[0] horse_data['race_type'] = data1[1] horse_data['ground'] = data1[2] horse_data['weather'] = data1[3] horse_data['gr_condition'] = data1[4] horse_data['place'] = data2[1] horse_data['rule'] = data2[3] horse_data['grade'] = data2[4] horse_data['sex'] = data2[5] horse_data['rule6'] = data2[6] horse_data['num_horse'] = data2[7] horse_data['year'] = kaisai_year horse_data['date'] = data3[data3.find("kaisai_date=")+12 : data3.find("kaisai_date=")+20] horse_data['race_id'] = race_id horse_data['rank'] = horse.find(class_='Rank').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['horse_number'] = horse.find(class_='Txt_C').find('div').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['horse_name'] = horse.find(class_='Horse_Name').find('a').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['seirei'] = horse.find(class_='Lgt_Txt Txt_C').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['jockey_weight'] = horse.find(class_='JockeyWeight').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['jockey_name'] = horse.find(class_='Jockey').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['race_time'] = horse.find(class_='RaceTime').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['popularity'] = horse.find(class_='OddsPeople').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['odds'] = horse.find(class_='Txt_R').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['passage'] = horse.find(class_='PassageRate').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['trainer'] = horse.find(class_='Trainer').find('a').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['weight'] = horse.find(class_='Weight').get_text().replace('\n','').replace(' ','').replace(' ','') horse_datas.append(horse_data) return horse_datas # レースデータをスクレイピング horse_datas = [] race_ids = get_race_ids(["20230528"]) for race_id in race_ids: try: # 出馬表を読み取る horse_datas = fetch_horse_datas(race_id, horse_datas) time.sleep(1.5) except Exception as e: print("error in race_id:",race_id,e) pass df = pd.DataFrame(horse_datas)
上記で2023年5月28日の全レース結果をスクレイピングできます。
3.ソースコード一式
以下にソースコード一式をまとめます。 以下では取得したい「年」を指定することで、その年の全レースデータ結果を取得できるようにしてます。
from bs4 import BeautifulSoup import requests import pandas as pd pd.set_option('display.max_columns', 150) pd.set_option('display.max_rows', 500) from tqdm import tqdm import time import datetime from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By # 今日の日付を取得 today = datetime.date.today() # 読み取り開始年、終了年 st_year = 2015 ed_year = today.year # 土日リストを取得 def get_weekends(): weekends = [] for target_year in range(st_year, ed_year+1): baseDate = datetime.date(target_year, 1, 1) days = (baseDate - datetime.date(target_year - 1, 1, 1)).days weekends.extend([(baseDate + datetime.timedelta(i)).strftime("%Y%m%d") for i in range(0, days) if (baseDate + datetime.timedelta(i)).weekday() >= 5]) return weekends def get_kaisai_dates(): weekends = get_weekends() return [weekend for weekend in weekends if (weekend<today.strftime("%Y%m%d"))] #selenium driverでブラウザ起動 def get_selenium_driver(url): options = Options() # ヘッドレスモードで実行する場合 options.add_argument("--headless") driver = webdriver.Chrome(options=options) # 取得先URLにアクセス driver.get(url) # コンテンツが描画されるまで待機 time.sleep(5) return driver #指定した開催日に開催されるレースのrace_id一覧を取得 def get_race_ids(kaisai_dates): race_ids = [] for kaisai_date in tqdm(kaisai_dates): url = 'https://race.netkeiba.com/top/race_list.html?kaisai_date=' + kaisai_date driver = get_selenium_driver(url) elements = driver.find_elements(By.CLASS_NAME,"MyRace_List_Item") for element in elements: race_id = str(element.get_attribute('id')) race_id = race_id.replace("myrace_","") race_ids.append(race_id) return race_ids def fetch_horse_datas(race_id, horse_datas): Base="https://race.netkeiba.com/race/result.html?race_id=" url = Base + race_id kaisai_year = int(race_id[:4]) request = requests.get(url) soup = BeautifulSoup(request.content, 'html.parser') horses = soup.find_all(class_='HorseList') data1 = soup.find(class_='RaceData01') data1 = data1.get_text().replace("/","").split() data2 = soup.find(class_='RaceData02') data2 = data2.select('span') data2 = [t.get_text(strip=True) for t in data2] data3 = soup.find(class_='Refundlink') data3 = str(data3) for horse in horses: horse_data = {} horse_data['start_time'] = data1[0] horse_data['race_type'] = data1[1] horse_data['ground'] = data1[2] horse_data['weather'] = data1[3] horse_data['gr_condition'] = data1[4] horse_data['place'] = data2[1] horse_data['rule'] = data2[3] horse_data['grade'] = data2[4] horse_data['sex'] = data2[5] horse_data['rule6'] = data2[6] horse_data['num_horse'] = data2[7] horse_data['year'] = kaisai_year horse_data['date'] = data3[data3.find("kaisai_date=")+12 : data3.find("kaisai_date=")+20] horse_data['race_id'] = race_id horse_data['rank'] = horse.find(class_='Rank').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['horse_number'] = horse.find(class_='Txt_C').find('div').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['horse_name'] = horse.find(class_='Horse_Name').find('a').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['seirei'] = horse.find(class_='Lgt_Txt Txt_C').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['jockey_weight'] = horse.find(class_='JockeyWeight').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['jockey_name'] = horse.find(class_='Jockey').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['race_time'] = horse.find(class_='RaceTime').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['popularity'] = horse.find(class_='OddsPeople').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['odds'] = horse.find(class_='Txt_R').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['passage'] = horse.find(class_='PassageRate').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['trainer'] = horse.find(class_='Trainer').find('a').get_text().replace('\n','').replace(' ','').replace(' ','') horse_data['weight'] = horse.find(class_='Weight').get_text().replace('\n','').replace(' ','').replace(' ','') horse_datas.append(horse_data) return horse_datas # レースデータをスクレイピング kaisai_dates = get_kaisai_dates() race_ids = get_race_ids(kaisai_dates) horse_datas = [] for race_id in tqdm(race_ids): try: # 出馬表を読み取る horse_datas = fetch_horse_datas(race_id, horse_datas) time.sleep(1.5) except Exception as e: print("error in race_id:",race_id,e) pass df = pd.DataFrame(horse_datas)