【初心者向け】コピペだけでできるAIを使った競馬予想

この記事では、競馬好きの方に人気のサイト「netkeiba.com」から、Pythonを使って競走馬の戦績を自動で取得する方法を、やさしく解説します。

事前準備

まずはPythonで必要なライブラリをインストールしましょう。ターミナルやコマンドプロンプトで以下を実行します:

pip install selenium beautifulsoup4 pandas openpyxl requests

コード全体(完全版)

以下が、netkeibaから競走馬の戦績を取得するPythonコードの全体です。

import requests
from bs4 import BeautifulSoup
import pandas as pd
import os
from openpyxl import load_workbook
import re
import time

# 定数の定義
OUTPUT_PATH = r"C:\Users\User\Desktop"
# RACE_URL は使わず、後で動的に生成します

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
    "Accept-Language": "ja,en;q=0.9"
}

# 小数点1桁に丸める対象の数値項目
SPECIAL_COLUMNS = {"斤量", "オッズ", "着差", "上り", "賞金"}
# 強制的にfloat型に変換する対象(「タイム」は必ずfloatで保存する)
FORCE_FLOAT_COLUMNS = {"タイム"}


def convert_value(x):
    """
    各セルの値が数字のみの場合、(カンマ除去後)floatまたはintに変換する。
    例:"55" → int(55)、"5.6" → float(5.6)
    """
    if isinstance(x, str):
        x_clean = x.replace(',', '').strip()
        if re.fullmatch(r'\d+(\.\d+)?', x_clean):
            return float(x_clean) if '.' in x_clean else int(x_clean)
        else:
            return x
    return x


def convert_df_numeric_full(df):
    """
    DataFrame 内の各セルについて、値が数字のみの場合は数値型に変換し、
    SPECIAL_COLUMNS の項目については float にして小数点1桁に丸め、
    FORCE_FLOAT_COLUMNS の項目は必ず float 型に変換する。
    """
    df = df.applymap(convert_value)
    for col in SPECIAL_COLUMNS:
        if col in df.columns:
            df[col] = df[col].apply(lambda x: round(float(x), 1) if isinstance(x, (int, float)) else x)
    for col in FORCE_FLOAT_COLUMNS:
        if col in df.columns:
            df[col] = df[col].apply(lambda x: float(x) if isinstance(x, (int, float)) else x)
    return df


def fetch_race_data(url):
    """
    Selenium を用いて、JavaScript 実行後のレースページの HTML を取得する
    """
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    chrome_options = Options()
    chrome_options.add_argument("--headless")
    chrome_options.add_argument("--disable-gpu")
    driver = webdriver.Chrome(options=chrome_options)
    driver.get(url)
    time.sleep(5)  # JavaScript実行待ち(必要に応じて調整)
    html = driver.page_source
    driver.quit()
    return BeautifulSoup(html, "html.parser")


def get_race_title(soup):
    """レースのタイトル情報を取得する関数(既存の処理)"""
    race_name = soup.find("h1", class_="RaceName").text.strip()
    race_data = soup.find("div", class_="RaceData02")
    spans = race_data.find_all("span")
    title = [
        race_name,
        spans[0].text.strip(),
        spans[1].text.strip(),
        spans[2].text.strip()
    ]
    return " ".join(title)


def get_race_number(soup):
    """
    レース番号の要素 <span class="RaceNum"> のテキストを取得します。
    """
    race_num_element = soup.find("span", class_="RaceNum")
    if race_num_element:
        return race_num_element.get_text(strip=True)
    return ""


def get_additional_race_info(soup):
    """
    レースページから「レース名」「場名」「芝・ダート」「距離」「天候」「馬場」を取得する関数
    ※ h1, RaceData01, RaceData02 の各要素から取得
    """
    h1 = soup.find("h1", class_="RaceName")
    race_name = h1.find(string=True, recursive=False).strip() if h1 and h1.find(string=True, recursive=False) else ""
    
    race_data02 = soup.find("div", class_="RaceData02")
    field_name = race_data02.find_all("span")[1].text.strip() if race_data02 and len(race_data02.find_all("span")) > 1 else ""
    
    race_data01 = soup.find("div", class_="RaceData01")
    if race_data01:
        span_list = race_data01.find_all("span")
        if span_list:
            surface_distance_text = span_list[0].text.strip()
            m = re.match(r'([芝ダート]+)(\d+m?)', surface_distance_text)
            if m:
                surface = m.group(1)
                distance = re.sub(r'\D', '', m.group(2))
            else:
                surface = ""
                distance = ""
        else:
            surface = ""
            distance = ""
        race_data01_text = race_data01.get_text(" ", strip=True)
        m_weather = re.search(r'天候:([^\s/]+)', race_data01_text)
        weather = m_weather.group(1).strip() if m_weather else ""
        m_track = re.search(r'馬場:([^\s/]+)', race_data01_text)
        track = m_track.group(1).strip() if m_track else ""
    else:
        surface = ""
        distance = ""
        weather = ""
        track = ""
        
    return {
        "レース名": race_name,
        "場名": field_name,
        "芝・ダート": surface,
        "距離": distance,
        "天候": weather,
        "馬場": track
    }


def parse_race_table(soup):
    """
    レーステーブルを解析して DataFrame を作成する関数
    不要な項目(例:"印", "馬メモ切替", "お気に入り馬", "マスターレース別馬メモ切替")は除外し、
    "オッズ更新_リンク" は取得しないように調整。
    """
    table = soup.find("table")
    if not table:
        print("テーブルが見つかりませんでした。")
        return None

    # ヘッダーは <thead> 内の最初の行を利用
    thead = table.find("thead")
    if not thead:
        print("theadが見つかりませんでした。")
        return None
    first_header_row = thead.find_all("tr")[0]
    all_headers = [th.text.strip() for th in first_header_row.find_all("th")]

    # 除外するヘッダー
    ignored_headers = {"印", "馬メモ切替", "お気に入り馬", "マスターレース別馬メモ切替", "更新"}
    # 除外するリンクヘッダー
    ignored_link_headers = {
        "枠_リンク", "馬番_リンク", "印_リンク", "性齢_リンク", "斤量_リンク",
        "馬体重(増減)_リンク", "更新_リンク", "人気_リンク",
        "オッズ 更新_リンク"
    }

    keep_indices = []
    kept_headers = []
    for i, header in enumerate(all_headers):
        if header in ignored_headers:
            continue
        keep_indices.append(i)
        kept_headers.append(header)

    # リンクヘッダーを決定
    kept_link_headers = []
    for header in kept_headers:
        link_header = f"{header}_リンク"
        if link_header in ignored_link_headers:
            kept_link_headers.append(None)
        else:
            kept_link_headers.append(link_header)

    final_columns = kept_headers + [lh for lh in kept_link_headers if lh]

    tbody = table.find("tbody")
    if not tbody:
        print("tbodyが見つかりませんでした。")
        return None
    data_rows = tbody.find_all("tr")

    data = []
    for row in data_rows:
        cells = row.find_all(["td", "th"])
        row_text = [cells[i].text.strip() for i in keep_indices if i < len(cells)]
        row_links = []
        for idx, i in enumerate(keep_indices):
            if i < len(cells):
                a_tag = cells[i].find("a")
                link = a_tag["href"] if a_tag else ""
                if kept_link_headers[idx] is not None:
                    row_links.append(link)
        data.append(row_text + row_links)

    df = pd.DataFrame(data, columns=final_columns)
    # 念のため「オッズ 更新_リンク」および「オッズ更新_リンク」を削除
    df.drop(
        columns=[col for col in df.columns if col in ["オッズ 更新_リンク", "オッズ更新_リンク"]],
        inplace=True,
        errors="ignore"
    )
    return df


def get_horse_data(link):
    """
    個別の馬の戦績データと血統情報を取得する関数  
    ・馬ページからレース結果テーブル(class="db_h_race_results nk_tb_common")を取得  
    ・同一ページ内の血統情報(<div class="db_prof_box">内の<table class="blood_table">)も取得  
    ※血統は、テーブル内の各リンクテキストをリストとして返します。
    """
    response = requests.get(link, headers=HEADERS)
    if response.status_code != 200:
        print(f"ステータスコードが200ではありません。HTTPコード: {response.status_code}")
        return None

    soup = BeautifulSoup(response.content, "html.parser")
    
    # --- 血統情報の取得 ---
    bloodline = []
    blood_div = soup.find("div", class_="db_prof_box")
    if blood_div:
        blood_table = blood_div.find("table", class_="blood_table")
        if blood_table:
            a_tags = blood_table.find_all("a")
            bloodline = [a.text.strip() for a in a_tags]
    # --- ここまで ---
    
    table = soup.find("table", class_="db_h_race_results nk_tb_common")
    if not table:
        print("指定されたテーブルが見つかりませんでした。")
        return None

    header = [th.text.strip() for th in table.find("thead").find_all("th")]
    header = [h if "オッズ" not in h else "オッズ" for h in header]
    rows = table.find("tbody").find_all("tr")
    data = [[col.text.strip() for col in row.find_all("td")] for row in rows]
    
    df = pd.DataFrame(data, columns=header)
    return df, bloodline


def get_jockey_name(url):
    """
    指定されたURLから騎手名を取得する関数  
    <div class="db_head_name fc">内の<h1>要素の、&nbsp;より前の部分のテキストを返します。
    """
    response = requests.get(url, headers=HEADERS)
    if response.status_code != 200:
        print(f"騎手ページの取得に失敗しました。HTTPコード: {response.status_code}")
        return None
    soup = BeautifulSoup(response.content, "html.parser")
    jockey_div = soup.find("div", class_="db_head_name fc")
    if jockey_div:
        h1 = jockey_div.find("h1")
        if h1:
            text = h1.get_text(strip=True)
            if "\xa0" in text:
                return text.split("\xa0")[0]
            else:
                return text
    return None


def get_trainer_name(url):
    """
    指定されたURLから厩舎名を取得する関数  
    <div class="db_head_name fc"> 内の <h1> 要素のテキストを取得し、  
    &nbsp;(実際はUnicodeの \xa0 )以降の文字列を除外して返す。
    """
    response = requests.get(url, headers=HEADERS)
    if response.status_code != 200:
        print(f"厩舎ページの取得に失敗しました。HTTPコード: {response.status_code}")
        return None

    soup = BeautifulSoup(response.content, "html.parser")
    trainer_div = soup.find("div", class_="db_head_name fc")
    if trainer_div:
        h1 = trainer_div.find("h1")
        if h1:
            text = h1.get_text(strip=True)
            trainer_name = text.split('\xa0')[0].strip()
            return trainer_name
    return None


def save_excel_with_title(df, path, title):
    """
    DataFrame を Excel に保存し、ブックのタイトルを設定する関数  
    ※1枚目のシート名は「出走馬一覧」として保存します。
    """
    file_name = f"{title}.xlsx"
    full_path = os.path.join(path, file_name)
    sheet_name = "出走馬一覧"
    
    df = convert_df_numeric_full(df)
    df.to_excel(full_path, index=False, sheet_name=sheet_name)
    
    wb = load_workbook(full_path)
    wb.properties.title = title
    wb.save(full_path)
    
    print(f"データを {sheet_name} シートに保存し、ブックのタイトルを '{title}' に設定しました: {full_path}")


def get_race_id(url):
    """
    URL 中の "race_id=" の後ろの数字部分を抽出して返す
    """
    m = re.search(r"race_id=(\d+)", url)
    if m:
        return m.group(1)
    return None


def parse_oikiri_table(soup):
    """
    オイキリ画面の評価テーブル(id="All_Oikiri_Table")から、
    各馬の評価情報(セル4: 評価コメント、セル5: 評価ランク)を抽出し、
    馬名をキーとした辞書を返す。
    ※1行目はヘッダー行として除外し、2行目以降のデータ行を処理します。
    返却値は、 {馬名: (評価コメント, 評価ランク)} となります。
    """
    table = soup.find("table", id="All_Oikiri_Table")
    if not table:
        print("オイキリテーブルが見つかりませんでした。")
        return {}
    tbodies = table.find_all("tbody")
    if tbodies:
        tbody = tbodies[0]
    else:
        tbody = table
    rows = tbody.find_all("tr")
    if not rows:
        print("オイキリテーブル内に行が見つかりませんでした。")
        return {}
    if rows[0].find_all("th"):
        data_rows = rows[1:]
    else:
        data_rows = rows
    evaluation_dict = {}
    for row in data_rows:
        cells = row.find_all("td")
        if len(cells) < 7:
            continue
        horse_name_tag = cells[3].find("a")
        if not horse_name_tag:
            continue
        horse_name = horse_name_tag.text.strip()
        eval_comment = cells[4].get_text(strip=True)
        eval_rank = cells[5].get_text(strip=True)
        evaluation_dict[horse_name] = (eval_comment, eval_rank)
    return evaluation_dict


def main():
    # 例として、race_id の先頭10桁 "2025060207" を固定し、下2桁を09~09にループします
    BASE_RACE_ID_PREFIX = "2025050206"
    for num in range(1~12, 2~12):
        race_id = BASE_RACE_ID_PREFIX + f"{num:02d}"
        race_url = f"https://race.netkeiba.com/race/shutuba.html?race_id={race_id}"
        print(f"\n====================\n現在処理中のレース: {race_id}\nURL: {race_url}\n====================")
        
        soup = fetch_race_data(race_url)
        if soup:
            race_title = get_race_title(soup)
            additional_info = get_additional_race_info(soup)
            
            # レース番号を取得し、ファイル名の先頭に追加する
            race_num = get_race_number(soup)
            if race_num:
                full_title = f"{race_num} {race_title}"
            else:
                full_title = race_title

            df_race = parse_race_table(soup)
            if df_race is not None:
                new_columns = ["レース名", "場名", "芝・ダート", "距離", "天候", "馬場"]
                for col in new_columns:
                    df_race[col] = additional_info[col]
                
                cols = list(df_race.columns)
                if "枠" in cols:
                    new_order = []
                    for col in cols:
                        if col == "枠":
                            new_order.extend(new_columns)
                            new_order.append(col)
                        elif col in new_columns:
                            continue
                        else:
                            new_order.append(col)
                    df_race = df_race[new_order]
                else:
                    df_race = df_race[new_columns + cols]
                
                df_race.rename(columns={"馬名": "馬", "馬名_リンク": "馬_リンク"}, inplace=True)
                
                # 「性齢」列が存在する場合、「性」と「齢」に分割する
                if "性齢" in df_race.columns:
                    idx = df_race.columns.get_loc("性齢")
                    df_race.insert(idx, "性", df_race["性齢"].astype(str).str[0])
                    df_race.insert(idx+1, "齢", df_race["性齢"].astype(str).str[1:])
                    df_race.drop("性齢", axis=1, inplace=True)
                
                if "馬体重(増減)" in df_race.columns:
                    def extract_increase(s):
                        if isinstance(s, str):
                            m = re.search(r'\((.*?)\)', s)
                            return m.group(1) if m else ""
                        return ""
                    df_race["増減"] = df_race["馬体重(増減)"].apply(extract_increase)
                    df_race["馬体重(増減)"] = df_race["馬体重(増減)"].apply(lambda s: re.sub(r'\(.*?\)', '', s).strip() if isinstance(s, str) else s)
                    df_race.rename(columns={"馬体重(増減)": "馬体重"}, inplace=True)
                    cols = list(df_race.columns)
                    if "馬体重" in cols:
                        idx = cols.index("馬体重")
                        if "増減" in cols:
                            cols.remove("増減")
                        cols.insert(idx+1, "増減")
                        df_race = df_race[cols]
                # Excel に保存(ブック名にレース番号が先頭に入る)
                save_excel_with_title(df_race, OUTPUT_PATH, full_title)

            file_path = os.path.join(OUTPUT_PATH, f"{full_title}.xlsx")
            df_all = pd.read_excel(file_path, sheet_name="出走馬一覧")
            
            url_column, horse_name_column = "馬_リンク", "馬"
            if url_column not in df_all.columns or horse_name_column not in df_all.columns:
                print(f"列 '{url_column}' または '{horse_name_column}' が見つかりませんでした。")
                continue

            all_horses_data = []
            bloodline_dict = {}
            for idx, row in df_all.iterrows():
                horse = row[horse_name_column]
                url = row[url_column]
                if pd.isna(url) or url == "" or pd.isna(horse) or str(horse).strip() == "":
                    print("URLまたは馬名が空です。スキップします。")
                    continue
                print(f"現在処理中の馬: {horse}")
                result = get_horse_data(url)
                if result is not None:
                    horse_data_df, bloodline = result
                    if "馬体重" in horse_data_df.columns:
                        def extract_increase(s):
                            if isinstance(s, str):
                                m = re.search(r'\((.*?)\)', s)
                                return m.group(1) if m else ""
                            return ""
                        horse_data_df["増減"] = horse_data_df["馬体重"].apply(extract_increase)
                        horse_data_df["馬体重"] = horse_data_df["馬体重"].apply(lambda s: re.sub(r'\(.*?\)', '', s).strip() if isinstance(s, str) else s)
                        cols = list(horse_data_df.columns)
                        if "馬体重" in cols:
                            idx_mt = cols.index("馬体重")
                            if "増減" in cols:
                                cols.remove("増減")
                            cols.insert(idx_mt+1, "増減")
                            horse_data_df = horse_data_df[cols]
                    horse_data_df.insert(0, '馬', horse)
                    horse_data_df = convert_df_numeric_full(horse_data_df)
                    all_horses_data.append(horse_data_df)
                    bloodline_dict[idx] = bloodline
                    print(f"{horse} の戦績データと血統データを取得しました。")
                else:
                    print(f"{horse} のリンク先からデータを取得できませんでした。")
            
            blood_cols = ["父", "父の父", "父の母", "母", "母の父", "母の母"]
            for col in blood_cols:
                df_all[col] = ""
            for idx, row in df_all.iterrows():
                bloodline_list = bloodline_dict.get(idx, [])
                if len(bloodline_list) > 0:
                    df_all.at[idx, "父"] = bloodline_list[0]
                if len(bloodline_list) > 1:
                    df_all.at[idx, "父の父"] = bloodline_list[1]
                if len(bloodline_list) > 2:
                    df_all.at[idx, "父の母"] = bloodline_list[2]
                if len(bloodline_list) > 3:
                    df_all.at[idx, "母"] = bloodline_list[3]
                if len(bloodline_list) > 4:
                    df_all.at[idx, "母の父"] = bloodline_list[4]
                if len(bloodline_list) > 5:
                    df_all.at[idx, "母の母"] = bloodline_list[5]
            
            cols = list(df_all.columns)
            if "厩舎_リンク" in cols:
                trainer_link_column = "厩舎_リンク"
                trainer_column = "厩舎"
                for idx, row in df_all.iterrows():
                    url = row[trainer_link_column]
                    if pd.isna(url) or url == "":
                        continue
                    trainer_name = get_trainer_name(url)
                    if trainer_name:
                        df_all.at[idx, trainer_column] = trainer_name
                        print(f"厩舎 {trainer_name} を取得しました。")
                    else:
                        print(f"厩舎名の取得に失敗しました: {url}")
                
                insert_at = cols.index("厩舎_リンク") + 1
                remaining = [c for c in cols if c not in blood_cols]
                new_order = remaining[:insert_at] + blood_cols + remaining[insert_at:]
                df_all = df_all[new_order]

            jockey_link_column = "騎手_リンク"
            jockey_column = "騎手"
            if jockey_link_column in df_all.columns:
                for idx, row in df_all.iterrows():
                    url = row[jockey_link_column]
                    if pd.isna(url) or url == "":
                        continue
                    jockey_name = get_jockey_name(url)
                    if jockey_name:
                        df_all.at[idx, jockey_column] = jockey_name
                        print(f"騎手 {jockey_name} を取得しました。")
                    else:
                        print(f"騎手名の取得に失敗しました: {url}")

            with pd.ExcelWriter(file_path, engine="openpyxl", mode="a", if_sheet_exists="replace") as writer:
                df_all.to_excel(writer, sheet_name="出走馬一覧", index=False)
            
            oikiri_url = f"https://race.netkeiba.com/race/oikiri.html?race_id={race_id}&rf=race_submenu"
            print(f"オイキリURL: {oikiri_url}")
            response = requests.get(oikiri_url, headers=HEADERS)
            if response.status_code != 200:
                print(f"オイキリページの取得に失敗しました。HTTPコード: {response.status_code}")
            else:
                soup_oikiri = BeautifulSoup(response.content, "html.parser")
                eval_dict = parse_oikiri_table(soup_oikiri)
                if not eval_dict:
                    print("オイキリから評価データを取得できませんでした。")
                else:
                    comment_list = []
                    rank_list = []
                    for idx, row in df_all.iterrows():
                        horse = str(row.get("馬", "")).strip()
                        if horse in eval_dict:
                            raw_comment, raw_rank = eval_dict[horse]
                            comment = re.sub(r'[A-Za-z]', '', raw_comment)
                            rank = ''.join(re.findall(r'[A-Za-z]', raw_rank))
                        else:
                            comment = ""
                            rank = ""
                        comment_list.append(comment)
                        rank_list.append(rank)
                    df_all["調教タイム評価‗コメント"] = comment_list
                    df_all["調教タイム評価‗ランク"] = rank_list
                    cols = list(df_all.columns)
                    if "人気" in cols:
                        idx_pop = cols.index("人気") + 1
                        for col in ["調教タイム評価‗コメント", "調教タイム評価‗ランク"]:
                            if col in cols:
                                cols.remove(col)
                        cols.insert(idx_pop, "調教タイム評価‗コメント")
                        cols.insert(idx_pop+1, "調教タイム評価‗ランク")
                        df_all = df_all[cols]
                    else:
                        print("「人気」列が見つからなかったため、評価関連の列は末尾に配置されます。")
                    with pd.ExcelWriter(file_path, engine="openpyxl", mode="a", if_sheet_exists="replace") as writer:
                        df_all.to_excel(writer, sheet_name="出走馬一覧", index=False)
                    print("調教タイム評価のコメントとランクを追加しました。")
            
            if all_horses_data:
                combined_data = pd.concat(all_horses_data, ignore_index=True)
                if "距離" in combined_data.columns:
                    distance_idx = combined_data.columns.get_loc("距離")
                    combined_data.insert(distance_idx, "芝・ダート", 
                                           combined_data["距離"].apply(lambda x: x[0] if isinstance(x, str) and x and x[0] in {"芝", "ダ"} else ""))
                    combined_data["距離"] = combined_data["距離"].apply(lambda x: re.sub(r'^[芝ダ]', '', x) if isinstance(x, str) else x)
                
                if "開催" in combined_data.columns:
                    combined_data["開催"] = combined_data["開催"].apply(
                        lambda x: re.sub(r'^\d+|\d+$', '', x) if isinstance(x, str) else x
                    )
                
                combined_data = convert_df_numeric_full(combined_data)
                # キーワードに部分一致する列名を削除する
                keys_to_drop = ["馬場指数", "タイム指数", "厩舎コメント", "備考", "勝ち馬(2着馬)", "賞金","映像"]
                drop_list = [col for col in combined_data.columns if any(key in col for key in keys_to_drop)]
                combined_data.drop(columns=drop_list, inplace=True)
                
                with pd.ExcelWriter(file_path, engine="openpyxl", mode="a", if_sheet_exists="replace") as writer:
                    combined_data.to_excel(writer, sheet_name="全馬戦績", index=False)
                
                print("全ての馬の戦績データが '全馬戦績' シートに保存されました。")
            else:
                print("データを取得できた馬がありませんでした。")
                
        else:
            print("レースページの取得に失敗しました。")


if __name__ == "__main__":
    main()

保存場所の設定

最初に保存する場所を設定する必要があります。

OUTPUT_PATH = r"C:\Users\User\Desktop"

太字の部分に保存したいフォルダのパスを入れてください。

取得したいレースの設定

途中以下のコードがありますが、netkeibaの取得したいレースのURLの数字にコピペして書き換えてください。

例えば、https://race.netkeiba.com/race/shutuba.html?race_id=202505020611のid=の後ろの数字をBASE_RACE_ID_PREFIX = “2025050206“にコピペしてください。

その後、数字の後ろ二つを削除してください。上のレースURLでだと、BASE_RACE_ID_PREFIX = “202505020611“の11の部分を削除。

次に、for num in range(1~12, 2~12):の数字を入力してください。

11Rを取得したい場合は、for num in range(11, 12):と入力してください。

1R~12Rを取得したい場合は、for num in range(1, 13):

def main():
    # 例として、race_id の先頭10桁 "2025060207" を固定し、下2桁を09~09にループします
    BASE_RACE_ID_PREFIX = "2025050206"
    for num in range(1~12, 2~12):
        race_id = BASE_RACE_ID_PREFIX + f"{num:02d}"
        race_url = f"https://race.netkeiba.com/race/shutuba.html?race_id={race_id}"
        print(f"\n====================\n現在処理中のレース: {race_id}\nURL: {race_url}\n====================")

実行結果(例)

レース名	場名	芝・ダート	距離	天候	馬場	枠	馬番	馬	性	齢	斤量	騎手	厩舎	馬体重	増減	"オッズ

更新" 人気 調教タイム評価‗コメント 調教タイム評価‗ランク
NHKマイルC 東京 芝 1600 晴 良 1 1 モンドデラモーレ 牡 3 57 戸崎圭太 千葉直人 19 8 動き軽快 B
NHKマイルC 東京 芝 1600 晴 良 1 2 ショウナンザナドゥ 牝 3 55 池添謙一 松下武士 80.3 14 上積無し C


AIにどの馬が勝つのか聞いてみる

chatgptやgemini、claudeなどに取得したエクセルをコピペ、アップロード等をして聞いてみてください。

聞き方は人によってさまざまあると思いますので、お任せします。

注意点

  • 商用利用はNG:netkeibaはスクレイピングの商用利用を禁止しています。個人の学習・研究目的にとどめましょう。
  • アクセスしすぎ注意:短時間で大量アクセスするとブロックされる可能性も。必要に応じて time.sleep() を入れてください。

まとめ

今回は、Pythonでnetkeibaから競走馬の戦績をスクレイピングする方法を、コード付きで分かりやすく解説しました。

データが取れれば、以下のような分析も可能になります:

  • 騎手ごとの勝率ランキング
  • 距離別の得意不得意
  • 馬場状態との相性 など

「Python × 競馬」はとても面白い分野ですので、ぜひチャレンジしてみてください!

コメント

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