とあるデータサイエンティストの競馬予測チャレンジ データ収集編1

機械学習応用編

最近、馬娘で人気沸騰中の競馬ですが、友人の勧めで競馬予測を始めました。

 

競馬予測チャレンジ目次!



  1. データ取得編1(過去のレースデータ取得) ⇦本記事はココ

  2. データ取得編2(スピード指数取得)

  3. 特徴量エンジニアリング編

  4. モデリング、評価編

  5. 実践編

     



 

 

競馬予測するためにはデータが必要になります。まずはデータ収集からです。

ここではスクレイピングを使ってデータをネットから自動収集していきます。

機械学習からは少し外れますが、スクレイピングができると何かと楽しいです。

 

競馬に必要なデータはどこから入手するか?

競馬関連の情報が手に入るサイトはいくつかありますが、次の二つを使いました。

1つ目のnetkeiba.comでは過去に行われたレース情報がタダで入手できます。

2つ目の競馬新聞&スピード指数(無料)では、netkeiba.comでは手に入らない(有料会員で閲覧可能)、スピード指数と呼ばれる情報を手に入れるために使います。

 

どうやってデータを抽出するか?

PythonのBeautiful Soupライブラリを使ってスクレイピングします。

ウェブページを開いてパソコンのF12を押すとウェブページを構成するスクリプトが表示されます。

ここから馬の名前や過去の結果等の必要な情報を取得していきます。

URLの構成

今回情報を取得する(スレイピングする)対象はnetkeiba.comという競馬情報サイトです。

まずは、このサイトのURL構成を分析してみましょう。

ウェブサイトのデータベースにあるレース情報をなんでもいいので複数開いてみると次の6つの要素から構成されていることがわかります。

(例)https://db.sp.netkeiba.com/race/201501010201
   1回 札幌 2日目

  1. URL冒頭の「https://db.sp.netkeiba.com/race/」(これは固定)
  2. レースの開催年の西暦(4桁)
    • 本記事では2015年から2020年を対象
  3. 開催場所(2桁)
    • 10か所(札幌、函館、福島、新潟、東京、中山、中京、京都、阪神、小倉) 
  4. 〇〇回(2桁)
    • おそらく1~10回まで
  5. ○○日目(2桁)
    • おそらく1~10日目まで
  6. 第〇レース(2桁)
    • 第1レースから第12レース

ここまでわかれば、あとは機械的にfor文をひたすら回せばレース情報が記載されているウェブページを表示させることができそうです。

スクレイピングによる情報の抽出

用いる手法はPythonのBeautiful Soupライブラリを使います。

 

スクレイピングするときの注意点

まずはその前に、前項のパターンを全て足し合わせると6×10×10×10×12=7万2千個のURLを処理することになります。

この数字、一見そこまで多くなさそうですが、

必ず、サーバに負荷を与えないように各リクエスト処理には毎回最低1秒の時間間隔を

設けて取得してください。※後のコードでも説明します。

つまり、全処理を実行すると単純計算で20時間(7万2千秒)かかることになります。

非常に長いです。

そこで、次のような時間短縮策も打ちます。(多少ましになる、程度ですが。。。)

  • レースの途中で情報が欠けている場合、その日のその後のレース情報は取得しない
    • 例)第4レースの情報がない場合、第5レース以降の情報はスキップする
  • こまめに取得した情報をCSVファイルに保存する
    • 何らかの原因で処理エラーになった場合に途中から再開できるようにするため

取得するデータ

netkeiba.comから取得できるデータ一覧はこちら!

カラム名
日付 8/1/2015
曜日 (土)
レース名 2歳未勝利
レース時間 09:55発走
サーフェス 芝1500m(右)
天気1
天気2
レース情報1 1回札幌1日目 2歳未勝利
レース情報2 (混)[指]馬齢
着順 1
枠番 2
馬番 2
馬名 マカーオーン
性齢 牡2
斤量 54
騎手 勝浦正樹
タイム 01:29.5
着差 クビ
タイム指数 81
通過 4/5/2004
上り 36.1
単勝 7.3
人気 4
馬体重 466(-2)
調教タイム (有料会員のみ表示)
厩舎コメント (有料会員のみ表示)
備考 (不明)
調教師 [東]古賀史生
馬主 ドリームジャパンホースレーシング
賞金 500

 

スクレイピングコード例 ※実行するときは自己責任でお願いします。

import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
from tqdm import tqdm
import time
from bs4 import BeautifulSoup
import pandas as pd
df1=pd.DataFrame()
flg=0
flg2=0
base='https://db.sp.netkeiba.com/race/' # URL先頭の固定部分
for year in tqdm(range(2020, 2021)):
    for basyo in range(1,12):
        for kaisu in range(1,12):
            for nichime in range(1,12):
                for race in range(1,13):

                    # 超重要。毎回リクエストを送る最に1秒間の時間を置く
           time.sleep(1)

           # URLを作成。zfill関数を使って「3」→「03」のように2ケタ表示に変換
                    url=base+str(year)+str(basyo).zfill(2)+str(kaisu).zfill(2)+str(nichime).zfill(2)+str(race).zfill(2) 

                    html = requests.get(url) # リクエスト
                    html.encoding = html.apparent_encoding # 日本語を取得したときに文字化けしなければ必要ないかも?
                    soup = BeautifulSoup(html.text, 'lxml') 

                    table=soup.find(class_="table_slide_body ResultsByRaceDetail") # 表を取得
                    if (table == []) | (table == None): #tableが上手く取得できていない場合、その後の処理はスキップ
                        print('break',url)
                        break

                    else:
                        for tr in table.find_all('tr')[1:]: # trタグごとに処理。最初のtrタグは表のカラム名なのでスキップ
                            try:
                                temp=[] #このリストにデータを1カラムずつ格納していく
                  # まずは共通項目から取得
                                temp.append(soup.find_all(class_="Race_Date")[0].contents[0].string.strip()) #日付
                                temp.append(soup.find_all(class_="Race_Date")[0].contents[1].string.strip()) #曜日                               
                                temp.append(soup.find_all(class_="RaceName_main")[0].string) # レース名
                                temp.append(soup.find_all(class_="RaceData")[0].contents[1].string) #レース時間
                                temp.append(soup.find_all(class_="RaceData")[0].contents[3].string) #芝 or ダート
                                temp.append(soup.find_all(class_="RaceData")[0].contents[5].contents[0]) # 天気
                                temp.append(soup.find_all(class_="RaceData")[0].contents[7].string) # レース場の状態
                                temp.append(soup.find_all(class_="RaceHeader_Value_Others")[0].contents[1].string) #レース情報1
                                temp.append(soup.find_all(class_="RaceHeader_Value_Others")[0].contents[3].string) #レース情報2
                                #出場馬ごとの情報の抽出
                                for td in tr.find_all('td'):
                                    temp.append(td.string)
                                df1=pd.concat([df1,pd.DataFrame(temp).T])
                                flg=0
                            except: # 情報取得できなかった場合はフラグを立てる
                                flg=1
                                pass
                        if flg == 0:
                            print('OK',url)
                        else:
                            print('NG',url)
                            flg=0

    df1.to_csv('table1_'+str(year)+'.csv',encoding='utf_8_sig') # 年ごとにデータをcsvにして保存
    
# 最後にカラム名を設定
df1.columns=['日付','曜日','レース名','レース時間','サーフェス','天気1','天気2','レース情報1','レース情報2',
             '着順','枠番','馬番','馬名','性齢','斤量','騎手','タイム','着差','タイム指数','通過','上り','単勝','人気','馬体重','調教タイム','厩舎コメント','備考','調教師','馬主',"賞金"]

以上となります。

勘と経験で競馬予測している人が重要視するとされる指標がスピード指数です。

それだけ重要な指標は入れないわけにはいきません!スクレイピングで取得していきましょう!

ということで次回は「データ取得編2(スピード指数取得)」です!

 

コメント

タイトルとURLをコピーしました