最初に競馬予測をするためには、データ収集が必要です。
このデータ収集で、ミスしてたりすると、最初からやり直しなので、気分が折れます(3敗)。
基本的は、https://www.netkeiba.com/ さんからデータをスクレイピングで抜いています。
あんまり負荷をかけるのは、よくないので各自モラルを守ってスクレイピングしてください。
プログラム
make_database():
netkeiba.comさんの結果URLは規則的に配置されていて、
_https://db.netkeiba.com/race/200003010109
のように、12桁の数字で結果を見れます
虱潰しに入れて行けば、どの数字が何を表すかがわかります。
そして
race_url = f'https://db.netkeiba.com/race/{year}{place_number}{place_open_count}{place_day_count}{round}'
このコードで、いつのレースかを指定できます。
- year : 開催した年
- place_number : 開催した場所コード
- ’01’ : 札幌
- ’02’ : 函館
- ’03’ : 福島
- ’04’ : 新潟
- ’05’ : 東京
- ’06’ : 中山
- ’07’ : 中京
- ’08’ : 京都
- ’09’ : 阪神
- ’10’ : 小倉
- place_open_count : 「第n回」みたいなやつ
- place_day_count : n日目みたいなやつ
- round : その日のラウンド数
だと思います。
つまり、指定した範囲のURLデータは次のコードで取ってこれます。
def make_database():
years = [f'20{i:02}' for i in range(25)][::-1]
race_place_numbers = [
'01',#札幌
'02',#函館
'03',#福島
'04',#新潟
'05',#東京
'06',#中山
'07',#中京
'08',#京都
'09',#阪神
'10'#小倉
]
race_place_open_counts = [f'{i:02}' for i in range(1,7)]
race_rounds = [f'{i:02}' for i in range(1,13)]
for year in years:
for place_number in race_place_numbers:
save_name = f'keiba_source_data/{year}_{place_number}.data'
if os.path.isfile(save_name):
print(f'already created database :{save_name}')
else:
ret = []
for place_open_count in race_place_open_counts:
for place_day_count in race_place_open_counts:
for round in race_rounds:
race_url = f'https://db.netkeiba.com/race/{year}{place_number}{place_open_count}{place_day_count}{round}'
ret.append(wrapped_url_accecc_func(race_url))
with open(save_name, mode="wb") as f:
pickle.dump(ret, f)
print(f'save file as {save_name}')
とりあえず、年と場所で、区切ってデータを収集し、それをpickle形式で保存します。
処理が途中で落ちちゃっても、再開できるようにファイルが既に有れば、取ってこないようにもしました。
回線強度等にも、依存すると思いますが、私の環境では数日かかりました。
joblib等で並列化アクセスすればもっと早いと思いますが、スクレイピングでは推奨されないので、個人の裁量に任せます。
wrapped_url_access_func(race_url, try_count=0):
エラー処理等が起きたときに、リトライするための関数です。
def wrapped_url_access_func(race_url, try_count=0):
try:
race_horses_data = url_access_func(race_url)
except (ValueError, AttributeError, TimeoutError, requests.exceptions.SSLError, requests.exceptions.ChunkedEncodingError) as e:
print('error page. url:', race_url)
return {}
except requests.exceptions.ConnectionError:
if try_count == 10:
print('connection error:', race_url, 'no retry')
return {}
else:
print('connection error:', race_url, 'retry after 0.1 sec')
time.sleep(0.1)
race_horses_data = wrapped_url_access_func(race_url, try_count= try_count+1)
return race_horses_data
else:
if len(race_horses_data) <= 1:
print('error page. url:', race_url)
return {}
else:
return race_horses_data
url_access_func(race_url):
更に一枚、関数を噛ませます。タイムアウト処理を施しました。
@timeout(300, use_signals=False)
def url_access_func(race_url):
print(race_url)
race_horses_data = get_race_data_from_race_after_URL(race_url)
time.sleep(0.1)
return race_horses_data
def get_race_data_from_race_after_URL(url):
長い処理ですが、やってることは単純です。
beautifulsoupで取ってきたhtmlを整形して処理しているだけです。
実際の機械学習には必要ないデータも取ってくるようにしました。
このあたりは、解析してみて、必要かどうかを検証したいと思います。
def get_race_data_from_race_after_URL(url):
time.sleep(0.1)
headers = requests.utils.default_headers()
headers.update({'User-Agent': 'My User Agent 1.0', })
race_horses_data = []
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.content, 'html.parser')
race_name = soup.find('dl', class_='racedata fc')
if race_name == None:
return []
temp_data = [item for item in race_name.text.split('\n') if item!='']
race_num = temp_data[0]
race_name = temp_data[1]
race_info = temp_data[2]
race_type, race_weather, race_ground_type, race_start_time = race_info.strip().split('/')
#print(race_type, race_weather, race_ground_type, race_start_time)
race_place_data = {}
race_place_data['race_name'] = race_name
race_place_data['race_type'] = race_type.strip()[0]
race_place_data['race_distance'] = float(race_type.strip()[2:-1])
race_place_data['weather'] = race_weather.strip()[-1]
race_place_data['race_condition'] = race_ground_type.strip()[-1]
temp_data = soup.find('p', class_='smalltxt').text.split(' ')
race_date = temp_data[0]
race_date = datetime.datetime.strptime(race_date, '%Y年%m月%d日')
race_place_data['race_date'] = race_date
race_grade = temp_data[2]
race_place_data['race_grade'] = race_grade
table = soup.find('table', class_='race_table_01 nk_tb_common')
for row in table.find_all('tr')[1:]:
#print(race_place_data)
items = [item for item in row.find_all('td')]
horse_data_dict = {
'rank':string_to_number(items[0].text),
'waku':string_to_number(items[1].text),
'horse_number':string_to_number(items[2].text),
'name':items[3].text.strip(),
'sex':0 if items[4].text[0] == '牡' else 1,
'age':string_to_number(items[4].text[1]),
'jocky_weight':string_to_number(items[5].text),
'jocky_name':items[6].text.strip(),
'time':items[7].text.strip(),
'odds':string_to_number(items[12].text),
'popular':string_to_number(items[13].text),
'weight':string_to_number(items[14].text[0:3]),
'weight_sub':string_to_number(items[14].text[4:-1]),
'prize':string_to_number(items[20].text)
}
for key in ['rank', 'waku', 'horse_number', 'age', 'jocky_weight', 'odds', 'popular', 'weight', 'weight_sub']:
if horse_data_dict[key] == None:
return []
href = items[3].find('a').get('href')
horse_id = href.split('/')[-2]
horse_ped_data_url = f'https://db.netkeiba.com/horse/ped/{horse_id}/'
horse_data_dict['ped_data'] = get_ped_data_from_URL(horse_ped_data_url)
race_horses_data.append(horse_data_dict)
race_horses_data.sort(key=lambda item : item['horse_number'])
table = soup.find('table', class_='pay_table_01')
rows = table.find_all('tr')
tansyo_items = [item.text.strip() for item in rows[0].find_all('td')]
hukusyo_items = [item.get_text(',').split(',') for item in rows[1].find_all('td')]
race_payout_data = {
'tansyo_ret':tansyo_items[0],
'tansyo_payout':tansyo_items[1],
'tansyo_popular':tansyo_items[2],
'hukusyo_ret':hukusyo_items[0],
'hukusyo_payout':hukusyo_items[1],
'hukusyo_popular':hukusyo_items[2],
}
ret = {
'race_place_data':race_place_data,
'race_horses_data':race_horses_data,
'race_payout_data':race_payout_data
}
return ret
get_ped_data_from_URL(url):
血統データを見る関数です。血統は解析するのが難しいのですが、とりあえず取ってきました。
def get_ped_data_from_URL(url):
time.sleep(0.1)
headers = requests.utils.default_headers()
headers.update({'User-Agent': 'My User Agent 1.0', })
ped_data_response = requests.get(url, headers=headers)
ped_data_soup = BeautifulSoup(ped_data_response.content, 'html.parser')
ped_data_table = ped_data_soup.find('table', class_='blood_table detail')
peds = []
for pre_data_row in ped_data_table.find_all('tr'):
for item in pre_data_row.find_all('td'):
name = item.find('a').get_text().strip().replace('\n', '')
peds.append(name)
return peds
string_to_number
結構な割合で不要な文字(括弧とか)が、切り出した文字中に含まれていることがあります。
しかも、馬が欠場とかでいないと、数値じゃないものが入ってたりします。
既製のfloat関数で文字をキャストすると、数値以外が入っていたときに止まってしまうので、自分で書きました。
def string_to_number(str):
number = [f'{n}' for n in range(10)] + ['.', '-']
number_str = ''
for s in str:
if s in number:
number_str += s
if number_str == '':
return None
else:
return float(number_str)
if name == ‘main‘:
あとは上記の関数を一つのプログラム中に書いて、mainでmake_databaseを呼べば完成です。
if __name__ == '__main__':
make_database()
実行されると、永遠とページを彷徨ってログが出力されます。
...
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
error page. url: https://db.netkeiba.com/race/200407030409
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
error page. url: https://db.netkeiba.com/race/200407030411
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
![](data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%20160%20160%22%3E%3C/svg%3E)
競馬データベース | 競走馬・騎手など情報満載 - netkeibanetkeibaが誇る国内最大級の競馬データベースです。50万頭以上の競走馬、騎手・調教師・馬主・生産者の全データがご覧いただけます。
...
2000年から2024年までのページを取ってきているので、少し時間がかかります。
実際には機械学習が活発に利用され始める2015年を堺に、オッズ等の結果が変わっていそうです。
ですから、そんな昔まで要らないという方は、パラメータを弄って適度にしてください。