ガンプラ出荷日を自動で Google カレンダーに出力してみた。

 以下がガンプラ出荷日を出力した Google カレンダー(こちらが共有用のリンク)です。バンダイが公表している PDF から製品と出荷日をスケジュールします。カレンダーを共有していますのでご自分の Google カレンダーに表示することが可能です。

 ただ今は一枚目の PDF だけを対象としています。ガンプラ以外のプラモデルの出荷も PDF には記載されています。プログラム上どれがガンプラかそれ以外かを判別する方法が分からず。過去数ヶ月の PDF を見た限り一枚目だけで対象とすればガンプラの出荷は漏れなくピックアップ出来そうだったので1枚目だけを対象にしています。今後に期待です。プログラムの詳細は後述していますので興味があれば見て頂ければ幸いです。

これをやろうとした背景

 最近のガンプラの新商品の予約はミノフスキー・ドライブを軽く超える速度で売り切れます。なので新商品を発売日に手に入れることは若干諦めています(手に入れてもすぐ作らないし)。しかし今は新商品だけじゃなくて過去に発売したガンプラも軒並み売り切れ、もしくは定価以上の価格で売られています。おそらくはコロナの巣ごもり需要やガンプラ生産の資材の供給不足が原因だとは思いますが(あと転売。本当○んでくれないかな。)、欲しい時に手に入らないのは中々ストレスです。

定価 6,600 が1万超え。昔はこんなこと無かったんだけどな…

 そこで欲しいガンプラを可能な限り手に入れる可能性を高めるためにバンダイが月一で公表している出荷表を毎月見ています。そしてその中に欲しいガンプラがあれば出荷日を Google カレンダーにスケジュールする、ということを続けていたのですが手作業が面倒くさくなってきた(あと PDF がお世辞にも見やすいものではない)ので自動化してみることにしました。

ナイチンゲールとΞガンダムが欲しい

 ちなみに今月 7/29 に HGUC Ξガンダムの出荷7月の出荷表)があります。実際に店頭に並ぶのはその二日後ということらしいのでこれは確実に抑えたいところです。

プログラムとその実行

 自動化のためのプログラムが以下です。python で作っています。そして毎月これを実行します。今のところは残念ながら手動です。AWS Lambda で定期実行することを考えています。

※ 7/27 更新
バンダイが公開している PDF が月によってカラム名が変更される可能性があるのでカラム名に依存しないように処理を変更しています。

# coding: UTF-8
from tabula import read_pdf
import pandas as pd
import os
import requests
from bs4 import BeautifulSoup
import re
import datetime
import time
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials

# Google Setting
#CALENDER_ID = "*********************@group.calendar.google.com" #prod
CALENDER_ID = "*********************@group.calendar.google.com" #test
SCOPES      = ['https://www.googleapis.com/auth/calendar']

# PDF ダウンロード URL を取得
html = requests.get("https://bandai-hobby.net/site/schedule.html")
soup = BeautifulSoup(html.content, "html.parser")
onclick = soup.find(value="PDFダウンロード", type="button")['onclick']
pdf_url = 'https://bandai-hobby.net/site/' + re.sub('window.open\(\'|\'\)$', '', onclick)

# PDF を取得、変換
df = read_pdf(pdf_url)
df = pd.concat(df)

"""
//想定されるデータ構造//
0            NaN        NaN                                 NaN    NaN         NaN      NaN               NaN                  NaN                         NaN    NaN        NaN  NaN
1   <THE ORIGIN>        NaN                                 NaN    NaN  <ガンダムデカール>      NaN               NaN                  NaN                         NaN    NaN        NaN  NaN
2        5057656          9            024HG シャア専用ザクII 赤い彗星Ver.  1,800         10日      NaN           5061135                    2             GD MG 汎用-Zシリーズ用    400        26日  NaN
3        5057576          0                  025HG ザクII C-6/R6型  1,800         30日      NaN           5057493                    0            GD MG 汎用-逆襲のシャア用    400        26日  NaN
4        5058929          3     026HG RX-78-02 ガンダム ORIGIN Ver.  2,300         10日      NaN           5057511                    1               GD 逆シャア 連邦 汎用    400        26日  NaN
5        5059154          8                          HG ガンダムFSD  2,200          5日      NaN           5057512                    8            GD 逆シャア ネオジオン 汎用    400        26日  NaN
(...)
53          <SDW     EROES>                                 NaN    NaN         NaN  5061610                 4   MG RX-78-2 Ver.3.0                       4,500     5日        NaN  NaN
54       5061784          2                     曹操ウイングガンダム 倚聖の装    800         26日      NEW               NaN                  NaN                         NaN    NaN        NaN  NaN
"""

# 1行づつキット名と日付を抽出
dt_now = datetime.datetime.now()
for row in df.fillna('-').itertuples():
    for x in row:
        # 文字列 "XX日" を含む要素を抽出
        if re.compile("[0-9][0-9]?日").search(str(x)):
            date_indexs = [i for i, y in enumerate(list(row)) if y == x]
            for date_index in date_indexs:
                # 出荷日を yyyy-mm-dd に整形
                # 出荷日がX月Y日の場合
                if '月' in x:
                    month  = str(re.sub('月\d?日', '', x))
                    day    = str(re.sub('\d月|日', '', x))
                    # 来月が1月の場合は来年
                    if month == '1':
                        year = str(dt_now.replace(year = now.year + 1).year)
                    else:
                        year = str(dt_now.year)
                else:
                    year  = str(dt_now.year)
                    month = str(dt_now.month)
                    day   = str(re.sub('日', '', x))
                date = year + '-' + month + '-'  + day

                # キット名
                # 商品名頭の数字を除去
                name = re.sub('^([0-9]|[ ])+', '', row[date_index - 2])
                if 'NEW' in str(row[date_index + 1]):
                    name = '【NEW】' + name

                print(name,date)
            # 一行に同日があった場合は for 文を抜ける
            if len(date_indexs) == 2:
                break

            # GoogleCalender に予定を登録
            # TODO 同一のキット、出荷日の場合は登録をしない処理を入れる
            creds = Credentials.from_authorized_user_file('token.json', SCOPES)
            service = build('calendar', 'v3', credentials=creds)
            calender_event = {
              'summary': name,
              'location': '',
              'description': '',
              'start': {
                'date': date,
                'timeZone': 'Japan',
              },
              'end': {
                'date': date,
                'timeZone': 'Japan',
              },
            }
            calender_event = service.events().insert(calendarId=CALENDER_ID, body=calender_event).execute()

参考サイト

PythonでPDFの表からデータを抽出する

【Python】Google Calendar APIを使ってGoogle Calendarの予定を取得・追加する