はじめに
前回と前々回のプログラムを結合して、指定期間のレース結果を取得してCSVに保存するプログラムを作成しました。前々回のプログラムの一部を変更しています。
変更したのは、距離/天候/競争種別/馬場状態/開催日/開催場所を追加しています。
AIで予測するデータを「タイム」とすることにしました。
「タイム」を1列目に移動してCSVファイルに保存します。
競馬データの取得先
以下のページから競馬データを取得します。
プログラムの概要
前回作成したプログラムと前々回作成したプログラムを結合したプログラムです。
変更が有った箇所は「変更有り」と記載しています。
該当行 | 概要 |
1~12 | ライブラリの読み込み。変更有り |
17~50 | メイン処理。各関数を呼び出して各レース結果をCSVに保存する。変更有り |
53~92 | レース詳細検索で検索条件を指定して検索する。変更無し |
95~127 | レース詳細検索の結果から各レース結果のURLを抽出する。変更無し |
130~157 | レース詳細検索の結果で「次」がある場合、「次」をクリックする。変更無し |
160~239 | レース結果の詳細を取得する。前々回のプログラムで変更有り |
事前準備
2022年の1~5月の芝・ダートのレース結果を取得する場合、6700レースの情報を取得することになります。1秒間に4レースの情報を取得できたとしても、プログラムの実行にはおよそ27分かかります。進捗状況を確認できるように、tqdmを用いて進捗バーを表示します。tqdmはpipでインストールします。
pip install tqdm
tqdmを使用することで、下記のように進捗バーを表示することができるようになります。
作成したプログラム
掲載したプログラムは、前回と前々回から「変更有り」の箇所となります。
ライブラリ読み込み
requests、re、tqdmのライブラリを追加しました。
# ライブラリの読み込み
import pandas as pd
import urllib
import requests
import re
from webdriver_manager.chrome import ChromeDriverManager
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from bs4 import BeautifulSoup
# プログレスバーを表示するためのライブラリを読み込む
from tqdm import tqdm
メイン処理
取得したレース結果をCSVに保存する処理と、コメントを変更しました。
# メイン
def main():
# webdriver_managerで最適なchromeのバージョンをインストールして設定する
browser = webdriver.Chrome(ChromeDriverManager().install())
# 競馬データベースを開く
browser.get('https://db.netkeiba.com/?pid=race_search_detail')
browser.implicitly_wait(10) # 指定した要素が見つかるまでの待ち時間を10秒と設定する
search_rase(browser) # 検索条件を設定して検索する
link_list = [] # リンクページのURLリスト
no_click_next = 0 # 0:次のページ無し、1:次のページ有り
count_click_next = 0 # 0:検索結果の1ページ目、1以上:検索結果の2ページ目以上
while no_click_next == 0:
# レース結果のリンクページのURLを取得する
link_list = make_raseURL(browser, link_list)
# 「次」をクリックし、「次」の有無とクリックした回数を返す
no_click_next, count_click_next = click_next(browser, count_click_next)
df_RaceResult = pd.DataFrame()
# レース結果のリンクページにアクセスして、レース結果を取得する
# 日本レース以外のレース結果は取得しない
# 日本以外のレースのリンクページには、/race/2022H1a00108/のようにアルファベットが含まれている
# isdecimalで文字列が数字のみかを判定している
print('レース結果の詳細を取得します')
for url in tqdm(link_list):
if url.split('/')[-2].isdecimal():
df_RaceResult = pd.concat([df_RaceResult, output_RaceResult(url)], axis=0)
# CSVにレース結果を保存する
df_RaceResult.to_csv('./レース結果.csv', encoding='cp932', header=False, index=False, errors="ignore")
レース結果の詳細を取得する
レース距離、天候、馬場の状態、レース名、開催日、開催場所を前々回から追加して取得します。
「タイム」を1列目に移動したデータフレームを作成します。
# レース結果の詳細を取得する
def output_RaceResult(url):
res = requests.get(url) # 指定したURLからデータを取得する
soup = BeautifulSoup(res.content, 'html.parser') # content形式で取得したデータをhtml形式で分割する
# レース名を取得する
race_name = soup.find_all('h1')
race_name = race_name[1].text
# 開催日、開催場所を取得する
race_base_info = soup.find('p', attrs={'class': 'smalltxt'})
rase_base_info_text = race_base_info.text.replace(u'\xa0', u' ')
words = rase_base_info_text.split(' ')
race_date = words[0]
race_place = words[1]
# レース情報を取得する
race_info = soup.find('diary_snap_cut')
race_info_text = race_info.text.replace(u'\xa0', u' ')
race_info_text = race_info.text.replace(u'\xa5', u' ')
words = race_info_text.split('/')
race_info_distance = int(re.sub(r'\D', '', words[0])) # レース距離だけを取り出してint型で保存する
race_info_weather = words[1].split(':') # 天候
race_info_condition = words[2].split(':') # 馬場の状態
# tableデータを抽出する
tables = soup.find('table', attrs={'class': 'race_table_01'})
tables = tables.find_all('tr')
# 取得したデータからindex名を抽出する
indexs = tables[0].text.split('\n')
# レース情報/結果を取得する
tmp = []
df = pd.DataFrame()
df_tmp1 = pd.DataFrame()
for table in tables[1:]:
tmp = table.text.split('\n')
df_tmp1 = pd.Series(tmp)
df = pd.concat([df, df_tmp1], axis=1)
# 学習に必要な情報のみを抽出する
# 着順:要素No.1、馬名:要素No.5、性齢:要素No.7、斤量:要素No.8、騎手:要素No.10、
# タイム:要素No.12、上り:要素No.21、単勝:要素No.23、人気:要素No.24、馬体重:要素No.25の情報を抽出する
df_tmp2 = pd.DataFrame()
df_tmp3 = pd.DataFrame()
for i in (1, 5, 7, 8, 10, 12, 21, 23, 24, 25):
df_tmp2 = df.iloc[i]
df_tmp3 = pd.concat([df_tmp3, df_tmp2], axis=1)
# カラム名を設定する
tmp = ['着順', '馬名', '性齢', '斤量', '騎手', 'タイム', '上り', '単勝', '人気', '馬体重']
df_columns = pd.Series(tmp)
df_tmp3.columns = df_columns # index名を設定する
df_tmp4 = pd.DataFrame()
try:
# 距離、天候、馬場、状態の列を追加する
df_tmp3['距離'] = race_info_distance
df_tmp3['天候'] = race_info_weather[1]
df_tmp3['馬場'] = race_info_condition[0]
df_tmp3['状態'] = race_info_condition[1]
df_tmp3['開催日'] = race_date
df_tmp3['レース名'] = race_name
df_tmp3['開催場所'] = race_place
# 説明変数として使用しない列を削除する
df_tmp3.drop(['着順', '上り'], axis=1, inplace=True)
# 目的変数にするタイムを1列に変更する
df_tmp4 = df_tmp3.reindex(columns=['タイム', '馬名', '性齢', '斤量', '騎手', '単勝',
'人気', '馬体重', '距離', '天候', '馬場', '状態',
'開催日', 'レース名', '開催場所'])
except:
print('レース情報を取得できませんでした')
return df_tmp4
全コードのまとめ
下記に各コードをまとめました。
プログラムの実行結果
プログラム実行のカレントフォルダに「レース結果.csv」が作成されます。
また、標準出力では下記のメッセージが表示されます。
中止になったレース情報を参照した場合、「レース情報を取得できませんでした」と表示されます。
====== WebDriver manager ======
Current google-chrome version is 102.0.5005
Get LATEST chromedriver version for 102.0.5005 google-chrome
Driver [C:\Users\xxxx\.wdm\drivers\chromedriver\win32\102.0.5005.61\chromedriver.exe] found in cache
C:\Users\ xxxx\Google ドライブ\Web_Scraping\xxxx.py:20: DeprecationWarning: executable_path has been deprecated, please pass in a Service object
browser = webdriver.Chrome(ChromeDriverManager().install())
検索条件を設定します
取得したHTMLからレース結果のURLを抽出します
URLを抽出しました
~中略~
取得したHTMLからレース結果のURLを抽出します
URLを抽出しました
取得したHTMLからレース結果のURLを抽出します
URLを抽出しました
取得したHTMLからレース結果のURLを抽出します
URLを抽出しました
取得したHTMLからレース結果のURLを抽出します
URLを抽出しました
次のページ無し
レース結果の詳細を取得します
98%|█████████▊| 6559/6700 [36:36<00:50, 2.80it/s]レース情報を取得できませんでした
レース情報を取得できませんでした
98%|█████████▊| 6561/6700 [36:37<00:52, 2.62it/s]レース情報を取得できませんでした
レース情報を取得できませんでした
98%|█████████▊| 6563/6700 [36:37<00:51, 2.65it/s]レース情報を取得できませんでした
レース情報を取得できませんでした
98%|█████████▊| 6565/6700 [36:38<00:49, 2.74it/s]レース情報を取得できませんでした
レース情報を取得できませんでした
98%|█████████▊| 6567/6700 [36:39<00:42, 3.11it/s]レース情報を取得できませんでした
レース情報を取得できませんでした
100%|██████████| 6700/6700 [37:25<00:00, 2.98it/s]
プロセスは終了コード 0 で終了しました
プログラム作成時にハマったところ
レースが中止になった場合のデータを取得するとプログラムが異常終了
中止になったレースの情報も情報としてサイトに掲載されています。そのレースをプログラムでデータ取得した際、空のデータとして取得することになります。空のデータをpandasで処理しようとすると、exceptionが発生しました。
基本的なことですが、今後は、異常処理が発生することを想定してプログラムを作成するようにします。
CSVファイルにデータを保存しようとしたときにエンコードでエラー
取得したデータをCSVファイルに保存するタイミングで下記のエラーが出ました。
UnicodeEncodeError: 'cp932' codec can't encode character '\xa0' in position 47: illegal multibyte sequence
対処としては、replaceを使って問題となっている文字列を変換しました。
race_info_text = race_info.text.replace(u'\xa0', u' ')
race_info_text = race_info.text.replace(u'\xa5', u' ')
また、CSVファイルに保存する際のオプションで「errors=”ignore”」を追加しました。
# CSVにレース結果を保存する
df_RaceResult.to_csv('./レース結果.csv', encoding='cp932', header=False, index=False, errors="ignore")
次回、試すこと
AIモデルを作成する前に、まずは取得したデータの全体像を把握します。
取得したデータから馬ごと、騎手ごと、開催場所ごと、様々な要素ごとにデータを整理してみます。
コメント
有益な記事ありがとうございます。
コードを活用させて頂いております。ありがとうございます。
こちらのコードを実行すると、下記のエラーが出たのでclass名が変わったのかなと思ったのでデベロッパーツールで確認したところ『nk_tb_common race_table_01』というクラスに変わっていました。
その後、94行目をtable_data = soup.find(class_=’nk_tb_common race_table_01′) と変更して実行してみました。
しかし、下記のエラーが再度でました。コードの一部を変えてみたりしたのですが、結局下記のエラーが主に出てきました。
宜しければお助けいただけないでしょうか。
返信お待ちしております。
———————————————————————–
browser = webdriver.Chrome(ChromeDriverManager().install())
検索条件を設定します
検索を行います
取得したHTMLからレース結果のURLを抽出します
Traceback (most recent call last):
File “/Users//Desktop/memo.txt/keiba.py”, line 155, in
main()
File “/Users//Desktop/memo.txt/keiba.py”, line 30, in main
link_list = make_raseURL(browser, link_list)
File “/Users//Desktop/memo.txt/keiba.py”, line 96, in make_raseURL
for element in table_data.find_all(‘a’):
AttributeError: ‘NoneType’ object has no attribute ‘find_all’
———————————————————————–
エラーメッセージを見るに、netkeibaから情報を取得できていないように見受けられます。
掲載しているプログラムの動作確認をしてみました。
正常に動作していることを確認しましたので、プログラムの実行環境に影響しているかもしれません。
プログラムを実行するとchromeが自動インストールされます。
そのあとにchromeが自動起動して取得処理をseleniumで行います。
netkeibaの情報取得に時間が掛かっているかもしれません。
main関数内の下記の箇所を10から30に変更すると、うまくいくかもしれません。
browser.implicitly_wait(10) → browser.implicitly_wait(30)
迅速な返信ありがとうございます。
教えてくださったコードを書き換えてみたところ、おっしゃる通りnetkeibaから情報を取得できていなかったらしくて正常に実行することが出来ました。
ありがとうございました!
何度もすみません。統合プログラムをコピペしたら、下記のようなエラーが発生しました。コピペしたのでスペルミスはないとおもうのですが、対処法を教えてください。
NameError: name ‘output_RaceResult’ is not defined
“NameError: name ‘output_RaceResult’ is not defined” エラーは、output_RaceResult 関数が定義されていない場合に発生します。
output_RaceResult 関数の貼り付けで手違いがあった可能性があります。
対処案として、「全コードのまとめ」を掲載しました。
掲載した全コードで動作するかをお試しください。
ありがとうございます。実行はできました‼
追加で2点聞きたいのですが、
一つ目は実行時にchromeが自動で立ち上がるのですがこれは仕様ですか?
二点目は
# 期間を2010年から2022年に設定する
elem_start_year = browser.find_element(By.NAME, value=’start_year’)
elem_start_year_select = Select(elem_start_year)
elem_start_year_select.select_by_value(‘2022’)
の’2022’を’2010’に変更すればスクレイピングのスタート年を変更できるんですよね?
回答が遅くなり申し訳ありません。
コメントを見逃していました。。。
■1つ目の質問について
仕様となります。
chromeのオプションで「–headless」を付けることで、chromeを非表示することは可能です。
# WebDriverのオプションを設定
options = webdriver.ChromeOptions()
options.add_argument('--headless')
# webdriver_managerで最適なchromeのバージョンをインストールして設定する
browser = webdriver.Chrome(ChromeDriverManager().install(), options=options)
■2つ目の質問について
はい、その認識の通りです。
「elem_start_year_select.select_by_value(‘2022’)」でスクレイピングのスタート年を設定しています。