netkeibaスクレイピング

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

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

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

```python
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の一覧を取得。

```python
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を使って各レースの情報を取得します。
また、以降の関数の入力は以下のコードで得たものを入力とします。

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

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

```python
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
```

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

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

```python
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
```

## 払戻金の取得

```python
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
```

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

```python
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
```

### 使用例
```python
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)
```