netkeibaから競馬データをスクレイピングする方法についてまとめます。既に同様の記事は世にたくさん出回ってますが、少し改良して効率化してみました。
競馬データのスクレイピングで検索すると、以下のようなソースコードが多いと思います。
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
def get_selenium_driver(url):
options = Options()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
driver.get(url)
time.sleep(5)
return driver
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日の全レース結果をスクレイピングできます。
以下にソースコード一式をまとめます。
以下では取得したい「年」を指定することで、その年の全レースデータ結果を取得できるようにしてます。
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"))]
def get_selenium_driver(url):
options = Options()
options.add_argument("--headless")
driver = webdriver.Chrome(options=options)
driver.get(url)
time.sleep(5)
return driver
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)