お気に入り種牡馬の産駒の出走予定をLineに通知する

# はじめに

皆さん、競馬楽しんでいますか? (笑)

以前、エスポワールシチーという馬の一口馬主をやっておりまして…
エスポワールシチーは引退後、無事種牡馬に上がり、昨年から子供たちがレースに出始めています。

一口馬主で持っている馬たちは扶養(?)しているということもあり「子供の運動会」を見る感じでレースを楽しんでいたのですが、その子供たちがレースに出るとなると「孫の運動会」になるわけです(笑)

その「孫の運動会」を見逃さないように、孫たち(?)の出走が決まったら、Lineに通知するような環境を構築してみました。

# 環境の概要

- 公益社団法人日本軽種馬協会さんが提供している[JBIS-Search](https://www.jbis.or.jp/index.html)の「種牡馬情報：産駒出走情報」を元データに使わせていただきました。
- JBIS-Searchから[requests](http://docs.python-requests.org/en/master/)を使って取得したデータから[BeautifulSoup](https://pypi.org/project/beautifulsoup4/)を使って必要な情報を抽出。
- 通知したい文章に整形し直した上で、[LINE Notify](https://notify-bot.line.me/ja/)を使って自分宛てに通知。
- Heroku Schedulerを使って上記処理を定時実行。

なお、今回は以下の記事を参考に実装しています。
非常に参考になりました。ありがとうございます。

[Pythonで少し詳しいお天気情報をLINEに通知する (1)](https://qiita.com/seigo-pon/items/7827bbf7cf6792df265a)
[Pythonで少し詳しいお天気情報をLINEに通知する (2)](https://qiita.com/seigo-pon/items/acc5537a491ebc039059)
[Herokuでお天気Pythonの定期実行](https://qiita.com/seigo-pon/items/ca9951dac0b7fa29cce0)

# 開発時のライブラリ等のバージョン

- Python 3.7.1
- beautifulsoup4 4.6.3
- mojimoji 0.0.8
- python-dotenv 0.9.1
- requests 2.20.1

# 実装

## 産駒出走情報の取得、抽出

requestsを使ってJBIS-Searchさんから情報取得、BeautifulSoupを使って必要な情報を抽出、1頭1頭の情報をnamedtupleに入れてyieldで返す関数を作ってみました。
本当は異常系に対する実装すべきですがそこは追々…

```python:jbis.py
"""JBISへのアクセス関数群."""
import re
import datetime
from collections import namedtuple

import requests
from bs4 import BeautifulSoup


def iter_sire_entries(horseid):
    """指定されたIDの種牡馬の産駒の出走予定を返す."""
    r = requests.get(f'https://www.jbis.or.jp/horse/{horseid}/sire/entry/')
    soup = BeautifulSoup(r.content, "html.parser")
    h2s = soup.find_all('h2')
    today = datetime.date.today()

    Entry = namedtuple(
        'Entry', ['date', 'course', 'raceno', 'racename', 'horsename'])

    for h2 in h2s:
        m = re.fullmatch(r"\s?([0-9]{1,2})月\s?([0-9]{1,2})日出走分", h2.string)
        month = int(m.group(1))
        day = int(m.group(2))
        date = datetime.date(
            today.year + (0 if today.month <= month else 1), month, day)

        for tr in h2.find_next('tbody').find_all('tr'):
            tds = tr.find_all('td')
            entry = Entry(date, tr.find('th').string,
                          tds[0].string, tds[1].text.strip(), tds[7].string)
            yield entry
```

## 通知したい文章に整形

上の関数で取得した出走予定を日付でグループ化、それを更にレースごとにグループ化して文章を作っています。
groupbyを使うには、事前にキーでソートされている必要がありますが、JBIS-Searchで取得した時点で少なくとも日付ではソート済み、レースごとは確証はないものの、まぁソート済みだろう…という感じなのでその前提で…
あと元のコンテンツがアルファベット等が全角になっていますので、カナ以外は半角にするため、[mojimoji](https://github.com/studio-ousia/mojimoji)を使わせてもらっています。

```python:espoircity_child_entry.py
def main():
    """メイン関数."""
    p = ArgumentParser(description='エスポワールシチー産駒の出走予定をLINE Notifyに送る')
    p.add_argument('-d', '--debug', action='store_true', help='デバッグ用')
    args = p.parse_args()

    content = f'エスポワールシチー産駒の出走予定\n'

    for date, g in groupby(jbis.iter_sire_entries('0000888832'), lambda e: e.date):
        content += f'\n{date.strftime("%m/%d")}\n'
        for r, g2 in groupby(g, lambda x: (x.course, x.raceno, x.racename)):
            racename = mojimoji.zen_to_han(r[2], kana=False)
            content += f'　{r[0]}{r[1]}R {racename}\n'
            for e in g2:
                content += f'　　{e.horsename}\n'

    if args.debug:
        print(content)
    else:
        line.notify(settings.NOTIFY_ACCESS_TOKEN, content)
```

## LINE Notifyを使っての通知

[LINE Notify](https://notify-bot.line.me/ja/)についてはリンク先を参照のこと。

LINE Notifyを使っての通知はトークンさえ作れれば非常に簡単。

```python:line.py
"""LINEへのアクセス関数群."""
import requests


def notify(token, message):
    """LINE Notifyでメッセージを送る."""
    payload = {'message': message}
    headers = {'Authorization': 'Bearer ' + token}
    requests.post('https://notify-api.line.me/api/notify',
                  data=payload, headers=headers)
```

トークンに関してはソース直書きするのはちょっと…なので、[python-dotenv](https://github.com/theskumar/python-dotenv)を使って、.envファイル、もしくは環境変数から取得するようにしました。

```python:settings.py
"""設定情報."""
import os
from os.path import dirname, join

from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

NOTIFY_ACCESS_TOKEN = os.environ.get("NOTIFY_ACCESS_TOKEN")
```

使う側は以下のようにすればOK.

```python:espoircity_child_entry.py
import settings

(省略)

line.notify(settings.NOTIFY_ACCESS_TOKEN, content)
```

## Heroku Schedulerを使っての定時実行

ここに関しては完全に[Herokuでお天気Pythonの定期実行](https://qiita.com/seigo-pon/items/ca9951dac0b7fa29cce0)のままの内容になります。わかりやすい記事で感謝です。
この記事のおかげもありますが、Herokuってこんなに楽にデプロイできるのね…というのが実感…

# 完成!
実際に12/4に通知されたLINEのキャプチャです。これでお気に入り種牡馬の産駒の出走予定を見逃さなくて済みますね!

![Screenshot_20181204-213756.jpg](https://qiita-image-store.s3.amazonaws.com/0/83499/00fe791b-e05c-8926-7bb1-231f28df4dae.jpeg)

なお、ソースは[github](https://github.com/masaminh/notify_espoircity)の方においてあります。

# 最後に

競馬もコンピュータを使って快適に楽しみましょう！(笑)