【スクレイピング】「Selenium」でnetkeibaから開催レースのURLを取得【地方競馬】

競馬AI

本記事の目的

目的
  • 「開催レース一覧」から、地方競馬の開催レースのURL一覧を取得する

開催レース一覧【地方競馬】

作業概要

本サイトでは、下記の手順で「netkeiba.com」からレースデータを取得します。

ねずみくん
ねずみくん

このページでは、「①-2:開催レースのURL一覧 (地方競馬) を取得」を解説するよ

①開催レースのURL一覧を取得

ソースコード

""" メソッド: 【地方競馬】レース結果のURL一覧取得 """

# ライブラリ
import datetime
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
import time
import pandas as pd

def get_url_local(start_str, end_str):
    
    # 初期値
    driver_path = 'C:/Users/kouhe/OneDrive/01_Documents/02_機械学習/スクレイピング/chromedriver.exe'
    url = 'https://nar.netkeiba.com/top/?rf=navi'
    target_flg = False
    interval = 30
    ret_df = pd.DataFrame(columns=['date', 'url'])
    url_list = []
    
    # 文字列を日付型に変換
    start_target = datetime.datetime.strptime(start_str, '%Y%m%d')
    end_target = datetime.datetime.strptime(end_str, '%Y%m%d')
    
    # Google Chromeで「レース結果一覧」を開く ---------------------- (※1)
    browser = webdriver.Chrome(executable_path=driver_path)
    browser.implicitly_wait(interval)
    browser.get(url)
    wait = WebDriverWait(browser, interval)

    while not target_flg:

        # 画面に表示されている開催日を取得 ---------------------- (※2)
        date_list = []
        div_tag = browser.find_element_by_css_selector('div#RaceTopRace.RaceList_Date_Top')
        li_tag_list = div_tag.find_elements_by_css_selector('li.ui-tabs-tab.ui-corner-top.ui-state-default.ui-tab')
        for li_tag in li_tag_list:
            date = li_tag.get_attribute('date')
            if date is None:
                continue
            else:
                date_list.insert(0, date)
        
        # 取得対象データの場合、レース結果ページのurlを取得
        for date_str in date_list:
            
            print('日付 : ', date_str)
           
            # 取得対象データであるかをチェック
            date_target = datetime.datetime.strptime(date_str, '%Y%m%d')
            if date_target > end_target:
                pass
            
            elif date_target < start_target:             
                
                # 重複するURLを削除
                ret_df = ret_df.drop_duplicates(subset='url')
                
                # インデックスを振りなおす
                ret_df = ret_df.reset_index(drop=True)   
                
                # ブラウザを閉じる
                browser.close()
                return ret_df
                
            else:    
                # 月と日を取得
                month_str = date_str[4:6]
                day_str = date_str[6:8]  
            
                # 曜日を取得
                week = date_target.strftime('%a')
                if week == 'Sun':
                    week_str = '日'
                elif week == 'Mon':
                    week_str = '月'
                elif week == 'Tue':
                    week_str = '火'
                elif week == 'Wed':
                    week_str = '水'
                elif week == 'Thu':
                    week_str = '木'
                elif week == 'Fri':
                    week_str = '金'
                elif week == 'Sat':
                    week_str = '土'
            
                # 開催日をMM月YY日(Z)に変換 ---------------------- (※3)
                date_str_mod = month_str + '/' + day_str + '(' + week_str + ')'
            
                # 日付タブをクリック ---------------------- (※4)
                text = div_tag.find_element_by_partial_link_text(date_str_mod)
                browser.execute_script('arguments[0].scrollIntoView(true);', text)
                text.click()
                    
                # 画面に表示されている競馬場を取得
                ul_tag = div_tag.find_element_by_css_selector('ul.RaceList_ProvinceSelect')
                a_tag = ul_tag.find_elements_by_tag_name('a')
                
                for a in a_tag:
                    
                    # 競馬場タブをクリック 
                    if a.text == '':
                        continue
                    area_text = ul_tag.find_element_by_partial_link_text(a.text)
                    browser.execute_script('arguments[0].scrollIntoView(true);', area_text)
                    area_text.click()
                    
                    # レース結果ページのURLを取得 ---------------------- (※5)
                    class_list = div_tag.find_elements_by_css_selector('li.RaceList_DataItem ')
                    url_list = []
                    for element in class_list:
                        a_tag_list = element.find_elements_by_tag_name('a')
                        for a_tag in a_tag_list:
                            try:
                                url = a_tag.get_attribute('href')
                                if 'race/result' in url:
                                    url_list.append(url) 
                            except Exception:
                                continue
                        
                    # 戻り値のデータフレームに取得結果を格納
                    ret_date_list = [date_str] * len(url_list)
                    ret_list = list(zip(ret_date_list, url_list))
                    df = pd.DataFrame(ret_list, columns=['date', 'url'])
                    ret_df = ret_df.append(df)            
        
        # 画面に表示されているURLを取得後、 「前」ボタンを1回クリックする ---------------------- (※6)
        try:
            text = browser.find_element_by_partial_link_text('前')            
            browser.execute_script('arguments[0].scrollIntoView(true);', text)
            wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'Active')))
            text.click()
            time.sleep(1)
            
        except Exception as e:
            print('例外が発生しました。')
            print(e)
            
            # URLが重複する行を削除する
            ret_df = ret_df.drop_duplicates(subset='url')
            
            # インデックスを振りなおす
            ret_df = ret_df.reset_index(drop=True)
            
            # ブラウザを閉じる
            browser.close()
            return ret_df

メソッドの引数と戻り値

カテゴリ 変数名 説明
引数 start_str str 取得開始日
end_str str 取得終了日
戻り値 ret_df DataFrame レース結果のUR一覧

解説

Google Chromeで「レース結果一覧」を開く(※1)

「開催レース一覧」ページは、上部の「日付ボタン」や「競馬場ボタン」、「前ボタン」、「後ボタン」をクリックしないと、画面に表示されていない開催日のレース結果URLを取得できません。

なので、今回はWebブラウザーを遠隔操作する「Selenium」というモジュールを使用して、スクレイピングを行います。

本ページでは「Google Chrome」を遠隔操作して、開催レースのURL一覧を取得する方法を解説します。

(※1)の処理内容
  • ①Google Chromeを起動
  • ②ドライバの初期化が完了するまで、最大3秒間待つ
  • ③URLを読み込む
    # Google Chromeで「レース結果一覧」を開く ------------------ (※1)
    browser = webdriver.Chrome(executable_path=driver_path)
    browser.implicitly_wait(interval)
    browser.get(url)

画面に表示されている開催日を取得(※2)

スクレイピングする際には、まずは欲しい情報のHTMLを確認する必要があります。

「欲しいデータを選択 → 右クリック → 検証」の順に操作することで、HTMLを確認できます。

開催レース一覧」のHTMLを、↓の動画のように確認すると、

  • 開催日情報は、
    「ID属性」が”RaceTopRace”、かつ「class属性」が”RaceList_Date_Top”である、<div>タグ
    →<li>タグにおける「date属性」に記載されている。

ということが分かります。

この情報をもとに、下記の処理で開催日を取得しています。

(※2)の処理内容
  • ①開催日を格納する空リストを準備する
  • 「ID属性」が”RaceTopRace”、かつ「class属性」が”RaceList_Date_Top”である、<div>タグを取得する

  • ③取得した<div>タグの中から、<li>タグを取得する
  • 取得した<li>タグの中から、「date属性」を取得する
  • ⑤「date情報」を取得できた場合は、①のリストに追加する
# 画面に表示されている開催日を取得 ---------------------- (※2)
date_list = []
div_tag = browser.find_element_by_css_selector('div#RaceTopRace.RaceList_Date_Top')
li_tag_list = div_tag.find_elements_by_css_selector('li.ui-tabs-tab.ui-corner-top.ui-state-default.ui-tab')
for li_tag in li_tag_list:
    date = li_tag.get_attribute('date')
    if date is None:
        continue
    else:
        date_list.insert(0, date)

開催日をMM月YY日(Z)に変換(※3)

↑の赤枠の箇所をクリックするために、先ほど取得した開催日の変数「date_list」から、文字列「MM月YY日(Z)」を作成します。

(※3)の処理内容
  • ①月と日を取得
  • ②曜日を取得
  • ③開催日をMM月YY日(Z)に変換
                # 月と日を取得
                month_str = date_str[5] if date_str[4] == '0' else date_str[4:6]
                day_str = date_str[7] if date_str[6] == '0' else date_str[6:]  
            
                # 曜日を取得
                week = date.strftime('%a')
                if week == 'Sun':
                    week_str = '日'
                elif week == 'Mon':
                    week_str = '月'
                elif week == 'Tue':
                    week_str = '火'
                elif week == 'Wed':
                    week_str = '水'
                elif week == 'Tue':
                    week_str = '木'
                elif week == 'Fri':
                    week_str = '金'
                elif week == 'Sat':
                    week_str = '土'
            
                # 開催日をMM月YY日(Z)に変換 ------------------------------- (※3)
                date_str_mod = month_str + '月' + day_str + '日(' + week_str + ')'

日付タブをクリック(※4)

日付タブをクリックするためには、クリックする場所を特定する必要があります。

再び↓の動画の方法でHTMLを確認すると、

  • 開催日ボタンは、<a>タグにおける「title属性」に「MM月YY日(Z)」が記述されている。

ことが分かります。

そこで本処理では、先ほど作成した文字列「MM月YY日(Z)」と一致するテキストを含む要素を

メソッド

find_elements_by_patial_link_text(text)

で取得し、該当箇所をクリックします。

「Selenium」で画面外の要素をクリックしようとすると、エラーが生じてしまいます。なので、クリックする前には必ず

メソッド

execute_script(“arguments[0].scrollIntoView(true);”, text)

で、スクロールするようにしましょう。

(※4)の処理内容
  • ①文字列「MM月YY日(Z)」を含む要素を取得
  • ②「title属性」を取得し「MM月YY日(Z)」と合致する場合は、該当箇所が表示されるようにスクロールする
  • ③開催日をMM月YY日(Z)に変換
                # 日付タブをクリック ---------------------------------------- (※4)
                text_list = browser.find_elements_by_partial_link_text(date_str_mod)
                for text in text_list:
                    title = text.get_attribute('title')
                    if title == date_str_mod:
                        print('日付 : ', date_str_mod)

                        # 要素を表示するようスクロール
                        browser.execute_script("arguments[0].scrollIntoView(true);", text)
                        text.click()
                        break

レース結果ページのURLを取得(※5)

レース結果ページのURLを取得するには、再びHTMLを確認してURLが記述されている場所を特定する必要があります。

またまた、↓の動画でHTMLを確認すると

  • レース結果のURLは、
      <dd>タグ(class属性:「RaceList_Data」)
    の中の
      <a>タグの「href属性」
    の中に記述されている。

ということが分かります。

上記の情報を基にして、レース結果ページのURL一覧を取得します。

(※5)の処理内容
  • ①<dd>タグの「class属性」が「RaceList_Data」の要素を取得
  • ②上記の要素の中で、<a>タグの「href属性」を取得
  • ③取得したURLに文字列「race/result」を含む場合、変数「url_list」に追加

                # レース結果ページのURLを取得 --------------------------- (※5)
                class_list = browser.find_elements_by_css_selector('dd.RaceList_Data')
                url_list = []
                for element in class_list:
                    a_tag_list = element.find_elements_by_tag_name('a')
                    for a_tag in a_tag_list:
                        try:
                            url = a_tag.get_attribute('href')
                            if 'race/result' in url:
                                url_list.append(url) 
                        except Exception:
                            continue

「前」ボタンをクリックする(※6)

画面に表示されているレースのレース結果URLを取得した後は、「前」ボタンをクリックして再度レース結果URLを取得します。

(※5)の処理内容
  • ①「前」ボタンの場所までスクロール
  • ②「前」ボタンをクリック
  • ③1秒時間停止

# 画面に表示されているURLを取得後、 「前」ボタンをクリックする ---------------------- (※6)
text = browser.find_element_by_partial_link_text('前')            
browser.execute_script('arguments[0].scrollIntoView(true);', text)
wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'Active')))
text.click()
time.sleep(1)

参考図書

本記事の内容は、以下の図書を参考にしています。

本書は、下記のようにスクレイピングについてとても詳しく解説されています。

本書の目次
  • 第一章:クローリングとスクレイピング
    • データのダウンロード
    • BeautifulSoupでスクレイピング
    • CSSセレクタについて
    • リンク先を丸ごとダウンロード
  • 第二章:高度なスクレイピング
    • ログインの必要なサイトからダウンロード
    • ブラウザーを経由したスクレイピング
    • スクレイピング道場
    • Web APIからのデータ取得
    • cronと定期的なクローリング
  • 第三章:…
ねずみくん
ねずみくん

本書の内容は「ブラウザーを経由したスクレイピング」を参考にしているよ!

本書は、上記の他にも

  • データベースへの登録方法
  • 機械学習
  • ディープラーニング

等も内容に含まれています。

Python初心者にも理解しやすい内容になっているので、ぜひ参考にしてください。

本書の特徴
  • Python初心者でも理解できるくらい具体的に説明されている
  • ソースコードも併せて紹介されているため、コード記述の際に躓きづらい

次回の内容

次回は、今回取得したURL一覧を用いて、レースデータ(レース情報・レース結果)を取得します。

ぜひご覧になってください!

コメント

タイトルとURLをコピーしました