【Python】競馬のデータを取得するスクリプトをアレンジしてみた
今回はPythonで競馬の過去データをスクレイピングするスクリプトを書いてみました。
参考とさせていただいたのはこちらのサイトです。 qiita.com
基本的にはこちらをリスペクトさせていただく形で取り組みましたが、追加として以下を取得できるようにしました。
- 枠番
- 着差
- 3Fタイム
あとで機械学習などのデータ解析をしてみたいなぁということでなるべく数値データとして扱える型に変換しています。
そのため、着差については
- ハナ → 0.0625
- アタマ → 0.125
- クビ → 0.25
など、1馬身を基準に1/2ずつ値を区切る形で表現しています。
この辺りは実際もっと厳密にできる気もしますが、いまはこれでヨシとしています。
作者さんのサイトでも言及されていますが、スクリプトの実行は結構時間がかかります。
取得年数に比例する形で実行時間が大きくなるため、期間を小さく区切りながら実行したほうがいいかもしれません。
あと、requestはやりすぎるとサーバ負荷をかけてしまうため、1回ごとに1秒間をあけるようにしています。
以下は今回のPythonのスクリプトです。
# %% ライブラリの読み込み import requests import re from tqdm import tqdm import time from bs4 import BeautifulSoup import pandas as pd def numStr(num): if num >= 10: return str(num) else: return '0' + str(num) Base = "http://race.sp.netkeiba.com/?pid=race_result&race_id=" dst = '' df_col = ['year', 'date', 'field', 'race', 'race_name', 'course', 'head_count', 'rank', 'horse_name', 'gender', 'age', 'trainerA', 'trainerB', 'weight', 'c_weight', 'jockey', 'j_weight', 'odds', 'popu', 'time', 'gap', 'time_3f', 'condition', 'bracket_number'] df = pd.DataFrame() # データ取得開始年と終了年を設定 start_year = 2018 end_year = 2020 for year in tqdm(range(start_year, end_year+1)): for i in tqdm(range(1, 11)): for j in tqdm(range(1, 11)): for k in tqdm(range(1, 11)): for l in range(1, 13): # urlでぶっこ抜く url = Base + str(year) + numStr(i) + \ numStr(j) + numStr(k) + numStr(l) time.sleep(1) try: html = requests.get(url) except ZeroDivisionError: pass else: html.encoding = 'EUC-JP' # scraping soup = BeautifulSoup(html.text, 'html.parser') # ページがあるかの判定 if soup.find_all('div', attrs={'class', 'Result_Guide'}) != []: break else: # 共通部分を抜き出す CommonYear = year CommonDate = soup.find_all('div', attrs={'class', 'Change_Btn Day'})[ 0].string.strip() CommonField = soup.find_all('div', attrs={'class', 'Change_Btn Course'})[ 0].string.strip() CommonRace = soup.find_all('div', attrs={'Race_Num'})[ 0].span.string.strip('R') CommonRname = soup.find_all('dt', attrs={'class', 'Race_Name'})[ 0].contents[0].strip() CommonCourse = soup.find_all('dd', attrs={'Race_Data'})[ 0].span.string.strip('m') CommonCondition = soup.find_all('span', attrs={'class', 'Item03'})[ 0].string # たまに頭数取得ができないケースがあるのでこちらで取得 CommonHcount = len( soup.find_all('div', attrs='Rank')) for m in range(len(soup.find_all('div', attrs='Rank'))): dst = pd.Series(index=df_col) try: dst['year'] = CommonYear dst['date'] = CommonDate dst['field'] = CommonField # 開催場所 dst['race'] = CommonRace dst['race_name'] = CommonRname dst['course'] = CommonCourse dst['head_count'] = CommonHcount # 頭数 dst['condition'] = CommonCondition dst['rank'] = soup.find_all('div', attrs='Rank')[ m].contents[0] dst['horse_name'] = soup.find_all( 'dt', attrs=['class', 'Horse_Name'])[m].a.string detailL = soup.find_all( 'span', attrs=['class', 'Detail_Left'])[m] dst['gender'] = list( detailL.contents[0].split()[0])[0] dst['age'] = list( detailL.contents[0].split()[0])[1] dst['trainerA'] = detailL.span.string.split('・')[ 0] dst['trainerB'] = detailL.span.string.split('・')[ 1] if len(detailL.contents[0].split()) >= 2: dst['weight'] = detailL.contents[0].split()[ 1].split('(')[0] if len(detailL.contents[0].split()[1].split('(')) >= 2: dst['c_weight'] = detailL.contents[0].split()[1].split( '(')[1].strip(')').strip('+') # 多分馬の体重変動 detailR = soup.find_all('span', attrs=['class', 'Detail_Right'])[ m].contents if "\n" in detailR or "\n▲" in detailR or '\n☆' in detailR: detailR.pop(0) dst['jackie'] = detailR[0].string.strip() if detailR[2].string is not None: dst['j_weight'] = detailR[2].strip().replace( '(', '').replace(')', '').strip('.0') # 多分jockeyの体重変動 else: dst['j_weight'] = detailR[2] Odds = soup.find_all('td', attrs=['class', 'Odds'])[ m].contents[1] if Odds.dt.string is not None: dst['odds'] = Odds.dt.string.strip('倍') dst['popu'] = Odds.dd.string.strip( '人気') # 何番人気か Time = soup.find_all('td', attrs=['class', 'Time'])[ m].contents[1] if Time.dt.string is not None: race_time = Time.find_all('dd') # 走破タイム dst['time'] = Time.dt.string dst['gap'] = race_time[0].string # 着差 dst['time_3f'] = race_time[1].string.strip( '()') # 3Fタイム Number = soup.find_all( 'td', attrs=['class', re.compile('Num Waku')])[m*2] # 枠番は偶数番目の要素に格納される Number2 = soup.find_all( 'td', attrs=['class', re.compile('Num Waku')])[m*2+1] # 馬番は奇数番目の要素に格納される dst['bracket_number'] = Number.div.string dst['horse_number'] = Number2.div.string except ZeroDivisionError: pass dst.name = str( year) + numStr(i) + numStr(j) + numStr(k) + numStr(l) + numStr(m) df = df.append(dst) # 出頭数を整数化 df['head_count'] = df['head_count'].astype('i') # courseをcourse_typeとdistanceに分割 df['course'] = df['course'].replace('(.*)ダ(.*)', r'\1ダ_\2', regex=True) # _を追加 df['course'] = df['course'].replace('(.*)芝(.*)', r'\1芝_\2', regex=True) # _を追加 df['course'] = df['course'].replace('(.*)障(.*)', r'\1障_\2', regex=True) # _を追加 df = pd.concat([df, df['course'].str.split('_', expand=True)], axis=1).drop('course', axis=1) df.rename(columns={0: 'course_type', 1: 'distance'}, inplace=True) # 走破タイムを秒に変換 base_time = pd.to_datetime('00:00.0', format='%M:%S.%f') df['time'] = pd.to_datetime(df['time'], format='%M:%S.%f') - base_time df['time'] = df['time'].dt.total_seconds().astype('f') # 着差のデータ整形 df['gap'] = df['gap'].fillna(0) # 欠損値に0を代入 df['gap'] = df['gap'].replace('(.*)/(.*)', r'\1_\2', regex=True) df['gap'] = df['gap'].replace('(.*).1_2(.*)', r'\1.5\2', regex=True) df['gap'] = df['gap'].replace('(.*)1_2(.*)', '0.5', regex=True) df['gap'] = df['gap'].replace('(.*).3_4(.*)', r'\1.75\2', regex=True) df['gap'] = df['gap'].replace('(.*)3_4(.*)', '0.75', regex=True) df['gap'] = df['gap'].replace('(.*).1_4(.*)', r'\1.25\2', regex=True) df['gap'] = df['gap'].replace('(.*)1_4(.*)', '0.25', regex=True) df['gap'] = df['gap'].replace('大', '11') df['gap'] = df['gap'].replace('クビ', '0.25') df['gap'] = df['gap'].replace('アタマ', '0.125') df['gap'] = df['gap'].replace('ハナ', '0.0625') df['gap'] = df['gap'].astype('f') # 着差を数値化(float) # 開催年の整数化 df['year'] = df['year'].astype('i') # 除外が含まれる行を削除 # df = df[df['rank'] != '除外'] # 除外が含まれる行以外を選択してdfに代入している # 出力ファイル名の設定 file_name = 'keiba_PS_' + start_year + '-' + end_year + '_.xlsx' # データフレームをExcelに出力 df.to_excel(file_name, sheet_name='race_data')
次回以降、このデータをもとに機械学習を使ってデータ分析していきます。(やるとはいってない)