22
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

netkeibaスクレイピング

Last updated at Posted at 2021-11-05

スクレイピングは節度を守って行いましょう。(ここに書いてあるものは全て1秒以上の間隔をあけています)
また、出てくるデータが若干汚いですが、後で整形すればいいやという気持ちでやっています。
また、掲載にあたって少しコードをいじったので、動かないことがあります。

2022/02/19追記
コードまとめました。競艇と競輪のコードもあります。
個人用なのでドキュメントとか無いですけど、それでもよければ使ってみてください。
https://github.com/apiss2/scraping

レースIDの取得

netkeibaでは、各レースがレースIDという数字で管理されているようです。レース一覧のページから、当日の各レースについてレースIDを取得できます。

def get_raceid_list_from_date(today):
    date = f'{today.year:04}{today.month:02}{today.day:02}'
    url = 'https://db.netkeiba.com/race/list/' + date
    html = requests.get(url)
    html.encoding = "EUC-JP"
    soup = BeautifulSoup(html.text, "html.parser")
    race_list = soup.find('div', attrs={"class": 'race_list fc'})
    if race_list is None:
        return list()
    a_tag_list = race_list.find_all('a')
    href_list = [a_tag.get('href') for a_tag in a_tag_list]
    race_id_list = list()
    for href in href_list:
        for race_id in re.findall('[0-9]{12}', href):
            race_id_list.append(race_id)
    return list(set(race_id_list))

使用例

1年分のレースIDの一覧を取得。

def scraping_race_id_list(year):
    today = datetime.date(year, 1, 1)
    race_id_list = list()
    for i in tqdm(range(365)):
        race_id_list += get_raceid_list_from_date(today)
        today = today + relativedelta(days=1)
        time.sleep(1)
    return race_id_list

レース情報の取得

上記までに取得したレースIDを使って各レースの情報を取得します。
また、以降の関数の入力は以下のコードで得たものを入力とします。

url = f"https://db.netkeiba.com/race/{race_id}"
html = requests.get(url)
html.encoding = "EUC-JP"
soup = BeautifulSoup(html.text, "html.parser")

レース概要の把握

レース名、天候、レースの種類、コースの長さ、馬場の状態、日付など、レースに関する様々な情報を取得します。
ここらへんは改良の余地があると思います。 (見づらくて申し訳ないです)

def get_race_info(soup):
    race_name = soup.find("dl", attrs={"class": "racedata fc"}).find('h1').text
    race_info_list = [race_name]
    race_info_list += soup.find("div", attrs={"class": "data_intro"}).find_all("p")[0].find('span').text.replace('\xa0', '').split('/')
    race_info_list += soup.find("div", attrs={"class": "data_intro"}).find_all("p")[1].text.replace('\xa0', '').split(' ')
    return race_info_list

def parse_race_info(race_info_list):
    assert 8 <= len(race_info_list) <= 9
    race_info = [race_info_list[0]]
    race_info.append('' if '' in race_info_list[1] else '' if '' in race_info_list[1] else '不明')
    race_info.append('障害' if '' in race_info_list[1] else 'ダート' if '' in race_info_list[1] else '')
    race_info.append(race_info_list[3].split(' : ')[-1])  # 馬場状態
    race_info.append(re.findall('\d\d\d0m', race_info_list[1])[0][:-1])  # 距離
    race_info.append(race_info_list[2].split(' : ')[1])  # 天候
    race_info.append(race_info_list[5])  # 日次
    race_info.append(race_info_list[4].split(' : ')[1])  # 発走
    # 開催場所、タイミング
    hold_n = re.search('\d+回', race_info_list[6])
    day_n = re.search('\d+日目', race_info_list[6])
    racecourse = race_info_list[6][hold_n.span()[1]:day_n.span()[0]]
    race_info.append(hold_n[0])
    race_info.append(day_n[0])
    race_info.append(racecourse)
    # 条件
    s = race_info_list[7] if len(race_info_list)==8 else race_info_list[7] + race_info_list[8]
    sep = s.find('[') if (s.find('[') != -1) and (s.find('[') < s.find('(')) else s.find('(')
    race_info.append(s[:sep])
    race_info.append(s[sep:])  # ここが詳しく分離できない
    return race_info

また、レース種別が芝とダートの組み合わせになっており、馬場状態もそれに伴って複数出現するものもありますが、それは面倒なので考慮していません。

出走する各馬の情報を取得

該当するレースに出走する馬と騎手についての情報を取得します。各馬について詳細情報(過去のレース結果一覧など)を取得する方法については気が向いたら書きます。

def get_horse_info(html, race_id):
    # 出走馬のテーブルデータを取得
    df = pd.read_html(html.text)[0]
    df.index = [race_id] * len(main_df)
    # 馬ID、騎手IDを取得
    horse_idlist = get_idlist_from_table(soup, 'horse')
    df["horse_id"] = horse_idlist
    jockey_idlist = get_idlist_from_table(soup, 'jockey')
    df["jockey_id"] = jockey_idlist
    return df

def get_idlist_from_table(soup, target):
    # 参照先のURLから馬や騎手の固有IDを取得
    idlist = list()
    atag_list = soup.find("table", attrs={"summary": "レース結果"}).find_all(
        "a", attrs={"href": re.compile(f"^/{target}")})
    for atag in atag_list:
        target_id = re.findall(r"\d+", atag["href"])
        idlist.append(target_id[0])
    return idlist

払戻金の取得

def get_return_table(html, race_id):
    # 改行が含まれているので、置換してから読み込み
    tables = pd.read_html(html.text.replace('<br />', 'sep'))
    return_df = pd.concat(tables[1:3])
    return_df.index = [race_id] * len(return_df)
    return return_df

レース情報についてのまとめ

def scraping_race_table(race_id):
    url = f"https://db.netkeiba.com/race/{race_id}"
    html = requests.get(url)
    html.encoding = "EUC-JP"
    soup = BeautifulSoup(html.text, "html.parser")
    # 出走馬情報のスクレイピング
    main_df = get_horse_info(html, race_id)
    # レース情報のスクレイピング
    race_info = get_race_info(soup)
    race_info = parse_race_info(race_info)
    # 払い戻し情報のスクレイピング
    return_df = get_return_table(html, race_id)
    return main_df, return_df, race_info

使用例

target_dir = 'path/to/save_dir'
race_id_list = scraping_race_id_list(2020)
for race_id in race_id_list:
    main_df, return_df, race_info = scraping_race_table(race_id)
    # 保存
    path = os.path.join(target_dir, 'race_info.csv')
    with open(, 'a', encoding='utf-8') as f:
        s = ','.join(race_info)
        f.write(f'{race_id},' + s+'\n')
    path = os.path.join(target_dir, 'horse', f'{race_id}.csv')
    main_df.to_csv(path, encoding='utf-8')
    path = os.path.join(target_dir, 'prize', f'{race_id}.csv')
    return_df.to_csv(path, encoding='utf-8')
    time.sleep(1)
22
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?