AI競馬で回収率100%越えを目指して

はなむけ競馬場

python プログラム 競馬

【スクレイピング】netkeiba.comから競馬データを収集する【コード公開】

更新日:

競馬予想のための機械学習モデルを作成するにあたり、データを集める必要があります。

JRAなどが有料で提供しているものに加入していれば比較的容易にデータを集めることができます。

しかし、そこは出来れば無料で集めたいと思うのが人情です。

そして素晴らしい先達がおり、netkeiba-scraperというコードを無償で公開している聖人がいます。

これを使えば大方のデータを一発で入手することが可能です。

netkeiba-scraperはScalaというプログラミング言語で書かれており、私はまったく嗜んでいません。

故に、微塵も使い方が分かりませんでした。

加えて、netkeiba-scraperだけでは私が必要としていたデータを集めきることができなかったので、netkeiba-scraperを参考にしつつ自作することにしました。

そのコードを公開し、出来る限り使い方も説明していきます。

 

必要なもの

  1. Anaconda(機械学習を使う。私はwindows版を使っています)
  2. XAMPP(データベースの構築)
  3. ChromeDriver(スクレイピングに必要)

事前準備

  1. Anacondaをダウンロードして、Jupyter-notebookというものを起動できる状態にする
  2. XAMPPを起動して、黄色で示したボタン(Start)を押す。MySQLのAdminボタンを押して、管理画面に行く
  3. 新規作成からデータベース名を適当に決め(私はkeibaにした)、作成をクリック
  4. keibaデータベースができるので、上タブの構造をクリックし、画面下部にあるテーブルを作成に適当なテーブル名(私はrece_result)を入力し、実行をクリック
  5. 数のような画面になり、名前や長さ/値の入力を求められる。必要なデータの数だけ埋めていく。
  6. そうしようかと思ったが、書くのが面倒になったのでhttps://mesen-project.com/download/race_result_1/からデータが1個だけ入ったデータベースをzipファイルで用意しました。リンク先からファイルをダウンロードして、インポートしてください。
  7. たぶんデータベースが構築されているはずです。最低限の構造が出来ています。
  8. ChromeDriverをリンク先からダウンロードします。自分のChromeと同一バージョンをダウンロードしてください。
  9. zipファイルがダウンロードされるので、適当な場所で展開(解凍)してください。
  10. そのフォルダの絶対パスをコピーしておいて、環境変数のPathに新規追加してください。

これで基本的な準備は終わっているはずです。

これで動かなければ問い合わせしてもらうか、自分で調べてください。

コードの実行

XAMPPのApacheとMySQLをStartした状態を前提にここからの作業を進行していきます。

いついかなるときも、XAMPPは起動する必要があります。

コードを実行するためにライブラリと呼ばれる便利機能を呼び出さなければいけません。

呼び出すものは以下の通りです。


from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import urllib.error
import urllib.request
import json
import time
import math
import random
import MySQLdb
import datetime
import re
import math
import random
import datetime
import re
import pandas as pd
import os
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains

もしかしたら足りないものがあるかもしれませんし、インストールされていないものがあるかもしれません。

そんなときは、Anacondaをインストールされているおかげで、Anaconda Promptというものが皆さんのPCに入っています。

それを起動して「pip install xxx」(xxxの部分はライブラリの名前)と入力してください。入っていないライブラリの名前とpipで検索したらインストールの仕方のページが出てくるので、それに従ってください。

 

次の下記コードを実行します。

①各日付ごとにどんなレースが行われたのか取得する(race_linkに格納)

options = webdriver.ChromeOptions()
options.add_argument('--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X) AppleWebKit/602.3.12 (KHTML, like Gecko) Version/10.0 Mobile/14C92 Safari/602.1')

driver = webdriver.Chrome(options=options)

driver.get("https://db.netkeiba.com/?pid=race_top")
race_link = []
from selenium.webdriver.common.action_chains import ActionChains
time.sleep(3)

#ここの数字で何か月分取るか決める
#range(X)のXの数字で何か月取るか決まる。12だったら1年分
for i in range(2):
    
    WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "side")))
    
    target = driver.find_element_by_class_name('race_kaisai')
    actions = ActionChains(driver)
    actions.move_to_element(target)
    actions.perform()
    
    time.sleep(1)
    schedule = driver.find_element_by_css_selector("#side > div.race_calendar > dl > dd > table")
    schedule_a_tag = schedule.find_elements_by_tag_name("a")
    for i in schedule_a_tag:
        print(i.get_attribute("href"))
        race_link.append(i.get_attribute("href"))
        
    prev = driver.find_element_by_css_selector("#side > div.race_calendar > dl > dt > ul > li.rev > a:nth-child(2) > img").click()
    
    time.sleep(3)

 

②各レースの結果が載ったリンクを取得(each_race_linkに格納)


each_race_link = []
for link in race_link:
    driver.get(link)
    WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "main")))
    
    #その日のレース情報一覧
    race_name_div = driver.find_element_by_css_selector("#main > div.race_kaisai_info > div")

    #aタグを取得
    race_name_a = race_name_div.find_elements_by_tag_name("a")
    
    for i in race_name_a:
        
        #レース動画のリンクは除外
        if "movie" in i.get_attribute("href"):
            pass
        else:
            print(i.get_attribute("href"))
            each_race_link.append(i.get_attribute("href"))
            
    time.sleep(random.uniform(2,5))

 

③各レースの結果を取得し、データベースに突っ込んでいく


import math
# 接続する
conn = MySQLdb.connect(
user='root',
passwd='',
host='localhost',
db='keiba',
charset='utf8')
# カーソルを取得する
cur = conn.cursor()
# カーソルを取得する
cur = conn.cursor(MySQLdb.cursors.DictCursor)

for link in each_race_link:
    
    

    #テーブルのあるURL
    url = link
    driver.get(url)
    
    WebDriverWait(driver, 120).until(EC.presence_of_element_located((By.ID, "PageTOP")))

    try:
        #レース結果
        dfs = pd.read_html(url)
        
    except:
        time.sleep(15)
        dfs = pd.read_html(url)

    #カラムの変更
    dfs[0].columns = list(dfs[0].loc[0])

    df = dfs[0].drop(0)

    #レースの名前を取得
    race_name = driver.find_element_by_css_selector(".RaceName_main").text

    #ダートか芝かの判定
    dirt = driver.find_elements_by_css_selector(".Dirt")
    turf = driver.find_elements_by_css_selector(".Turf")

    if dirt:
               
        torf = "ダ"
        distance = re.search(r"\d{4}",dirt[0].text).group(0)
        
        try:
            LorR = re.search(r"右|左|外-内|直線|内-外",dirt[0].text).group(0)
            #print(torf)
            #print(distance)
            #print(LorR)
            
        except:
            LorR = None

    elif turf:
        torf = "芝"
        distance = re.search(r"\d{4}",turf[0].text).group(0)
        
        try:
            LorR = re.search(r"右|左|外-内|直線|内-外",turf[0].text).group(0)
            
        except:
            LorR = None

    else:
        torf = "他"
        distance = None
        LorR = None

    #天気
    weather = driver.find_element_by_css_selector(".WeatherData").text
    #print(weather)

    #地面の状態
    race_data = driver.find_elements_by_css_selector("div[class='RaceData']")
    ground_situation = re.search(r"(良|重|不良|稍重|稍|不)",race_data[0].text).group(0)
    #print(ground_situation)

    #日にち
    date = driver.find_element_by_css_selector(".Race_Date").text[0:-3]
    #print(date)

    #発走時間
    run_time = driver.find_element_by_css_selector(""body > div.Wrap.wide_table.fc > div > div.RaceHeader_Value > div.RaceData > span:nth-child(1)").text
    run_time = re.match(r"\d{2}",run_time).group(0)
    #print(run_time)
    
    #場所
    place = driver.find_element_by_css_selector("body > div.Wrap.wide_table.fc > div > div.RaceHeader_Value > div.RaceHeader_Value_Others").text
    place = re.search(r"(\d{1,2}回)(\S{1,2})(\d{1,2}日目)",place).group(2)

    df["増減"] = None
    df["年齢"] = None
    df["場所"] = place
    df["レース日"] = date
    df["レース日"] = pd.to_datetime(df["レース日"])
    df["レース名"] = race_name
    df["発走時間"] = run_time
    df["修正タイム"] = 0
    df["修正タイム着差加味"] = 0
    df["レースID"] = os.path.basename(url.rstrip("/"))
    #print(os.path.basename(url.rstrip("/")))
    df["芝ダ"] = torf
    df["距離"] = distance
    df["左右"] = LorR
    df["天気"] = weather
    df["状態"] = ground_situation
    df["年齢"] = df["性齢"].str.extract("(\d)",expand=True)
    df["性齢"] = df["性齢"].str.replace("\d","")
    df["馬体重"] = df["馬体重"].str.replace("+","")
    df["増減"] = df["馬体重"].str.extract("(\(-?\d+\))",expand=True)
    df["馬体重"] = df["馬体重"].str.replace("(\(-?\d+\))","")
    df["増減"] = df["増減"].str.replace("(","")
    df["増減"] = df["増減"].str.replace(")","")

    #タイムの時間を変更
    for index, row in df.iterrows():   
        #print(index)
            
        if type(row["タイム"]) == str:
            match = re.search(r"(\d+):(\d{1,2})\.(\d{1,2})",row["タイム"])
            m = match.group(1)
            s = match.group(2)
            f = match.group(3)

            goal_time = float(m)*60 + float(s) + float(f)/10

            df.loc[index,"修正タイム"] = goal_time
            #print(int(m)*60 + int(s) + float(f))
            
        else:
            goal_time = None

    for index,row in df.iterrows():
        sql = str(row["着順"]) + "','" + str(row["枠番"]) + "','" + str(row["馬番"]) + "','" + str(row["馬名"]) + "','" + \
        str(row["性齢"]) + "','" +str(row["斤量"])+ "','" +str(row["騎手"])+ "','" +str(row["タイム"])+ "','" +str(row["修正タイム"])+ "','"+str(0)+ "','"+str(row["着差"])+ "','" +\
        str(row["タイム指数"])+ "','" +str(row["通過"])+ "','" +str(row["上り"])+ "','" +str(row["単勝"])+ "','" +str(row["人気"])+ "','"+str(row["馬体重"])+ "','" +\
        str(row["調教タイム"])+ "','" +str(row["厩舎コメント"])+ "','" +str(row["備考"])+ "','" +str(row["調教師"])+ "','" +str(row["馬主"])+ "','" +\
        str(row["賞金(万円)"])+ "','" +str(row["増減"])+ "','" +str(row["年齢"])+ "','" +str(row["芝ダ"])+ "','" +str(row["距離"])+ "','" +\
        str(row["左右"])+ "','" +str(row["天気"])+ "','" +str(row["状態"])+ "','" +str(row["レース日"])+ "','" +str(row["発走時間"])+ "','" +\
        str(row["レースID"]+ "','" +str(row["場所"])+ "','" +str(row["レース名"]))

        sql = "INSERT INTO `race_result`(`着順`, `枠番`, `馬番`, `馬名`, `性齢`, `斤量`, `騎手`, `タイム`, `修正タイム`,`修正タイム着差加味`,`着差`, `タイム指数`, `通過`, `上り`, `単勝`, `人気`, `馬体重`, `調教タイム`, `厩舎コメント`, `備考`, `調教師`, `馬主`, `賞金(万円)`, `増減`, `年齢`, `芝ダ`, `距離`, `左右`, `天気`, `状態`, `レース日`, `発走時間`, `レースID`,`場所`,`レース名`)"\
        + "VALUES ('" + sql + "')"
        #print(sql)

        cur.execute(sql)
        conn.commit()
        
    time.sleep(random.uniform(1,1.5))
    
    res = requests.get(url)
    #print(res.text)
    
    
conn.close

 

上記の順番でコードを実行していけば、必要なデータが手に入るかと思います。

汚いコードで申し訳ありませんが、独学なのでご容赦ください。

何か指摘やコメントなどありましたらお願いします。

好きに使用してください。netkeiba-scraperのほうが良いと思いますが。

-python, プログラム, 競馬

Copyright© はなむけ競馬場 , 2021 All Rights Reserved Powered by AFFINGER5.