Skip to content

モジュール設計

scraper/race_calendar.py

当日のJRAレーススケジュールをnetkeiba.comから取得する。

データクラス

python
@dataclass
class RaceInfo:
    race_id: str      # "202609010101" (12桁)
    venue_code: str   # "09" (会場コード)
    venue_name: str   # "阪神"
    race_num: int     # 1〜12
    post_time: str    # "10:05"
    race_name: str    # "3歳未勝利"

主要関数

関数引数戻り値説明
fetch_today_racesdate_str: strlist[RaceInfo]指定日の全JRAレース一覧を取得

取得URL

https://race.netkeiba.com/top/race_list_sub.html?kaisai_date=YYYYMMDD

会場コード一覧

コード会場コード会場
01札幌06中山
02函館07中京
03福島08京都
04新潟09阪神
05東京10小倉

scraper/odds_fetcher.py

netkeiba.comのJSON APIから単勝オッズを取得する。最重要モジュール。

データクラス

python
@dataclass
class OddsData:
    horse_number: int  # 馬番
    odds_win: float    # 単勝オッズ
    popularity: int    # 人気順位

主要関数

関数引数戻り値説明
fetch_win_oddsrace_id: strdict[int, OddsData]単勝オッズを取得(馬番→OddsData)

取得URL

https://race.netkeiba.com/api/api_get_jra_odds.html?race_id=XXXX&type=1&action=update

ポイント

  • JSON APIのため Playwright不要、requestsのみで取得可能
  • JSONP形式のレスポンスにも対応(関数名を自動除去)
  • 取消馬(オッズ0)は自動スキップ

scraper/horse_info.py

出馬表から馬番→馬名・騎手名マッピングを取得する。

データクラス

python
@dataclass
class HorseEntry:
    number: int    # 馬番
    name: str      # 馬名
    jockey: str    # 騎手名

主要関数

関数引数戻り値説明
fetch_horse_entriesrace_id: strdict[int, HorseEntry]出馬表を取得(キャッシュ付き)
clear_cacheなしNoneキャッシュクリア

キャッシュ戦略

  • race_id 単位でメモリキャッシュ
  • 出馬表は開催中変わらないため、1レース1回の取得で十分

analyzer/snapshot_store.py

オッズスナップショットの保存と取得を管理する。

データクラス

python
@dataclass
class OddsSnapshot:
    race_id: str
    timestamp: datetime
    odds: dict[int, OddsData]  # 馬番 → OddsData

SnapshotStore クラス

メソッド説明
save(snapshot)SQLite + in-memory に保存
get_latest(race_id)直近スナップショット取得
get_previous(race_id)1つ前のスナップショット取得
get_history(race_id, limit)SQLiteから履歴取得
get_snapshot_count(race_id)スナップショット数を返す

SQLiteスキーマ

sql
CREATE TABLE snapshots (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    race_id TEXT NOT NULL,
    horse_number INTEGER NOT NULL,
    odds_win REAL,
    popularity INTEGER DEFAULT 0,
    fetched_at TEXT NOT NULL  -- ISO 8601
);

analyzer/change_detector.py

前回と今回のスナップショットを比較し、急騰・急落を判定するコアロジック。

データクラス

python
@dataclass
class OddsChange:
    horse_number: int
    horse_name: str
    jockey: str
    old_odds: float
    new_odds: float
    pct_change: float      # 正=オッズ上昇, 負=オッズ低下
    direction: str         # "急騰" / "急落" / ""
    direction_arrow: str   # "↑" / "↓" / "-"
    is_alert: bool         # Slack通知対象か

主要関数

関数説明
detect_changes(prev, curr, horses, ...)変動検知(全馬分返却、abs降順ソート)
filter_significant(changes, threshold)閾値以上のみフィルタ
split_by_direction(changes)急騰/急落に分離

判定ロジック

変動率 = (新オッズ - 旧オッズ) / 旧オッズ × 100

オッズ低下 (変動率 < 0) → 買い集中 → ↑ 急騰 (緑)
オッズ上昇 (変動率 > 0) → 買い減少 → ↓ 急落 (赤)

notifier/terminal_display.py

richライブラリを使ったカラー付きターミナルダッシュボード。

主要関数

関数説明
render_dashboard(...)全レースダッシュボードを表示
render_race_odds(...)1レース分のテーブル生成
print_startup_info(...)起動情報表示

表示例

┌──────────────────────────────────────────────────┐
│ 🏇 競馬オッズ急変動モニター  2026-03-14 15:33:12 │
╰──────────────────────────────────────────────────╯
  全レース 急変動ランキング TOP20
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━━━┓
┃ レース  ┃ 馬番 ┃ 馬名   ┃ 前回 ┃ 今回 ┃ 変動%  ┃ 方向   ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━━━╇━━━━━━━━┩
│ 阪神1R  │   11 │ ○○○ │180.3 │258.4 │ +43.3% │ ↓ 急落 │
│ 阪神1R  │   05 │ △△△ │ 44.2 │ 33.7 │ -23.8% │ ↑ 急騰 │
└─────────┴──────┴────────┴──────┴──────┴────────┴────────┘

notifier/slack_notifier.py

Slack Incoming Webhookによるアラート通知。

SlackNotifier クラス

メソッド説明
notify(race, changes)急変動アラートを送信(クールダウン付き)
send_test()テスト通知送信

クールダウン機能

  • 同一レース・同一馬番への再通知を一定時間(デフォルト5分)ブロック
  • Slackスパム防止

メッセージ形式

Slack Block Kit を使用:

🏇 オッズ急変動検知
阪神 1R コーラルS  15:30発走
📈 急騰(支持上昇 = 買い集中)
• 05番 メイショウハリオ: 44.2 → 33.7 (-23.8%)

HorseRacingDataChecker Design Document