Pythonを使ったスクレイピング
解決したいこと
Web上に無料で公開されているソースコードを使い、スクレイピングを行っております。
しかしエラーが解決できずに困っております。解決方法を教えてください。
スクレイピング先URL:https://db.netkeiba.com/
使用言語:Python
発生している問題・エラー
Traceback (most recent call last):
File "d:\Python\hellogood.py", line 238, in <module>
main()
File "d:\Python\hellogood.py", line 46, in main
df_RaceResult = pd.concat([df_RaceResult, output_RaceResult(url)], axis=0)
^^^^^^^^^^^^^^^^^^^^^^
File "d:\Python\hellogood.py", line 184, in output_RaceResult
tables = tables.find_all('tr')
^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'find_all'
該当するソースコード
# ライブラリの読み込み
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
# メイン
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")
# 検索条件を設定して検索する
def search_rase(browser):
print('検索条件を設定します')
# 競争種別で「芝」と「ダート」にチェックを入れる
elem_check_track_1 = browser.find_element(By.ID, value='check_track_1')
elem_check_track_2 = browser.find_element(By.ID, value='check_track_2')
elem_check_track_1.click()
elem_check_track_2.click()
# 期間を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')
# 月を指定する場合は、select_by_valueで月数を指定する
elem_start_month = browser.find_element(By.NAME, value='start_mon')
elem_start_month_select = Select(elem_start_month)
elem_start_month_select.select_by_value('1')
elem_end_year = browser.find_element(By.NAME, value='end_year')
elem_end_year_select = Select(elem_end_year)
elem_end_year_select.select_by_value('2022')
# 月を指定する場合は、select_by_valueで月数を指定する
elem_end_month = browser.find_element(By.NAME, value='end_mon')
elem_end_month_select = Select(elem_end_month)
elem_end_month_select.select_by_value('2')
# 画面を下にスクロールする
# browser.execute_script('window.scrollTo(0, 400);')
# 表示件数を100件にする
elem_list = browser.find_element(By.NAME, value='list')
elem_list_select = Select(elem_list)
elem_list_select.select_by_value('100')
# 検索をクリック(submit)する
elem_search = browser.find_element(By.CLASS_NAME, value='search_detail_submit')
elem_search.submit()
print('検索を行います')
def make_raseURL(browser, link_list):
print('取得したHTMLからレース結果のURLを抽出します')
html = browser.page_source.encode('utf-8') # UTF-8でHTMLを取得する
soup = BeautifulSoup(html, 'html.parser') # 検索結果をbeautifulSoupで読み込む
table_data = soup.find(class_='nk_tb_common') # 検索結果のテーブルを取得する
for element in table_data.find_all('a'):
url = element.get('href') # リンクページを取得する
# リンクページがjavascriptの場合は、次のリンクページの処理に移る
if 'javascript' in url:
continue
# リンクページの絶対URLを作成する
link_url = urllib.parse.urljoin('https://db.netkeiba.com', url)
# レース結果のみを抽出する
# レース結果のURLは'https://db.netkeiba.com/rase/yyyyXXmmddXX'となる
# 馬情報のURLは'https://db.netkeiba.com/horse/yyyymmddXXXX'
# 騎手情報のURLは'https://db.netkeiba.com/jockey/result/recent/'
# レース結果以外のリンクページを除外するための単語リストを用意する
word_list = ['horse', 'jockey', 'result', 'sum', 'list', 'movie']
tmp_list = link_url.split('/') # リンクページのURLを'/'で分割する
and_list = set(word_list) & set(tmp_list) # word_listとtmp_listを比較し、一致している単語を抽出する
# 一致している単語が0のリンクページのURLのリストを作成する
if len(and_list) == 0:
link_list.append(link_url)
print('URLを抽出しました')
return link_list
# 画面下にスクロールして「次」をクリックする
def click_next(browser, count_click_next):
# 画面を下にスクロールする
browser.execute_script('window.scrollTo(0, 2500);')
# 次をクリックする
# 検索1ページ目のxpathは2ページ以降とは異なるため、count_click_nextで
# 検索1ページ目なのかを判定している
if count_click_next == 0:
xpath = '//*[@id="contents_liquid"]/div[2]/a'
elem_search = browser.find_element(By.XPATH, value=xpath)
elem_search.click()
no_click_next = 0
else:
# 検索最後のページで次をクリックしようとすると例外処理が発生する
# exceptで例外処理を取得し、no_click_nextに1を代入する
try:
xpath = '//*[@id="contents_liquid"]/div[2]/a[2]'
elem_search = browser.find_element(By.XPATH, value=xpath)
elem_search.click()
no_click_next = 0
except:
print('次のページ無し')
no_click_next = 1
count_click_next += 1 # ページ数を判別するためのフラグに1を加算する
return no_click_next, count_click_next
# レース結果の詳細を取得する
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
if __name__ == "__main__":
main()
自分で試したこと
他言語の経験はありますが、Pythonは初めて使います。一つ一つの意味を調べながらエラー解決を目指しておりましたが、問題が解決できませんでした。
追記
コメント頂き、mainの呼び出しを最後に致しました。