見出し画像

Claude 3 Opusにnetkeibaのホームページを解析させてスクレイピングとデータ前処理をしてもらう

イントロダクション

みなさん、Claude 3 Opusは使ってますか?
私は発表されてすぐに課金して使ってますが、頭の良さに驚いてばっかりです。
話は変わりますが、私は競馬が大好きでこの前の弥生賞とチューリップ賞では痛い目を見ました。なので今とっても金欠です。
そこでClaude 3 Opusを使ってどうにか競馬予想ができないかということでひとまずデータ集めのコードを作ってもらったのがこの記事です。
※スクレイピングを短時間で大量に実施することはnetkeibaのサーバーに負担をかけてしまうため、今回は1ページのみで行っています。
https://support.keiba.netkeiba.com/hc/ja/articles/18841959592857-%E9%80%9A%E4%BF%A1%E5%88%B6%E9%99%90%E3%81%8C%E3%81%8B%E3%81%8B%E3%81%A3%E3%81%9F-%E3%82%B9%E3%82%AF%E3%83%AC%E3%82%A4%E3%83%93%E3%83%B3%E3%82%B0%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6


スクレイピングしたレース

今回は2020年の1回東京1日目の1Rをターゲットにしてスクレイピングしました。

Claude 3 Opusが最初に実装したコード

まずホームページの構造を与えずにスクレイピングのコードを実装してもらいました。(普通に考えると無理なので、まともに動かなかったです。)


import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
from datetime import datetime
import re

def scrape_race_results(race_id):
    """
    指定されたレースIDの競馬データをスクレイピングする関数
    """
    url = f"https://db.netkeiba.com/race/{race_id}/"
    response = requests.get(url)
    soup = BeautifulSoup(response.content, "html.parser")

    # レース情報の抽出
    race_info = {}
    race_info["race_id"] = race_id
    race_info["race_name"] = soup.find("div", class_="race_head").find("h1").text.strip()
    race_info["race_date"] = soup.find("div", class_="race_head").find("p").text.split("|")[0].strip()
    race_info["course"] = soup.find("div", class_="race_head").find("p").text.split("|")[1].strip()
    race_info["distance"] = int(re.findall(r"\d+", soup.find("div", class_="race_head").find("p").text.split("|")[2])[0])
    race_info["weather"] = soup.find("div", class_="race_head").find("p").text.split("/")[0].strip()
    race_info["track_condition"] = soup.find("div", class_="race_head").find("p").text.split("/")[1].strip()

    # 競走馬情報の抽出
    horse_data = []
    table = soup.find("table", class_="race_table_01")
    rows = table.find_all("tr")[1:]  # ヘッダー行を除く
    for row in rows:
        horse = {}
        cols = row.find_all("td")
        horse["horse_id"] = cols[1].find("a")["href"].split("/")[-2]
        horse["horse_name"] = cols[3].text.strip()
        horse["jockey_id"] = cols[6].find("a")["href"].split("/")[-2]
        horse["jockey_name"] = cols[6].text.strip()
        horse["time"] = cols[7].text.strip()
        horse["margin"] = cols[8].text.strip()
        horse["order"] = int(cols[0].text)
        horse["weight"] = float(cols[4].text)
        horse["odds"] = float(cols[12].text)
        horse["popularity"] = int(cols[13].text)
        horse_data.append(horse)

    return race_info, horse_data

def preprocess_data(race_info, horse_data):
    """
    スクレイピングしたデータを前処理する関数
    """
    # レース情報のデータフレーム化
    race_df = pd.DataFrame([race_info])
    race_df["race_date"] = pd.to_datetime(race_df["race_date"])

    # 競走馬情報のデータフレーム化
    horse_df = pd.DataFrame(horse_data)

    # カテゴリカル変数のエンコーディング
    horse_df["horse_id"] = horse_df["horse_id"].astype("category").cat.codes
    horse_df["jockey_id"] = horse_df["jockey_id"].astype("category").cat.codes

    # 欠損値の処理
    horse_df.fillna({"weight": horse_df["weight"].mean(), "odds": horse_df["odds"].mean()}, inplace=True)

    # 外れ値の処理
    horse_df["time"] = horse_df["time"].apply(lambda x: x.replace(".", ":") if "." in x else x)
    horse_df["time"] = pd.to_timedelta(horse_df["time"]).dt.total_seconds()
    horse_df["margin"] = horse_df["margin"].apply(lambda x: 0.0 if x == "" else float(x))

    # レース情報と競走馬情報の結合
    merged_df = pd.concat([race_df.iloc[np.arange(len(horse_df)).repeat(len(race_df))].reset_index(drop=True),
                           horse_df], axis=1)

    # CSVファイルに保存
    merged_df.to_csv(f"race_{race_id}.csv", index=False)

    return merged_df

def main():
    race_id = "202005010101"  # レースIDを指定
    race_info, horse_data = scrape_race_results(race_id)
    df = preprocess_data(race_info, horse_data)
    print(df.head())

if __name__ == "__main__":
    main()

ページ構造を与える

次にページ構造をClaudeに教えるために、ページの<html>タグの中身を全てコピペして情報を与えることにしました。

そのようにすると、以下のコードのようにタイムや上がり3Fなどを取得してくれます。

import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
from datetime import datetime
import re

def scrape_race_results(race_id):
    """
    指定されたレースIDの競馬データをスクレイピングする関数
    """
    url = f"https://race.netkeiba.com/race/result.html?race_id={race_id}"
    response = requests.get(url)
    soup = BeautifulSoup(response.content, "html.parser")

    # レース情報の抽出
    race_info = {}
    race_info["race_id"] = race_id
    race_info["race_name"] = soup.find("div", class_="RaceName").text.strip()
    race_info["race_date"] = soup.find("div", class_="RaceData01").text.split("/")[0].strip()
    race_info["course"] = soup.find("div", class_="RaceData01").text.split("/")[1].strip()
    race_info["distance"] = int(re.findall(r"\d+", race_info["course"])[0])
    race_info["weather"] = soup.find("div", class_="RaceData01").text.split("/")[2].strip().split(":")[1].strip()
    race_info["track_condition"] = soup.find("span", class_="Item04").text.split(":")[1].strip()

    # 競走馬情報の抽出
    horse_data = []
    table = soup.find("table", class_="RaceTable01 RaceCommon_Table ResultRefund Table_Show_All")
    rows = table.find_all("tr", class_="HorseList")
    for row in rows:
        horse = {}
        cols = row.find_all("td")
        
        horse["order"] = int(cols[0].text.strip())
        horse["frame_number"] = int(cols[1].text.strip())
        horse["horse_number"] = int(cols[2].text.strip())
        horse["horse_id"] = cols[3].find("a")["href"].split("/")[-2]
        horse["horse_name"] = cols[3].find("a").text.strip()
        horse["sex_age"] = cols[4].text.strip()
        horse["weight"] = float(cols[5].text.strip())
        horse["jockey_id"] = cols[6].find("a")["href"].split("/")[-2]
        horse["jockey_name"] = cols[6].text.strip()
        horse["time"] = cols[7].text.strip()
        horse["margin"] = cols[8].text.strip()
        horse["popularity"] = int(cols[9].text.strip())
        horse["odds"] = float(cols[10].text.strip())
        horse["last_3f"] = cols[11].text.strip()

        horse_data.append(horse)

    return race_info, horse_data


def preprocess_data(race_info, horse_data):
    """
    スクレイピングしたデータを前処理する関数
    """
    # レース情報のデータフレーム化
    race_df = pd.DataFrame([race_info])
    race_df["race_date"] = pd.to_datetime(race_df["race_date"], format="%Y年%m月%d日")

    # 競走馬情報のデータフレーム化
    horse_df = pd.DataFrame(horse_data)

    # カテゴリカル変数のエンコーディング
    horse_df["horse_id"] = horse_df["horse_id"].astype("category").cat.codes
    horse_df["jockey_id"] = horse_df["jockey_id"].astype("category").cat.codes

    # 欠損値の処理
    horse_df.fillna({"weight": horse_df["weight"].mean(), "odds": horse_df["odds"].mean()}, inplace=True)

    # タイムを秒数に変換
    horse_df["time"] = horse_df["time"].apply(lambda x: pd.to_timedelta(x).total_seconds())

    # マージンを1位からの差に変換する
    def convert_margin(margin, order):
        if margin.replace(".", "").isdigit():
            return float(margin)
        elif margin == "アタマ":
            return 0.1
        elif margin == "クビ":
            return 0.3
        elif margin == "ハナ":
            return 0.05
        elif margin == "同着":
            return 0.0
        else:
            return 0.0 if order == 1 else None

    horse_df["margin"] = horse_df.apply(lambda x: convert_margin(x["margin"], x["order"]), axis=1)
    horse_df["margin"] = horse_df.groupby("order")["margin"].cumsum()

    # レース情報と競走馬情報の結合
    merged_df = pd.concat([race_df.iloc[np.arange(len(horse_df)).repeat(len(race_df))].reset_index(drop=True),
                           horse_df], axis=1)

    # 秒数を適切な形式に変換
    merged_df["time"] = pd.to_datetime(merged_df["time"], unit="s").dt.strftime("%M:%S.%f").str[:-4]

    return merged_df


def main(race_id):
    race_info, horse_data = scrape_race_results(race_id)
    df = preprocess_data(race_info, horse_data)
    
    # CSVファイルに保存
    df.to_csv(f"race_{race_id}.csv", index=False)
    
    return df


if __name__ == "__main__":
    race_id = "202005010101"  # レースIDを指定
    result_df = main(race_id)
    print(result_df)

しかし日付のところでエラーが発生したので、そのように指摘すると、修正してくれました。

import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
from datetime import datetime
import re

def scrape_race_results(race_id):
    """
    指定されたレースIDの競馬データをスクレイピングする関数
    """
    url = f"https://race.netkeiba.com/race/result.html?race_id={race_id}"
    response = requests.get(url)
    soup = BeautifulSoup(response.content, "html.parser")

    # レース情報の抽出
    race_info = {}
    race_info["race_id"] = race_id
    race_info["race_name"] = soup.find("div", class_="RaceName").text.strip()
    
    race_data = soup.find("div", class_="RaceData02")
    race_info["kaisai_kai"] = race_data.find_all("span")[0].text.strip()
    race_info["kaisai_place"] = race_data.find_all("span")[1].text.strip()
    race_info["kaisai_date"] = race_data.find_all("span")[2].text.strip().replace("日目", "")
    race_info["year"] = race_id[:4]

    race_info["course"] = soup.find("div", class_="RaceData01").text.split("/")[1].strip()
    race_info["distance"] = int(re.findall(r"\d+", race_info["course"])[0])
    race_info["weather"] = soup.find("div", class_="RaceData01").text.split("/")[2].strip().split(":")[1].strip()
    race_info["track_condition"] = soup.find("span", class_="Item04").text.split(":")[1].strip()

    # 競走馬情報の抽出
    horse_data = []
    table = soup.find("table", class_="RaceTable01 RaceCommon_Table ResultRefund Table_Show_All")
    rows = table.find_all("tr", class_="HorseList")
    for row in rows:
        horse = {}
        cols = row.find_all("td")
        
        horse["order"] = int(cols[0].text.strip())
        horse["frame_number"] = int(cols[1].text.strip())
        horse["horse_number"] = int(cols[2].text.strip())
        horse["horse_id"] = cols[3].find("a")["href"].split("/")[-2]
        horse["horse_name"] = cols[3].find("a").text.strip()
        horse["sex_age"] = cols[4].text.strip()
        horse["jockey_weight"] = float(cols[5].text.strip())  # ジョッキーの斤量
        horse["jockey_id"] = cols[6].find("a")["href"].split("/")[-2]
        horse["jockey_name"] = cols[6].text.strip()
        horse["time"] = cols[7].text.strip()
        horse["margin"] = cols[8].text.strip()
        horse["popularity"] = int(cols[9].text.strip())
        horse["odds"] = float(cols[10].text.strip())
        horse["last_3f"] = cols[11].text.strip()
        horse["weight"] = int(cols[14].text.split("(")[0].strip())  # 馬体重

        horse_data.append(horse)

    return race_info, horse_data


def preprocess_data(race_info, horse_data):
    """
    スクレイピングしたデータを前処理する関数
    """
    # レース情報のデータフレーム化
    race_df = pd.DataFrame([race_info])
    race_df["race_date"] = pd.to_datetime(race_df["race_id"].astype(str).str[:8], format="%Y%m%d")

    # 競走馬情報のデータフレーム化
    horse_df = pd.DataFrame(horse_data)

    # ジョッキー名に含まれる記号を削除(カタカナ、ひらがな、全角英数字、半角英数字は残す)
    horse_df["jockey_name"] = horse_df["jockey_name"].str.replace(r'[^ァ-ヶぁ-ゔー0-9A-Za-z一-龥A-Za-z0-9]', '')

    # カテゴリカル変数のエンコーディング
    horse_df["horse_id"] = horse_df["horse_id"].astype("category").cat.codes
    horse_df["jockey_id"] = horse_df["jockey_id"].astype("category").cat.codes

    # 欠損値の処理
    horse_df.fillna({"weight": horse_df["weight"].mean(), "odds": horse_df["odds"].mean()}, inplace=True)

    # タイムを秒数に変換
    def convert_to_seconds(time_str):
        try:
            min_sec, ms = time_str.split(".")
            min, sec = map(int, min_sec.split(":"))
            return min * 60 + sec + float(ms) / 10
        except:
            return None

    horse_df["time"] = horse_df["time"].apply(convert_to_seconds)

    # マージンを1位からの差に変換する
    def convert_margin(margin, order):
        if margin.replace(".", "").isdigit():
            return float(margin)
        elif margin == "アタマ":
            return 0.1
        elif margin == "クビ":
            return 0.3
        elif margin == "ハナ":
            return 0.05
        elif margin == "同着":
            return 0.0
        else:
            return 0.0 if order == 1 else None

    horse_df["margin"] = horse_df.apply(lambda x: convert_margin(x["margin"], x["order"]), axis=1)
    horse_df["margin"] = horse_df.groupby("order")["margin"].cumsum()

    # レース情報と競走馬情報の結合
    race_df_repeated = race_df.loc[race_df.index.repeat(len(horse_df))].reset_index(drop=True)
    merged_df = pd.concat([race_df_repeated, horse_df], axis=1)

    return merged_df


def main(race_id):
    race_info, horse_data = scrape_race_results(race_id)
    df = preprocess_data(race_info, horse_data)
    
    # CSVファイルに保存
    df.to_csv(f"race_{race_id}.csv", index=False)
    
    return df


if __name__ == "__main__":
    race_id = "202005010101"  # レースIDを指定
    result_df = main(race_id)
    print(result_df)

このコードを実行すると以下のようなcsvファイルが得られます。

race_id,race_name,kaisai_kai,kaisai_place,kaisai_date,year,course,distance,weather,track_condition,race_date,order,frame_number,horse_number,horse_id,horse_name,sex_age,jockey_weight,jockey_id,jockey_name,time,margin,popularity,odds,last_3f,weight
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,1,2,2,0,クリアミッション,牡3,56.0,5,大庭,80.4,0.0,5,9.4,37.0,506
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,2,1,1,0,エストラード,牡3,56.0,1,江田照,80.4,0.1,4,8.3,37.3,478
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,3,5,9,0,マッチャパフェ,牝3,54.0,13,武藤,80.4,0.1,6,10.5,38.2,446
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,4,3,4,0,コルニリア,牡3,56.0,4,嘉藤,80.6,,9,19.3,38.1,440
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,5,7,12,0,ローズオブシャロン,牝3,54.0,7,石橋脩,80.6,0.3,3,7.3,38.2,458
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,6,3,5,0,バーンパッション,牡3,56.0,9,大野,80.7,0.3,10,20.2,37.1,468
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,7,6,10,0,ユタカサン,牡3,56.0,2,吉田豊,80.7,0.1,8,17.4,37.9,486
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,8,4,7,0,サイモンバトラー,牡3,56.0,6,田辺,80.7,0.05,7,15.0,37.4,462
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,9,4,6,0,コパノラクラク,牝3,54.0,10,宮崎,80.8,,12,97.2,38.0,442
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,10,8,14,0,コーラルティアラ,牝3,54.0,0,横山典,80.8,0.1,1,3.4,37.3,460
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,11,2,3,0,ストームファルコン,牡3,56.0,14,Mデム,80.9,,2,4.4,37.1,472
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,12,8,15,0,マサノゴールデン,牝3,54.0,3,北村宏,81.4,3.0,11,31.4,38.9,476
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,13,5,8,0,マインレッド,牝3,54.0,11,菅原隆,83.1,,15,451.2,40.0,436
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,14,6,11,0,アイアンカレン,牝3,54.0,8,丹内,83.7,,13,271.9,40.1,408
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,15,7,13,0,サンテンプル,牝3,53.0,12,野中,86.2,,14,389.3,40.0,418

標準化とスケーリング

次に機械学習に入れやすいように標準化とスケーリングをしてもらうように入力すると、数値型のデータだけを標準化とスケーリングしてくれるコードを実装してくれました。

以下にコードを載せます。

import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
from datetime import datetime
import re
from sklearn.preprocessing import StandardScaler, MinMaxScaler

def scrape_race_results(race_id):
    """
    指定されたレースIDの競馬データをスクレイピングする関数
    """
    url = f"https://race.netkeiba.com/race/result.html?race_id={race_id}"
    response = requests.get(url)
    soup = BeautifulSoup(response.content, "html.parser")

    # レース情報の抽出
    race_info = {}
    race_info["race_id"] = race_id
    race_info["race_name"] = soup.find("div", class_="RaceName").text.strip()
    
    race_data = soup.find("div", class_="RaceData02")
    race_info["kaisai_kai"] = race_data.find_all("span")[0].text.strip()
    race_info["kaisai_place"] = race_data.find_all("span")[1].text.strip()
    race_info["kaisai_date"] = race_data.find_all("span")[2].text.strip().replace("日目", "")
    race_info["year"] = race_id[:4]

    race_info["course"] = soup.find("div", class_="RaceData01").text.split("/")[1].strip()
    race_info["distance"] = int(re.findall(r"\d+", race_info["course"])[0])
    race_info["weather"] = soup.find("div", class_="RaceData01").text.split("/")[2].strip().split(":")[1].strip()
    race_info["track_condition"] = soup.find("span", class_="Item04").text.split(":")[1].strip()


    # 競走馬情報の抽出
    horse_data = []
    table = soup.find("table", class_="RaceTable01 RaceCommon_Table ResultRefund Table_Show_All")
    rows = table.find_all("tr", class_="HorseList")
    for row in rows:
        horse = {}
        cols = row.find_all("td")
        
        horse["order"] = int(cols[0].text.strip())
        horse["frame_number"] = int(cols[1].text.strip())
        horse["horse_number"] = int(cols[2].text.strip())
        horse["horse_id"] = cols[3].find("a")["href"].split("/")[-2]
        horse["horse_name"] = cols[3].find("a").text.strip()
        horse["sex_age"] = cols[4].text.strip()
        horse["jockey_weight"] = float(cols[5].text.strip())  # ジョッキーの斤量
        horse["jockey_id"] = cols[6].find("a")["href"].split("/")[-2]
        horse["jockey_name"] = cols[6].text.strip()
        horse["time"] = cols[7].text.strip()
        horse["margin"] = cols[8].text.strip()
        horse["popularity"] = int(cols[9].text.strip())
        horse["odds"] = float(cols[10].text.strip())
        horse["last_3f"] = cols[11].text.strip()
        horse["weight"] = int(cols[14].text.split("(")[0].strip())  # 馬体重

        horse_data.append(horse)

    return race_info, horse_data


def preprocess_data(race_info, horse_data):
    """
    スクレイピングしたデータを前処理する関数
    """
    # レース情報のデータフレーム化
    race_df = pd.DataFrame([race_info])
    race_df["race_date"] = pd.to_datetime(race_df["race_id"].astype(str).str[:8], format="%Y%m%d")

    # 競走馬情報のデータフレーム化
    horse_df = pd.DataFrame(horse_data)

    # ジョッキー名に含まれる記号を削除
    horse_df["jockey_name"] = horse_df["jockey_name"].str.replace(r'[^ァ-ヶぁ-ゔー0-9A-Za-z一-龥A-Za-z0-9]', '')

    # カテゴリカル変数のエンコーディング
    horse_df["horse_id"] = horse_df["horse_id"].astype("category").cat.codes
    horse_df["jockey_id"] = horse_df["jockey_id"].astype("category").cat.codes

    # 欠損値の処理
    horse_df.fillna({"weight": horse_df["weight"].mean(), "odds": horse_df["odds"].mean()}, inplace=True)

    def convert_to_seconds(time_str):
        try:
            min_sec, ms = time_str.split(".")
            min, sec = map(int, min_sec.split(":"))
            return min * 60 + sec + float(ms) / 10
        except:
            return None

    horse_df["time"] = horse_df["time"].apply(convert_to_seconds)

    # マージンを1位からの差に変換する
    def convert_margin(margin, order):
        if margin.replace(".", "").isdigit():
            return float(margin)
        elif margin == "アタマ":
            return 0.1
        elif margin == "クビ":
            return 0.3
        elif margin == "ハナ":
            return 0.05
        elif margin == "同着":
            return 0.0
        else:
            return 0.0 if order == 1 else None

    horse_df["margin"] = horse_df.apply(lambda x: convert_margin(x["margin"], x["order"]), axis=1)
    horse_df["margin"] = horse_df.groupby("order")["margin"].cumsum()

    # レース情報と競走馬情報の結合
    race_df_repeated = race_df.loc[race_df.index.repeat(len(horse_df))].reset_index(drop=True)
    merged_df = pd.concat([race_df_repeated, horse_df], axis=1)

    # 秒数を適切な形式に変換
    merged_df["time"] = pd.to_datetime(merged_df["time"], unit="s").dt.strftime("%M:%S.%f").str[:-4]

    # 数値型の特徴量を選択
    numeric_features = ["distance", "jockey_weight", "weight", "odds", "popularity", "order", "margin", "last_3f"]
    numeric_df = merged_df[numeric_features]

    # 標準化
    scaler_std = StandardScaler()
    scaled_std_df = pd.DataFrame(scaler_std.fit_transform(numeric_df), columns=numeric_features)

    # スケーリング(0から1の範囲に変換)
    scaler_mm = MinMaxScaler()
    scaled_mm_df = pd.DataFrame(scaler_mm.fit_transform(numeric_df), columns=numeric_features)

    # 標準化とスケーリングを適用した特徴量をデータフレームに追加
    merged_df[scaled_std_df.columns + "_std"] = scaled_std_df
    merged_df[scaled_mm_df.columns + "_mm"] = scaled_mm_df

    return merged_df


def main(race_id):
    race_info, horse_data = scrape_race_results(race_id)
    df = preprocess_data(race_info, horse_data)
    
    # CSVファイルに保存
    df.to_csv(f"race_{race_id}.csv", index=False)
    
    return df


if __name__ == "__main__":
    race_id = "202005010101"  # レースIDを指定
    result_df = main(race_id)
    print(result_df)

これを実行すると以下のようなcsvファイルを得られます。

race_id,race_name,kaisai_kai,kaisai_place,kaisai_date,year,course,distance,weather,track_condition,race_date,order,frame_number,horse_number,horse_id,horse_name,sex_age,jockey_weight,jockey_id,jockey_name,time,margin,popularity,odds,last_3f,weight,distance_std,jockey_weight_std,weight_std,odds_std,popularity_std,order_std,margin_std,last_3f_std,distance_mm,jockey_weight_mm,weight_mm,odds_mm,popularity_mm,order_mm,margin_mm,last_3f_mm
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,1,2,2,0,クリアミッション,牡3,56.0,5,大庭,01:20.40,0.0,5,9.4,37.0,506,0.0,1.0423368771244417,1.9523486259750373,-0.5559293635255036,-0.6943650748294136,-1.6201851746019649,-0.49626986991332017,-1.1077291441313792,0.0,1.0,1.0,0.013398838767306836,0.2857142857142857,0.0,0.0,0.0
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,2,1,1,0,エストラード,牡3,56.0,1,江田照,01:20.40,0.1,4,8.3,37.3,478,0.0,1.0423368771244417,0.8352009108394575,-0.563477779011161,-0.9258200997725514,-1.3887301496588271,-0.38598767659924904,-0.8245029425068813,0.0,1.0,0.7142857142857144,0.010942384993300584,0.21428571428571427,0.07142857142857142,0.03333333333333333,0.09677419354838612
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,3,5,9,0,マッチャパフェ,牝3,54.0,13,武藤,01:20.40,0.1,6,10.5,38.2,446,0.0,-0.7970811413304562,-0.4415393350297766,-0.5483809480398463,-0.4629100498862757,-1.1572751247156892,-0.38598767659924904,0.02517566236662622,0.0,0.3333333333333357,0.387755102040817,0.015855292541313088,0.3571428571428571,0.14285714285714285,0.03333333333333333,0.3870967741935498
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,4,3,4,0,コルニリア,牡3,56.0,4,嘉藤,01:20.60,,9,19.3,38.1,440,0.0,1.0423368771244417,-0.680928131130258,-0.4879936241545875,0.23145502494313785,-0.9258200997725514,,-0.06923307150820869,0.0,1.0,0.3265306122448983,0.0355069227333631,0.5714285714285714,0.21428571428571427,,0.35483870967741993
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,5,7,12,0,ローズオブシャロン,牝3,54.0,7,石橋脩,01:20.60,0.3,3,7.3,38.2,458,0.0,-0.7970811413304562,0.037238257171186145,-0.5703399749072131,-1.1572751247156892,-0.6943650748294136,-0.1654232899711067,0.02517566236662622,0.0,0.3333333333333357,0.5102040816326534,0.008709245198749441,0.14285714285714285,0.2857142857142857,0.09999999999999999,0.3870967741935498
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,6,3,5,0,バーンパッション,牡3,56.0,9,大野,01:20.70,0.3,10,20.2,37.1,468,0.0,1.0423368771244417,0.4362195840053218,-0.4818176478481406,0.4629100498862757,-0.4629100498862757,-0.1654232899711067,-1.0133204102565445,0.0,1.0,0.6122448979591839,0.03751674854845913,0.6428571428571428,0.3571428571428571,0.09999999999999999,0.03225806451612989
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,7,6,10,0,ユタカサン,牡3,56.0,2,吉田豊,01:20.70,0.1,8,17.4,37.9,486,0.0,1.0423368771244417,1.154385972306766,-0.5010317963570866,0.0,-0.23145502494313785,-0.38598767659924904,-0.2580505392578785,0.0,1.0,0.795918367346939,0.031263957123715935,0.5,0.4285714285714286,0.03333333333333333,0.2903225806451619
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,8,4,7,0,サイモンバトラー,牡3,56.0,6,田辺,01:20.70,0.05,7,15.0,37.4,462,0.0,1.0423368771244417,0.1968307879048404,-0.5175010665076116,-0.23145502494313785,0.0,-0.4411287732562846,-0.7300942086320464,0.0,1.0,0.5510204081632653,0.025904421616793207,0.4285714285714286,0.5,0.016666666666666666,0.12903225806451601
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,9,4,6,0,コパノラクラク,牝3,54.0,10,宮崎,01:20.80,,12,97.2,38.0,442,0.0,-0.7970811413304562,-0.6011318657634309,0.04657143614787378,0.9258200997725514,0.23145502494313785,,-0.1636418053830436,0.0,0.3333333333333357,0.34693877551020424,0.20946851272889683,0.7857142857142857,0.5714285714285714,,0.32258064516129004
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,10,8,14,0,コーラルティアラ,牝3,54.0,0,横山典,01:20.80,0.1,1,3.4,37.3,460,0.0,-0.7970811413304562,0.11703452253801327,-0.5971025389018164,-1.6201851746019649,0.4629100498862757,-0.38598767659924904,-0.8245029425068813,0.0,0.3333333333333357,0.5306122448979593,0.0,0.0,0.6428571428571428,0.03333333333333333,0.09677419354838612
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,11,2,3,0,ストームファルコン,牡3,56.0,14,Mデム,01:20.90,,2,4.4,37.1,472,0.0,1.0423368771244417,0.595812114738976,-0.5902403430057642,-1.3887301496588271,0.6943650748294136,,-1.0133204102565445,0.0,1.0,0.6530612244897966,0.0022331397945511396,0.07142857142857142,0.7142857142857143,,0.03225806451612989
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,12,8,15,0,マサノゴールデン,牝3,54.0,3,北村宏,01:21.40,3.0,11,31.4,38.9,476,0.0,-0.7970811413304562,0.7554046454726303,-0.4049610538123567,0.6943650748294136,0.9258200997725514,2.8121959295088144,0.6860367994904573,0.0,0.3333333333333357,0.6938775510204085,0.06252791424743188,0.7142857142857143,0.7857142857142857,1.0,0.612903225806452
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,13,5,8,0,マインレッド,牝3,54.0,11,菅原隆,01:23.10,,15,451.2,40.0,436,0.0,-0.7970811413304562,-0.8405206618639123,2.475788783350329,1.6201851746019649,1.1572751247156892,,1.724532872113628,0.0,0.3333333333333357,0.2857142857142865,0.9999999999999998,1.0,0.8571428571428571,,0.9677419354838701
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,14,6,11,0,アイアンカレン,牝3,54.0,8,丹内,01:23.70,,13,271.9,40.1,408,0.0,-0.7970811413304562,-1.957668376999492,1.2453970591881813,1.1572751247156892,1.3887301496588271,,1.8189416059884629,0.0,0.3333333333333357,0.0,0.5995980348369807,0.8571428571428571,0.9285714285714286,,1.0
202005010101,3歳未勝利,1回,東京,1,2020,ダ1300m (左),1300,晴,稍,2020-05-01,15,7,13,0,サンテンプル,牝3,53.0,12,野中,01:26.20,,14,389.3,40.0,418,0.0,-1.7167901505579048,-1.5586870501653565,2.0510188573847024,1.3887301496588271,1.6201851746019649,,1.724532872113628,0.0,0.0,0.10204081632653139,0.8617686467172845,0.9285714285714286,1.0,,0.9677419354838701

まとめ

自分が1行もコードを書かなくても、ホームページの構造を理解し、スクレイピングのコードを実装し、データの前処理を行うコードも実装してくれました。
Claude 3 Opusのコンテキストウィンドウが非常に大きいためホームページのソースを全部入れることができるのが効いていると思いました。(小並感)

この記事が気に入ったらサポートをしてみませんか?