こんにちは!
半年前ほどから競馬予想AIの開発を進めているのですが、今回は話題沸騰中のClaudeCodeを用いて開発を行ってみました。
完全に0の状態からClaudeがどういった方針でアプリを作成していくのか見ていきましょう。
コードの生成
始めにClaudeにアプリ開発の計画を立てさせます。
今回はVScodeの拡張機能でClaudeを使っており、.mdの設定などは一切行っておりません。
Planモードで以下のプロンプトを実行
こんにちは!
あなたには #Rule です。今から #Object を作ってもらいます。
どのように作成していくか、まずは方針を示して
#Rule
-競馬のドメイン知識が豊富
-LightGBMなどの決定木アルゴリズムに精通している
-熟練の特徴量エンジニア
#Object
-netkeiba.comからスクレイピングを実行
-特徴量抽出を行い、回収率100%を超える競馬予想AI
まずは前提の確認ということで「レース種別」「馬券種別」「データ期間」「環境構築」を選択していきます。
感動してのがどの選択肢がどういった長所短所を持つか一言で書いている点。
アプリ開発の障壁をここまでかというほど下げてくれます。
今回は「JRAのレース」を「単勝・複勝」で「3年分」のデータを用いて予測していきます。
ポチポチと選択するだけで長文の実装計画を提示してくれます。
実装計画全文(フォント崩れ有)
競馬予想AI実装計画 Context(背景) netkeiba.comから中央競馬(JRA)の過去3年分のレースデータをスクレイピングし、LightGBMを用いた機械学習モデルで単勝・複勝の予測を行う競馬予想AIを構築します。目標は回収率100%超えを達成することです。競馬予想において回収率100%超えは非常に困難な目標ですが、以下のアプローチで実現を目指します:
ドメイン知識に基づいた高品質な特徴量設計
LightGBMの決定木アルゴリズムによる非線形パターンの学習
適切な閾値設定による賭け対象の選別(全レースに賭けない戦略)
プロジェクト構造
Test/
├── src/
│ ├── scraping/
│ │ ├── init.py
│ │ ├── netkeiba_scraper.py # netkeiba.comスクレイピング
│ │ └── utils.py # スクレイピング補助関数
│ ├── features/
│ │ ├── init.py
│ │ ├── feature_engineering.py # 特徴量生成
│ │ └── horse_features.py # 馬・騎手・調教師の特徴量
│ ├── models/
│ │ ├── init.py
│ │ ├── lightgbm_model.py # LightGBMモデル
│ │ └── evaluation.py # モデル評価・回収率計算
│ └── predict/
│ ├── init.py
│ └── predictor.py # 予測実行
├── data/
│ ├── raw/ # スクレイピングした生データ
│ ├── processed/ # 前処理済みデータ
│ └── models/ # 学習済みモデル
├── notebooks/
│ ├── 01_data_exploration.ipynb # データ探索
│ ├── 02_feature_analysis.ipynb # 特徴量分析
│ └── 03_model_evaluation.ipynb # モデル評価
├── config/
│ └── config.yaml # 設定ファイル
├── requirements.txt # 依存ライブラリ
├── main.py # メインスクリプト
└── README.md # プロジェクト説明
実装ステップ
- 環境構築とプロジェクト初期化
ファイル: requirements.txt
必要なライブラリ:
requests, beautifulsoup4, selenium (スクレイピング)
pandas, numpy (データ処理)
lightgbm (機械学習)
scikit-learn (評価指標)
pyyaml (設定管理)
tqdm (進捗表示)
jupyter, matplotlib, seaborn (分析・可視化)
2. スクレイピングモジュールの実装
ファイル: src/scraping/netkeiba_scraper.py
netkeiba.comから以下のデータを収集:
レース情報:
レースID、開催日、競馬場、距離、馬場状態、天候
レース条件(芝/ダート、クラス、斤量条件)
レース結果(着順、タイム、上がり3F)
馬情報:
馬ID、馬名、性別、年齢、馬体重
血統情報(父馬、母馬、母父馬)
過去成績(勝数、2着数、3着数、総賞金)
騎手・調教師情報:
騎手ID、騎手名、勝率、連対率
調教師ID、調教師名、勝率、連対率
オッズ情報:
単勝オッズ、複勝オッズ(最終オッズ)
スクレイピング戦略:
レース一覧ページから過去3年分のレースIDを取得
各レースIDに対して詳細ページをスクレイピング
Rate limiting実装(1秒あたり1-2リクエスト)でサーバー負荷を配慮
エラーハンドリングとリトライ機構
データをCSV形式でdata/raw/に保存
3. 特徴量エンジニアリング
ファイル: src/features/feature_engineering.py, src/features/horse_features.py
競馬ドメイン知識に基づく特徴量設計:
基本特徴量:
馬齢、性別、馬体重、馬体重変化
騎手勝率、調教師勝率
斤量、斤量差(過去平均との差)
単勝オッズ(対数変換)、人気順位
過去成績特徴量:
過去N走の平均着順(N=3, 5, 10)
同距離・同馬場での成績
同競馬場での成績
連対率、複勝率
最高タイム、平均タイム
前走からの日数
ペース・展開特徴量:
前半3Fのペース(推定)
上がり3Fタイム
脚質(逃げ、先行、差し、追込)の分類
血統特徴量:
父馬ID(カテゴリカル変数)
芝/ダート適性(血統ベース)
距離適性(血統ベース)
相対特徴量:
出走馬内での相対順位(各特徴量ごと)
オッズ偏差値
集計特徴量:
同レース内の平均オッズ、最小オッズ
騎手・調教師の組み合わせ成績
特徴量生成後、data/processed/にfeather形式で保存(高速読み込み)。
- モデル学習(LightGBM)
ファイル: src/models/lightgbm_model.py
ターゲット変数:
単勝: 1着なら1、それ以外0(二値分類)
複勝: 3着以内なら1、それ以外0(二値分類)
モデル設定:
params = {
'objective': 'binary',
'metric': 'auc',
'boosting_type': 'gbdt',
'num_leaves': 31,
'learning_rate': 0.05,
'feature_fraction': 0.8,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': -1,
'seed': 42
}
学習戦略:
時系列分割(Time Series Split): 過去データで学習、未来データで検証
Train: 最初の2年
Validation: 次の6ヶ月
Test: 最後の6ヶ月
Early stopping で過学習を防止
ハイパーパラメータチューニング(Optuna使用)
特徴量重要度分析:
SHAP値による特徴量の影響度分析
重要度の低い特徴量の削除
5. 評価と回収率最適化
ファイル: src/models/evaluation.py
評価指標:
AUC-ROC: モデルの識別能力
的中率: 予測が当たった割合
回収率: (払戻金合計 / 購入金額合計) × 100%
回収率最適化戦略:
予測確率の閾値を調整(例: 確率20%以上の馬のみに賭ける)
オッズと予測確率を組み合わせた期待値計算
Kelly Criterion(ケリー基準)による賭け金最適化
複数モデルのアンサンブル(単勝モデル、複勝モデル)
回収率100%超えの条件:
高確率×高オッズの馬を選別
低オッズ人気馬は避ける(回収率が低い)
見送り判定(賭けない選択)を積極的に活用
6. 予測実行モジュール
ファイル: src/predict/predictor.py
新しいレースデータの入力
特徴量生成
学習済みモデルでの予測
賭け対象の選別と推奨賭け金の計算
結果の出力(CSV、またはコンソール表示)
7. メインスクリプト
ファイル: main.py
コマンドライン引数で以下の操作を実行:
データ収集
python main.py --mode scrape --start-date 2022-01-01 --end-date 2024-12-31
特徴量生成
python main.py --mode feature
モデル学習
python main.py --mode train
予測実行
python main.py --mode predict --race-id <レースID>
評価
python main.py --mode evaluate
8. 分析用ノートブック
ファイル: notebooks/01_data_exploration.ipynb, etc.
データの分布確認
特徴量の相関分析
モデルの予測結果の可視化
回収率シミュレーション
重要な考慮事項
スクレイピングのマナー
robots.txtの確認
User-Agent設定
適切なRate limiting(1-2秒/リクエスト)
エラー時のリトライ制限
データ品質
欠損値の処理(特に馬体重、タイム)
異常値の検出と除外
除外・取消馬の処理
モデルの過学習対策
時系列分割での検証
Early stopping
正則化パラメータの調整
特徴量の選択
現実的な期待値
回収率100%超えは理論的には可能ですが、以下の点に注意:
テストデータでの回収率が実運用でも再現されるとは限らない
オッズは市場の予測を反映しており、単純に勝率だけでは回収率は上がらない
長期的な視点での評価が必要
検証方法
ユニットテスト: 各モジュールの動作確認
データ整合性チェック: スクレイピングしたデータの確認
特徴量の妥当性: 統計量と分布の確認
モデル評価: AUC, 的中率, 回収率を時系列分割で検証
バックテスト: 過去3年間のシミュレーション
感度分析: 閾値やパラメータの変化による回収率の変動確認
クリティカルファイル
実装時に主に作成・編集するファイル:
requirements.txt
src/scraping/netkeiba_scraper.py
src/features/feature_engineering.py
src/features/horse_features.py
src/models/lightgbm_model.py
src/models/evaluation.py
src/predict/predictor.py
main.py
config/config.yaml
まとめ
この計画では、競馬のドメイン知識を活かした特徴量設計と、LightGBMの高い表現力を組み合わせることで、回収率100%超えを目指します。特に重要なのは:
質の高い特徴量: 単なる統計値ではなく、競馬の特性を反映した特徴量
適切な閾値設定: 全レースに賭けるのではなく、期待値が高い馬を選別
時系列での評価: 過去データでの性能が未来でも再現されることを確認
実装順序:環境構築 → スクレイピング → 特徴量設計 → モデル学習 → 評価 → 予測
自分が半年かけて計画した実装計画をものの数秒で提示してくれました。
これはすごいぞClaudeCode…
文句なしなので"auto-accept"を選択します。
するとみるみるうちにファイルが作られ、その中にプログラムが実装されていきます。
計画を立てた段階で自分でタスクを定義し、コマンドラインでのフォルダ操作やプログラムの生成、さらにはREADMEまで作ってくれます。
10分くらいで全てのフォルダが完成しました。
ここまでプロンプトを一度書いたのみ、もちろんプログラミングも一切していません。
出来上がり次第全体像や主要機能、使用手順を説明してくれます。
手順を1つずつ実行していくとともに、完成されたコードをレビューしていきます。
生成されたコードの実行
ここからはClaudeが作成した使用手順に基づき、コードを1つずつ実行し中身を確認していきます。
1.依存ライブラリのインストール
それぞれの手順にターミナルの実行コマンドが記載されており、それを実行するだけでインストールは完了します。
ちなみに中身は以下の通りで、必要なライブラリは余さず入っている印象。
# Web Scraping
requests>=2.31.0
beautifulsoup4>=4.12.0
selenium>=4.15.0
lxml>=4.9.0
# Data Processing
pandas>=2.1.0
numpy>=1.24.0
pyarrow>=14.0.0 # For feather format
# Machine Learning
lightgbm>=4.1.0
scikit-learn>=1.3.0
optuna>=3.4.0 # For hyperparameter tuning
shap>=0.43.0 # For feature importance analysis
# Configuration
pyyaml>=6.0.1
# Utilities
tqdm>=4.66.0
python-dateutil>=2.8.2
# Jupyter & Visualization
jupyter>=1.0.0
matplotlib>=3.8.0
seaborn>=0.13.0
plotly>=5.18.0
# Development & Testing (optional)
pytest>=7.4.0
black>=23.11.0
flake8>=6.1.0
こちらは問題なくインストールが完了。
2. データ収集
スクレイピングは以下のコマンドを実行します。
python main.py --mode scrape --start-date 2022-01-01 --end-date 2024-12-31
実行結果(一部抜粋)は以下の通り。
2026-02-14 09:20:22,213 - src.scraping.netkeiba_scraper - INFO - 2024-11: 4件のレースを発見
2026-02-14 09:20:23,801 - src.scraping.netkeiba_scraper - INFO - 2024-12: 4件のレースを発見
2026-02-14 09:20:25,302 - src.scraping.netkeiba_scraper - INFO - 合計 0 件のレースIDを取得
2026-02-14 09:20:25,303 - src.scraping.netkeiba_scraper - WARNING - レースが見つかりませんでした
2026-02-14 09:20:25,303 - __main__ - INFO - スクレイピングモード完了
レースを見つけることはできていますが、最終的なレースIDが0件になっていますし、実行が明らかにサクサク進みました。
経験上さすがに3年分のスクレイピングは一晩以上かかるはず…
そしてデータもどこにあるかわからないため、ここいらでさすがにコードを確認しましょう。
コード理解
Claudeが用意してくれたREADMEファイルを確認します。
セットアップの章を見ると設定ファイルがconfig/config.yamlに書かれていることを知ります。
今回は真っ先にプログラムが動くかどうかを確認しましたが、皆さんは初めにREADMEをしっかり読みましょう。
config.yamlの中身が以下の通り。
# 競馬予想AI 設定ファイル
# データ収集設定
scraping:
# 期間設定
start_date: "2022-01-01"
end_date: "2024-12-31"
# スクレイピング設定
rate_limit_seconds: 1.5 # リクエスト間隔(秒)
max_retries: 3
timeout: 10
# User-Agent
user_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
# 保存設定
save_interval: 100 # N件ごとに保存
# データ処理設定
data:
# データパス
raw_data_dir: "data/raw"
processed_data_dir: "data/processed"
# 特徴量設定
past_race_windows: [3, 5, 10] # 過去N走の統計を取る
# 欠損値の処理
fill_missing_weight: true
default_weight: 480 # デフォルト馬体重(kg)
# 特徴量エンジニアリング設定
features:
# 基本特徴量
use_basic_features: true
# 過去成績特徴量
use_past_performance: true
# ペース・展開特徴量
use_pace_features: true
# 血統特徴量
use_pedigree_features: true
# 相対特徴量
use_relative_features: true
# 集計特徴量
use_aggregation_features: true
# モデル設定
model:
# LightGBM パラメータ
lightgbm:
objective: "binary"
metric: "auc"
boosting_type: "gbdt"
num_leaves: 31
learning_rate: 0.05
feature_fraction: 0.8
bagging_fraction: 0.8
bagging_freq: 5
max_depth: -1
min_data_in_leaf: 20
lambda_l1: 0.0
lambda_l2: 0.0
verbose: -1
seed: 42
n_estimators: 1000
early_stopping_rounds: 50
# 時系列分割設定
train_years: 2.0 # 学習データ期間(年)
validation_months: 6 # 検証データ期間(月)
test_months: 6 # テストデータ期間(月)
# ハイパーパラメータチューニング
use_optuna: false
optuna_trials: 100
# 評価設定
evaluation:
# 閾値設定
win_threshold: 0.15 # 単勝予測の閾値
place_threshold: 0.25 # 複勝予測の閾値
# 回収率計算設定
bet_unit: 100 # 1回あたりの賭け金(円)
# Kelly Criterion
use_kelly_criterion: true
kelly_fraction: 0.5 # Kelly比率(1.0=フルケリー、0.5=ハーフケリー)
# 予測設定
prediction:
# 予測対象
targets:
- "win" # 単勝
- "place" # 複勝
# モデルファイルパス
model_dir: "data/models"
# 出力設定
output_format: "csv" # csv or json
output_dir: "output"
# ログ設定
logging:
level: "INFO" # DEBUG, INFO, WARNING, ERROR
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
file: "logs/app.log"
User-Agentが一つしかない点くらいしか今のところ気になるところはないですね。
とりあえずデータが保存されているパスは"data/raw"というフォルダらしいですが、このフォルダにデータはありませんでした。
すなわちスクレイピングはうまくいっていないことがわかります。
Claudeにデバッグを行わせます。
コード修正
Planモードで現状の問題を伝え、デバッグの方針を伝えます。
ログから問題を把握し、src\scraping\utils.pyのextract_id_from_url関数が正しく動作しない可能性があると見つけてくれました。
今回は方法1のデバッグ用のスクリプトを用意してもらう方法を選択します。
デバッグスクリプトの作成から実行までを進めてくれます。
途中bashの実行に関しては都度許可を求めてくるのですが、それ以外は本当に自動です。
勝手に進めることもできるけど、上司の顔を立てるため一応承認を求める優秀な部下のようです。
デバッグを行った結果、netkeiba.comのHPがJavaScriptで動的に情報を読み込んでいることを理解してくれました。
自分も昔ここでつまづいたことがありましたが、AIに任せれば一瞬です。
正しいレースIDを取得しようとしていますが、404エラーに悩まされています。

Claudeが必死に解決策を探しています。
APIをたたき始めました。Seleniumを使えば一発のはずなのに、大丈夫でしょうか。
デバッグのコードも次々増えていきます。
ただ人間にはどうすることもできません。この行き過ぎたテクノロジーのように…
しかし、二回くらいの思考で静的URLを取得してくれました!
過去に自分もこのURLを見つけるのに苦労しました。
しかしClaudeにかかれば一発ですね。
正しくHTMLを取得した後に、テーブルが取得できているかを確認してくれています。
Todoリストを作成し、修正とテストまで勝手に行ってくれます。
最後にスクレイパーが動作していることを確認しましたが、どうやらレース情報や人気についてうまくスクレイピングできていないようです。
これについて調べたいというので、許可を与えるとHTMLを勝手に解析し始めました。
HTMLの中から重要なセレクタを見つけたようですね。
結果的に大幅改善されたようで良かったです。
この要領でどんどんClaudeに修正を行ってもらいます。
修正したコードをバッグログで実行してくれるとのことで、実際にスクレイピングを行ってもらいます。
まずは短期間(1か月分のみ)でコードのテストを行ってもらいます。
しかしWARNINGが発生しており、保存失敗のレースが2/3ほど出ているようです。
地方競馬のレースもスクレイピングしていることが原因のようです。
まずは中央競馬のレースに集中させましょう。
何度か実行を繰り返し無事地方競馬を除外するフィルタが動きました。
これでスクレイピングもばっちりです!
実行結果の確認(スクレイピング)
data\rawを確認すると、一か月分の取得データがcsvとして保存されています。
races_{YYYYMMDD}{HHMMSS}{horses|races}.csvという命名ルールのようで、racesにはレース情報、horsesにはレース結果が保存されていますね。
races_20260214_120521_horses.csv

races_20260214_120521_races.csv

_horses.csvを見ていて気付いたのですが、horse_idと
これでhorse_id, jockey_id, trainer_idを正しくスクレイピングできるようになりました。
期間を1か月のみに限定していましたが、少し広げて正しくスクレイピングできているか確認してみました。
(.venv) PS C:\Users\yasak\デスクトップ\KeibaAI_wClaude> python main.py --mode scrape --start-date 2022-01-01 --end-date 2022-03-31
2026-02-14 15:00:50,984 - __main__ - INFO - モード: scrape
2026-02-14 15:00:50,984 - __main__ - INFO - スクレイピングモード開始
2026-02-14 15:00:50,984 - src.scraping.netkeiba_scraper - INFO - データ収集開始
2026-02-14 15:00:50,984 - src.scraping.netkeiba_scraper - INFO - レース一覧取得開始: 2022-01-01 ~ 2022-03-31
2026-02-14 15:00:53,305 - src.scraping.netkeiba_scraper - INFO - 2022-01: 36件のJRAレースを発見(全100件中)
2026-02-14 15:00:56,624 - src.scraping.netkeiba_scraper - INFO - 2022-02: 36件のJRAレースを発見(全100件中)
2026-02-14 15:01:00,121 - src.scraping.netkeiba_scraper - INFO - 2022-03: 0件のJRAレースを発見(全98件中)
2026-02-14 15:01:01,621 - src.scraping.netkeiba_scraper - INFO - 合計 72 件のレースIDを取得
3月でレースの発見件数が0件…
どうやらここでも正しくページを取得できていないようですね。
Claudeに解析させた結果、問題はサーバーに送る一度のデータ取得件数を100件で制限している箇所のようです。
取得した100件が地方競馬のデータでしたらすべてが除外されてしまいますし、そもそも取得できていたと思っていたレースもほんの一部なのかもしれません。
100件以上のデータを送れるように修正した結果がこちらです。
2026-02-14 15:10:25,449 - src.scraping.netkeiba_scraper - INFO - データ収集開始
2026-02-14 15:10:25,449 - src.scraping.netkeiba_scraper - INFO - レース一覧取得開始: 2022-01-01 ~ 2022-03-31
2026-02-14 15:11:16,219 - src.scraping.netkeiba_scraper - INFO - 2022-01: 643件のJRAレースを発見(全1370件中)
2026-02-14 15:12:02,163 - src.scraping.netkeiba_scraper - INFO - 2022-02: 638件のJRAレースを発見(全1208件中)
2026-02-14 15:12:51,493 - src.scraping.netkeiba_scraper - INFO - 2022-03: 599件のJRAレースを発見(全1446件中)
2026-02-14 15:12:51,493 - src.scraping.netkeiba_scraper - INFO - 合計 1880 件のレースIDを取得
思った以上にレース数がありましたね。
この状態でスクレイピングを進めていましたが、track_condition, race_classが抽出できていないことに気づきます。
こいつは修正を行い再度スクレイピングを行います。
この後も競馬場コードがうまくスクレイピングできていなかったり、ちょいちょい問題はありましたがClaude Codeがあればなんのその。
無事スクレイピングできるようになったため、続いて特徴量の加工に移ります。
記事として膨大になってきたため、ここらで一度筆を置こうと思います。
もともとChatGPTとやり取りしながら競馬予想AIを作っていましたが、こんな便利なものを知ってしまうともう戻れません。
というかClaude CodeとともにVibe Codingをしていくのが今後の主流になっていくのではないのでしょうか。
したがって今後はこのシリーズを続けていこうと思います。
こうご期待。
















