メインコンテンツへスキップ
見出し画像

【競馬AI開発#54】全馬の調教データを自動収集する方法

    この【競馬AI開発】シリーズでは、競馬予想AIを作ることを通して、機械学習・データサイエンスの勉強になるコンテンツの発信や、筆者が行った実験の共有などを行っています。

    ‍■今回やること
    前回の記事では、Playwrightを使ってnetkeiba.comのプレミアム会員限定ページ(調教データ)をスクレイピングする方法を解説しました。ログイン処理やセッション管理、1頭分の調教データ取得までを実装し、動作確認まで行ったところです。

    今回は、その実装を拡張して必要な全ての調教ページをスクレイピングする仕組みを作ります。調教ページには他のページにはない「ページネーション(ページ分割)」があり、その対応が必要になります。

    ‍■競馬AIについて
    netkeiba.comから収集したデータを機械学習モデルに学習させることで期待リターンを計算し、リターンが最大化するように自動投票を行う競馬予想AIを開発しています。

    すでにシステムとしては一通り完成しており、現在はこの完成したシステムをベースに、特徴量の追加やモデルの改善など、さらなる精度向上に取り組んでいる段階です。

    このシリーズでは、こうしたシステムをゼロから構築する過程を、コード付きで詳しく解説しています。ソースコードは、下に進んでダウンロード・解凍すれば使うことができるので、ぜひ実際にコードを動かしてみてください。


    1. 調教ページのスクレイピング実装

    1.1. 前回の振り返り

    前回の記事では、以下の3つの関数を実装しました

    • ‍login(page)‍ … メールアドレスとパスワードを入力してnetkeiba.comにログインする

    • ‍get_context(playwright)‍ … 保存済みセッションの復元を試み、期限切れなら再ログインして認証済みのBrowserContextを返す

    • ‍scrape_training_html(horse_id_list)‍ … 馬IDのリストを受け取り、調教ページのHTMLをスクレイピングして保存する

    これらの関数の詳しい実装内容については、前回の記事で一つひとつ解説しているので、まだ読んでいない方はぜひそちらを先にご覧ください。

    今回は、この仕組みをベースに「全ての馬の調教ページを網羅的に取得する」ための実装を行っていきます。

    ここで、今回実装する関数は、このプロジェクトでこれまで使ってきたスクレイピング関数の基本パターンを踏襲した設計になっています。その原型となっているのが、#4の記事で馬の過去成績を取得するために作成したscrape_html_horse()‍です。

    ‍▼common/src/modules/scraping.py(該当部分を抜粋)

    def scrape_html_horse(
        horse_id_list: list[str],
        save_dir: Path = CommonPaths.html_horse,
        skip: bool = True,
        sleep_time: float = 3.0,
    ) -> list[Path]:
        """
        netkeiba.comのhorseページのhtmlをスクレイピングしてsave_dirに保存する関数。
        skip=Trueにすると、すでにhtmlが存在する場合はスキップされる。
        """
        updated_html_path_list = []
        save_dir.mkdir(parents=True, exist_ok=True)
        for horse_id in tqdm(horse_id_list):
            filepath = save_dir / f"{horse_id}.bin"
            if skip and filepath.is_file():
                print(f"skipped: {horse_id}")
            else:
                url = f"https://db.netkeiba.com/horse/result/{horse_id}"
                request = Request(url, headers={"User-Agent": random.choice(USER_AGENTS)})
                html = urlopen(request).read()
                time.sleep(sleep_time)
                with open(filepath, "wb") as f:
                    f.write(html)
                updated_html_path_list.append(filepath)
        return updated_html_path_list

    処理の流れは「horse_idのリストを受け取り → URLを組み立て → HTMLを取得 → .binファイルとして保存する」というものです。skip=Trueの場合は既にファイルが存在すればスキップするので、途中で中断しても再実行するだけで続きから再開できます。

    このパターンは、レースページの取得(scrape_html_race)、血統ページの取得(scrape_html_ped)、リーディングページの取得(scrape_html_leading)など、プロジェクト内の全てのスクレイピング関数で共通しています。
    このscrape_html系の関数の設計や実装の詳細については、以下の記事で詳しく解説しているので、初めての方はぜひそちらも参照してみてください。

    1.2. ページネーション対応のスクレイピング

    前回はnotebook上で関数を直接定義して動かしていましたが、今回はこれをモジュール化して、プロジェクト内のどこからでも呼び出せるようにします。

    前回作成した3つの関数をscraping_premium.pyというファイルにまとめ、modulesディレクトリに配置します。ディレクトリ構成は以下の通りです。

    keibaAI-v3/
    └── common/
        ├── src/
        │   ├── collect_data.ipynb     ← notebookから呼び出す
        │   └── modules/
        │       ├── __init__.py        ← scraping_premiumを追加
        │       ├── paths.py           ← html_trainingパスを追加
        │       └── scraping_premium.py  ← 新規作成
        └── data/
            └── html/
                └── horse/
                    └── training/      ← スクレイピング結果の保存先

    まず、メインとなるscraping_premium.pyの実装から見ていきましょう。前回の記事で作成した3つの関数をそのままモジュール化していますが、1つ大きな変更点があります。それがページネーション対応です。

    ‍▼common/src/modules/scraping_premium.py‍

     
     
    競馬予想AIを作ることを通して、機械学習・データサイエンスの勉強になるコンテンツの発信や、筆者が行った実験の共有などを行っていきます。

    あなたへのおすすめ