【競馬AI開発#2】ChromeDriverによるレースid一覧のスクレイピング
はじめに
この【競馬AI開発】シリーズでは、競馬予想AIを作ることを通して、機械学習・データサイエンスの勉強になるコンテンツの発信や、筆者が行った実験の共有などを行っていきます。
今回の記事は、以下の動画に補足を加えて簡単にまとめたものになります。
動画中の実行環境
・OS: Mac OS 14.2.1
・言語: Python 3.11.4
・エディタ: VSCode 1.87.0
VSCodeやPythonのインストール方法については様々な記事で紹介されているので、適宜参照して設定してください。
また、以下のライブラリを使用しています。
beautifulsoup4==4.12.3
pandas==2.2.1
selenium==4.18.1
tqdm==4.66.1
webdriver_manager==4.0.1
動画中のソースコード
※転載・再配布はお控えください
筆者のプロフィール
東京大学大学院卒業後、データサイエンティストとしてWEBマーケティング調査会社でWEB上の消費者行動ログ分析などを経験。
現在は、大手IT系事業会社で、転職サイトのレコメンドシステムの開発を行っています。
今回やること
前回に引き続き、まずはnetkeiba.comから2023年のレース結果のテーブルをスクレイピングにより取得して、Pythonで扱えるようにすることを目標にしています。
そのためのステップについての全体感は、前回の記事で解説しています。
今回は、開催ページから2023年の全レースid一覧をスクレイピングにより取得します。
スクレイピング手法の選定
前回と同様にスクレイピングをしていきたいのですが、上のページはjavascriptによってページが描画されているため、BeautifulSoupでスクレイピングすることができません。
Chrome上で右クリック→「検証」からhtmlタグを辿っていくと、実際に<script>タグが見つかります。
このような場合は、ChromeDriverを使ってスクレイピングします。
ただ、ChromeDriverはBeautifulSoupに比べると動作が重い・使用しているGoogleChromeに挙動が依存するなどのデメリットがあるため、極力BeautifulSoupを使った方が良いです。今回は仕方なく、という感じですね。
ChromeDriverのおすすめ設定方法
そんなChromeDriverですが、何よりgoogle chromeとのバージョン合わせがとても厄介です。
一昔前は、google chromeのバージョンが上がると、すぐにエラーを吐いて使えなくなり、新しいバージョンをインストールする必要がありました。
ただ、便利なライブラリがあって、以下のコードを実行すると、使用しているgoogle chromeのバージョンに合わせたものを自動的にインストールしてくれます。
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
driver_path = ChromeDriverManager().install()
このdriver_pathに、インストールしたChromeDriverのパスが入っているので、これを使って以下のように起動することができます。
driver = webdriver.Chrome(service=Service(driver_path))
オプションを指定するには以下のようにします。
from selenium.webdriver.chrome.options import Options
options = Options()
# ヘッドレスモード(バックグラウンド)で起動
options.add_argument("--headless")
# その他のクラッシュ対策
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
driver = webdriver.Chrome(service=Service(driver_path), options=options)
動画中で解説されている"--headless"に加えて、"--no-sandbox"と"--disable-dev-shm-usage"オプションが追加されていますが、これらも動作を円滑にするために一般的によく使われるものです。
また、ChromeDriverでは、webページ上の要素を読み込む前に次の処理に進んでしまい、エラーになる時があります。その対策として、ソースコード中では
# 要素を取得できない時、最大10秒待つ
driver.implicitly_wait(10)
の設定を加えています。
レースid取得の流れ
(動画中 9:20〜)
ChromeDriverが設定できたら、開催ページを取得します。
url = "https://race.netkeiba.com/top/race_list.html?kaisai_date=20230105"
driver.get(url)
こうすることで、バックグラウンドでgoogle chromeが起動し、urlで指定されたページを読み込みます。
google chromeで上のURLを開き、一つ目の「3歳未勝利」のリンクにカーソルを合わせ、右クリックから「検証」を押すと、以下のようなHTMLが表示されます。
構造を見ていくと、<li class="RaceList_DataItem …>というタグが、一つのレースに対応していることが分かります。
よって、まずはこのタグ一覧を取得します。
from selenium.webdriver.common.by import By
li_list = driver.find_elements(By.CLASS_NAME, "RaceList_DataItem")
li_list
すると、以下のように各レースの<li>要素が、webelementのリストとして返ってきます。
[<selenium.webdriver.remote.webelement.WebElement ...,
<selenium.webdriver.remote.webelement.WebElement ...,
<selenium.webdriver.remote.webelement.WebElement ...,
...
説明のため、先頭の<li>要素を取り出します。
li = li_list[0]
もう一度HTMLを見ると、このタグの中に<a>タグがあり、そこの"race_id="という形でレースidの情報が入っています。
よって、ここからさらに<a>タグに絞り込みます。
li.find_element(By.TAG_NAME, "a")
これが、<a href="…">を表しているので、ここからhref=の部分だけ取り出します。
href = li.find_element(By.TAG_NAME, "a").get_attribute("href")
href
出力:
'https://race.netkeiba.com/race/result.html?race_id=202306010101&rf=race_list'
あとは、前回と同様、正規表現を使ってレースidの部分を取り出します。
import re
re.findall(r"race_id=(\d{12})", href)[0]
出力:
'202306010101'
以上が、レースidを取得する流れになります。
scrape_race_id_list()の作成
(動画中 17:30〜)
レースid取得の流れが分かったので、開催日の一覧をリストで入れると、その開催日のレースid一覧をリストで返してくれるscrape_race_id_list()関数を作成します。
詳しい流れは動画上で解説しているので、そちらをご確認ください。
def scrape_race_id_list(kaisai_date_list: list[str]) -> list[str]:
"""
開催日(yyyymmdd形式)をリストで入れると、レースid一覧が返ってくる関数。
"""
options = Options()
# ヘッドレスモード(バックグラウンド)で起動
options.add_argument("--headless")
# その他のクラッシュ対策
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
driver_path = ChromeDriverManager().install()
race_id_list = []
with webdriver.Chrome(service=Service(driver_path), options=options) as driver:
# 要素を取得できない時、最大10秒待つ
driver.implicitly_wait(10)
for kaisai_date in tqdm(kaisai_date_list):
url = f"https://race.netkeiba.com/top/race_list.html?kaisai_date={kaisai_date}"
try:
driver.get(url)
time.sleep(1)
li_list = driver.find_elements(By.CLASS_NAME, "RaceList_DataItem")
for li in li_list:
href = li.find_element(By.TAG_NAME, "a").get_attribute("href")
race_id = re.findall(r"race_id=(\d{12})", href)[0]
race_id_list.append(race_id)
except:
print(f"stopped at {url}")
print(traceback.format_exc())
break
return race_id_list
この関数と、前回作成したscrape_kaisai_date()を組み合わせることによって、ソースコードのmain.ipynbから以下のように実行すれば、2023年の全てのレースidを取得することができます。
import scraping
# 開催日一覧を取得
kaisai_date_list = scraping.scrape_kaisai_date(
from_="2023-01", to_="2023-12"
)
# レースid一覧を取得
race_id_list = scraping.scrape_race_id_list(kaisai_date_list)
次回は、このrace_id_listに含まれるrace_idのレース結果をスクレイピングし、学習させるデータを作っていきます。
ここから先は
【定期マガジン】競馬AI研究所
「競馬予想AIを1から作る」ことを通して、機械学習・データサイエンスの勉強になるコンテンツの発信や、筆者が行った実験の共有などを行っていき…
この記事が気に入ったらサポートをしてみませんか?