競馬AIを作る

機械学習で競馬予測モデルを開発する過程をまとめていきます

競馬AIを作っていく。どんなことを考えながらやっていたか、なんとなく書きながらする。

まずはスクレイピングから

データの取得

競馬のデータを取得するために、netkeiba.comからスクレイピングを行う。いかんせん久しぶりにパソコンに触るため何もかも忘れている。。。。

スクレイピングの基本

bs4を使ってスクレイピングをするのだが、find_all()の使い方をまずは復習。

bs.find_all("タグ名")で指定したタグ名の要素を全て取得できる。
しかし、どうやらbs.select("cssセレクタ")の方が使い勝手が良いそう。

ということで必要最低限のcssセレクタを見る。 参考サイト

で、実装したのがこれ

Python
text = bs.select(".txt_r")

行けてそうではあるが、実行してみるとどうやら階層が一個下の物は取得できていないっぽい。
次に、以下を試してみた:

Python
text = bs.select("td")

これで取得できた。が他の部分も取得してしまう。どのように取り分けるか。。。。(出走馬の数は変動するのできちんと取り分ける必要がある)

Python
text = bs.select_one(".race_table_01.nk_tb_common")

これで解決。複数のクラスを持っている場合(クラスは空白で区切る)の指定方法に苦戦してしまった。

その人のデータも取得する。

Python
text = bs.select_one(".smalltxt")

と思ったが、先にurlのリストを作っておいた方がよさそう。参考にしたサイトでは作っていたが、最初私の考えでは取得しつつしていくのがいいと思っていたが、それだと無駄な処理が増えてdbサーバーに負荷をかけてしまいそうなのでリストを作る。

Python
links = bs.find_all("a")

集めたデータから正規表現を用いて必要なやつだけを出して。。。

Python
if re.match("/race/list/[0-9]+",link.get("href")):
    truelinks.append(link.get("href"))

おなじ内容のやつがあったからそれをsetで消してと。。。出来た!!

Python
with open("./data/urllist.txt", "w",newline="") as file:
    for i in range(0,6):
        for j in range(1,13):
            serchurl = URL + "202" + str(i) + str(j).zfill(2) + "01/"
            print(serchurl)
            html = requests.get(serchurl,headers=header).content
            bs = bs4.BeautifulSoup(html, "html.parser")
            links = bs.find_all("a")
            truelinks = []
            for link in links:
                if re.match("/race/list/[0-9]+",link.get("href")):
                    truelinks.append(link.get("href"))
            truelinks = list(set(truelinks))
            for truelink in truelinks:
                file.write(truelink + "\n")
                print(truelink)

ネストがひどいな。とりあえずレースの日にちが出たからこれからその日のレースのURLを取得するやつも書く。
forの部分をurllistから取ってくるようにして正規表現を変える。

これで綺麗にcsvになるようにするだけ

データの処理

とりあえずめんどくさいだけの作業なので生成AIにぶん投げてみてできたのがこれ

Python
def race_info(bs):
    title = bs.select("h1")
    print(title[1].get_text())
    text = bs.select_one(".smalltxt")
    textlist = text.get_text().split()
    print(textlist)

    # 必要に応じて出力を確認する
    race_surface_dir_distance = textlist[0]  # 例:"ダ右1800m"

    # race_idはURLから抽出する可能性がある(引数として渡す方が良いかも)
    race_id = URL.split('/')[-2]  # 「202008010901」のような部分

    # 各フィールドを抽出
    race_name = title[1].get_text().strip()
    date = textlist[0]  # 日付:"2020年1月26日"
    place = textlist[1].split('回')[1].split('日')[0]  # 「京都」を抽出

    # 馬場情報を抽出
    surface = textlist[9] if '/' in textlist and textlist.index('/') < len(textlist)-2 else "不明"
    surface_condition = textlist[11] if '重' in textlist else "不明"
    weather = textlist[4] if ':' in textlist and textlist.index(':') > 0 else "不明"

    # 方向と距離を分離
    if "左" in race_surface_dir_distance:
        direction = "左"
    elif "右" in race_surface_dir_distance:
        direction = "右"
    else:
        direction = ""

    # 距離を抽出(数字+mの部分)
    distance = re.search(r'(\d+)m', race_surface_dir_distance)
    distance = distance.group() if distance else ""

    return [race_id, race_name, date, place, surface, surface_condition, weather, direction, distance]

結果

['202008010901', '3歳未勝利', '2020年1月26日', '京都9', '不明', '不明', '不明', '', '']

全然だめじゃーん。少し手直し

Python
def race_info(bs):
    title = bs.select("h1")
    text = bs.select_one(".smalltxt")
    textlist = text.get_text().split()
    infos = bs.select_one("diary_snap_cut>span").get_text().split()

    #レースがない場合
    if not(infos[8]):
        return None
    # 各フィールドを抽出
    race_name = title[1].get_text().strip()
    date = textlist[0]  # 日付:"2020年1月26日"
    place = textlist[1].split('回')[1]
    place = re.split(r'[0-9]+',place)[0]  # 例:"東京"

    # レースのクラスを抽出
    class1 = textlist[2]
    class2 = textlist[3]

    # レースの馬場、馬場状態、天気、左右、距離を抽出
    surface = infos[0][0]
    surface_condition = infos[8]
    weather = infos[4]
    direction = infos[0][1]
    distance = infos[0][2:]
    return [race_name,class1 , class2, date, place, surface, surface_condition, weather, direction, distance]

これでいけるかな。。。。

次リザルトの方!これもとりあえず生成AIに投げてみる。

Python
def race_result(bs, race_id):
    table = bs.select_one(".race_table_01.nk_tb_common")
    formatted_results = []

    if table:
        # テーブルの行を取得
        rows = table.find_all('tr')

        # ヘッダー行をスキップして各行を処理
        for row in rows[1:]:
            # 各行のセルを取得
            cells = row.find_all(['td', 'th'])

            # 十分なセル数があることを確認
            if len(cells) >= 19:  # 調教師と馬主が含まれる必要がある
                # 特定のセルからテキストを抽出し、余分な空白を削除

                # 調教師のデータは [東] や [西] が含まれるのでクリーニング
                trainer_text = cells[18].get_text().strip()
                trainer = trainer_text.replace('[東]', '').replace('[西]', '').strip()

                # 馬主のテキストを取得
                owner = cells[19].get_text().strip()

                formatted_row = [
                    race_id,                          # race_id
                    cells[0].get_text().strip(),      # 着順
                    cells[1].get_text().strip(),      # 枠番
                    cells[2].get_text().strip(),      # 馬番
                    cells[3].get_text().strip(),      # 馬名
                    cells[4].get_text().strip(),      # 性齢
                    cells[5].get_text().strip(),      # 斤量
                    cells[6].get_text().strip(),      # 騎手
                    cells[7].get_text().strip(),      # タイム
                    cells[8].get_text().strip(),      # 着差
                    cells[10].get_text().strip(),     # 通過
                    cells[11].get_text().strip(),     # 上り
                    cells[12].get_text().strip(),     # 単勝
                    cells[13].get_text().strip(),     # 人気
                    cells[14].get_text().strip(),     # 馬体重
                    trainer,                          # 調教師
                    owner                             # 馬主
                ]
                formatted_results.append(formatted_row)

    return formatted_results

htmlと一緒に投げたなんかよさそう

次のステップ

これからの計画:

  • 収集したデータの前処理
  • 機械学習モデルの選定と実装
  • 特徴量エンジニアリング
  • モデルの評価と改善