スクレイピングをやってみる【レース結果の取得】

こんにちは!
完全競馬初心者が競馬予想AIをつくるアカウント、ビギ馬AIです!

今回は全ての基礎となる「データ」の取集、スクレイピングについて記録を残していきます。

■現状とゴール

現状:スクレイピング完全初心者 
Pythonで簡単な機械学習とデータ分析ができる

ゴール:netkeibaから過去のレース結果を収集する

■参照元

netkeibaをスクレイピングする方法を解説【準備編】
netkeibaのWebスクレイピングをPythonで行う【競馬開催日の抽出】
PythonでChromeDriverによりSelenium操作
10分で理解する Selenium
<a>の解説|クロノドライブのHTML辞典
とあるデータサイエンティストの競馬予測チャレンジ データ収集編1

■実行環境

PC:HP Pavilion Laptop 15-eh
OS:Windows11
Python3.10.4
実行環境:Jupyter lab

■手順

0.スクレイピングの心構え

こちらの記事が非常に参考になりました。
「違法性はないけど、良識の範囲内でね」というお話です。
こういった些細な事も発信している方は信頼がおけるなぁという印象です。偉そうにすいません…

1.必要となるライブラリをpip installする

pip install beautifulsoup4
pip install lxml
pip install selenium

横道:DockerでSeleniumの環境構築をする
Seleniumについて調べていたらDockerというものを見つけて、この勉強もしてみました。
簡単に言うと、「輸送しやすいアプリケーションやサーバーを仮想化を利用した環境で動作させることができるアプリケーション」みたいです。

2.開催一覧ページから開催日を取得する
3.開催日からレースID一覧を取得する
4.レースIDからレース情報を取得する

↑の手順でいこうとしたのですが、面倒で断念
(この手順ではseleniumを使ってChromeを操作しています。)

こちらの記事を参考にレースIDを分解してfor文でぶん回すことにしました。

スクレイピング先も
https://race.netkeiba.com/top/ (レースタブから遷移)
https://db.sp.netkeiba.com/?rf=navi (データベースタブから遷移)に変更しました。

変更後はスクレイピング先URLがシンプルで
'https://db.sp.netkeiba.com/race/202105030311' のように
'https://db.sp.netkeiba.com/race/(年)(場所)(回数)(日目)(R数)'
というURL構成をしています。

ちなみに競馬の場所・回数・日目というのは
東京3回5日目 のようになっている数字のことです。
私はこの概念が全く分からず少しつまづきましたが、

「4週8日の日程を1開催(○回東京□日目など)とする」
とWikipediaにありました。
中央競馬では5回開催=40日が標準的な開催回数/日数のようです。
X回目Y日目 → (X-1)*8 + Y = その年の何日目の開催日か
が分かるみたいです。

少し脱線しましたがソースコードを記します。
集計年:2008~2021
場所:01~10 (中央競馬場は全部で10)
回数:01~06 (5回開催ですが、念のため)
日目:01~09 (8日開催ですが、念のため) 
レース数:01~12 (原則12レース開催 Maxも12回)

#実行は自己責任でお願いいたします。
import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
from tqdm import tqdm
import time
from bs4 import BeautifulSoup
import pandas as pd
df1=pd.DataFrame()
flg=0
flg2=0
base='https://db.sp.netkeiba.com/race/' # URL先頭の固定部分
for year in tqdm(range(2008,2022)):
    for basyo in range(1,11):
        for kaisu in range(1,7):
            for nichime in range(1,10):
                for race in range(1,13):

                    #超重要。毎回リクエストを送る最に1秒間の時間を置く
                    time.sleep(1)
                    #URLを作成。zfill関数を使って「3」→「03」のように2ケタ表示に変換
                    url=base+str(year)+str(basyo).zfill(2)+str(kaisu).zfill(2)+str(nichime).zfill(2)+str(race).zfill(2) 

                    html = requests.get(url) # リクエスト
                    html.encoding = html.apparent_encoding # 日本語を取得したときに文字化けしなければ必要ないかも?
                    soup = BeautifulSoup(html.text, 'lxml') 

                    table=soup.find(class_="table_slide_body ResultsByRaceDetail") # 表を取得
                    if (table == []) | (table == None): #tableが上手く取得できていない場合、その後の処理はスキップ
                        print('break',url)
                        break

                    else:
                        for tr in table.find_all('tr')[1:]: #trタグごとに処理。最初のtrタグは表のカラム名なのでスキップ
                            try:
                                #このリストにデータを1カラムずつ格納していく
                                temp=[]
                                #まずは共通項目から取得
                                temp.append(soup.find_all(class_="Race_Date")[0].contents[0].string.strip()) #日付
                                temp.append(soup.find_all(class_="Race_Date")[0].contents[1].string.strip()) #曜日
                                temp.append(race) #R数
                                temp.append(basyo) #場所ID
                                temp.append(kaisu) #回数
                                temp.append(nichime) #日目
                                temp.append(soup.find_all(class_="RaceName_main")[0].string) # レース名
                                temp.append(soup.find_all(class_="RaceData")[0].contents[1].string) #レース時間
                                temp.append(soup.find_all(class_="RaceData")[0].contents[3].string) #芝 or ダート
                                temp.append(soup.find_all(class_="RaceData")[0].contents[5].contents[0]) # 天気
                                temp.append(soup.find_all(class_="RaceData")[0].contents[7].string) # レース場の状態
                                temp.append(soup.find_all(class_="RaceHeader_Value_Others")[0].contents[1].string) #レース情報1
                                temp.append(soup.find_all(class_="RaceHeader_Value_Others")[0].contents[3].string) #レース情報2
                                #出場馬ごとの情報の抽出
                                for td in tr.find_all('td'):
                                    temp.append(td.string)
                                df1=pd.concat([df1,pd.DataFrame(temp).T])
                                flg=0
                            except:
                                flg=1
                                pass
                        if flg == 0:
                            print('OK',url)
                        else:
                            print('NG',url)
                            flg=0


df1.columns=['日付','曜日','R数','場所ID','回数','日目','レース名','レース時間','レース場情報','天候','馬場','レース情報1','レース情報2','着順','枠番','馬番','馬名','性齢','斤量','騎手','タイム','着差','タイム指数','通過','上り','単勝','人気','馬体重','調教タイム','厩舎コメント','備考','調教師','馬主',"賞金"]                          
df1.to_csv('table_race_info.csv',encoding='utf_8_sig') 

全部で20時間以上かかりましたが、無事CSV形式で保存できました。

次回はデータの成形を行っていきます!

この記事が気に入ったらサポートをしてみませんか?