バスケ(Bリーグ)のデータをサーバレスに取得する


昔、<a href="https://qiita.com/itatibs/items/2b81c474ac1d32354dfb" target="_blank">ゆるふわに始めるバスケの分析</a>でやったものを、もう少し発展させてデータを取得し、GCPのStorageに保存できるまでをcloud functionsを使って実装しました。

# 構成
今回はスポナビのBOXスコアを1試合ずつ取得して、tsvファイルにしてStorageに保存します。
そのため、cloud functionsのpython3.7でトリガーをHTTPにしました。
HTTPトリガーのパラメータに試合日や試合番号を指定することで特定の試合結果を取得します。

フォルダ構成は以下のようにしました。

```
gcf_bleague_boxscore
├── main.py
└── requirements.txt
```

# 実装

pythonのBeautifulSoupでデータを取得し、取得したいデータを整形します。
cloud functionsの実行環境では/tmpを一時領域として使うことができるため、そこを利用してtsvファイルを作成しています。

```python:main.py
import pandas as pd
from bs4 import BeautifulSoup
import urllib.request
import re
from google.cloud import storage

def bleaue_boxscore(request):
    request_json = request.get_json()

    # 基本情報の設定
    if request_json and 'target_date' in request_json:
        TARGET_DATE = request_json['target_date']
    else:
        TARGET_DATE = '19900101'
    if request_json and 'target_game' in request_json:
        TARGET_GAME = request_json['target_game']
    else:
        TARGET_GAME = '3269'
    if request_json and 'div' in request_json:
        DIV = request_json['div']
    else:
        DIV = 'b1'
    URL_TOP = 'https://sportsnavi.ht.kyodo-d.jp/basketball/stats/'+DIV+'/score/'+TARGET_GAME+'/'
    URL_BOXSCORE =  URL_TOP+'box/'
    OUTPUT_FILENAME = 'boxscore_P'+TARGET_DATE+TARGET_GAME+'.tsv'
    
    # チーム名の取得
    html = urllib.request.urlopen(URL_TOP)
    top = BeautifulSoup(html, 'html5lib')
    team = top.find_all('div', class_='name')
    home_name = team[0].text.translate(str.maketrans({' ': '', '\n': ''}))
    away_name = team[1].text.translate(str.maketrans({' ': '', '\n': ''}))
    
    # 試合情報の取得(日付,tipoff,試合会場,節,カテゴリ) 
    loc = top.find('div', class_='note').string
    section = top.section.h1.string
    game_date = re.match(r'[0-9]{4}年[0-9]{1,2}月[0-9]{1,2}日' , loc).group()
    tipoff_time = re.search(r'[0-9]{1,2}:[0-9]{1,2}' , loc).group()
    arena = re.sub(r'.*試合会場：', '' , loc)
    sec = re.search(r'第[0-9]{1,2}節', section).group().translate(str.maketrans({'第': '', '節': ''}))
    div = re.match(r'B[0-9]{1}', section).group()
    
    # それぞれのボックススコアを取得
    tables_home = pd.read_html(URL_BOXSCORE + 'h/', flavor='bs4')
    tables_away = pd.read_html(URL_BOXSCORE + 'a/', flavor='bs4')
    
    # ボックススコアに情報を追加
    boxscore_home = tables_home[1].dropna(subset=['#']).assign(
                                        team=home_name,
                                        game_date=game_date,
                                        tipoff_time=tipoff_time,
                                        arena=arena,
                                        sec=sec,
                                        div=div)
    boxscore_away = tables_away[1].dropna(subset=['#']).assign(
                                        team=away_name,
                                        game_date=game_date,
                                        tipoff_time=tipoff_time,
                                        arena=arena,
                                        sec=sec,
                                        div=div)
    boxscore = pd.concat([boxscore_home, boxscore_away],ignore_index=True)
    
    boxscore.to_csv('/tmp/'+OUTPUT_FILENAME, sep='\t' ,header=False, index=False)

    # storageにファイルをアップロード
    storage_client = storage.Client()
    bucket = storage_client.get_bucket('ストレージのバケット名')
    blob = bucket.blob('data/'+OUTPUT_FILENAME)
    blob.upload_from_filename('/tmp/'+OUTPUT_FILENAME)

    return f'functions finished.'+OUTPUT_FILENAME
```

```python:requirements.txt
google-cloud-storage
bs4
html5lib
pandas
```

以下のコマンドでデプロイします。

```
gcloud beta functions deploy bleaue_boxscore --trigger-http --runtime=python37
```

# 使ってみた感じ
デプロイが完了するとAPIのURLが払い出されるのでリクエストパラメータに試合情報を入力してPOSTすることで、データの取得が可能になります。

```
curl -X POST https://[リージョン]ー[プロジェクト名]cloudfunctions.net/bleaue_boxscore -H "Content-Type:application/json" --data '{"target_date":"20190421","target_game":"3525","div":"b1"}'
```

<img width="1039" alt="img01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231123/048f9cad-df3d-0d40-357b-543609521fa3.png">