🏀

東京オリンピックのバスケを逃さず見たいのでGoogle Apps Scriptで自動でカレンダーにイベント作る

2021/07/25に公開

(自分で書いた技術ブログ

https://tech.motoki-watanabe.net/entry/2021/07/25/130852

をそのまま転載しています)


結果

https://github.com/XxGodmoonxX/gas-tokyo2020_basketball_calender

上記リポジトリです。

動機

Google Apps Script、地味に便利でSlackとの連携とか色々使えるのですが使ったことがなかったので使ってみたかったのですよね。
そして東京オリンピックのバスケをちゃんと見たいので自分のGoogleカレンダーに登録しようと思ってちまちまやっていたのですが3x3も含めるとけっこう多くてしんどいなあと思い、いままさにここでGoogle Apps Scriptの出番!!と思いやりました。結局ちまちま登録したほうが早いのはその通りなのですが気にしない。未来の自分の糧にはなってる。

あと勉強のためにスプレッドシートに記入してみたかったのでしていますが本来カレンダーに直接登録すれば良い。

説明

スクレイピングしてWebサイトから情報を取得する

https://qiita.com/takaito0423/items/259097b55b026800c875

上記を参考に。

まずはスプレッドシートを新規で作成し、ツール>スクリプトエディタからgasのファイルを作成。(よくわかっていないけれどたぶんここから作ればスプレッドシートとスクリプトを繋ぎ合わせることが出来るのだと思う)
そして、URLから必要な情報を取得。gorin.jpのバスケのページのhtmlをまず取得します。

const URL = {
  MEN: 'https://www.gorin.jp/game/BKBMTEAM5-------------/',
  WOMEN: 'https://www.gorin.jp/game/BKBWTEAM5-------------/',
  MEN_3x3: 'https://www.gorin.jp/game/BK3MTEAM3-------------/',
  WOMEN_3x3: 'https://www.gorin.jp/game/BK3WTEAM3-------------/'
}

URLを変数に入れておく。男子女子のバスケと3x3バスケそれぞれ。

function getCalender(url) {
  const html = UrlFetchApp.fetch(url).getContentText()
}
// getCalender(URL.MEN) でそのページのhtmlとれる

で、このhtmlから情報を抽出する。Parserというライブラリを使います。上記Qiitaを参考に入れて使います。

const gameList = Parser.data(html).from('<div class="list-in">').to('</div>').iterate()
let data = [];
gameList.map((game, index) => {
  if(!game.match( url === URL.MEN ? /日本|米国/ : /日本/)) return // 男子だけはアメリカも含み、それ以外は日本以外無視
  game = game.replace('<span> - </span>', '') // 不要な部分を削除
  const time = Parser.data(game).from('<span class="ms">').to('</span>').build() // 時間を抜き出す ex: '7/29 13:40'
  const competition =  Parser.data(game).from('<span>').to('</span>').build() // 対戦を抜き出す ex: '日本 vs スロベニア'
  data = [...data, {time: time, competition: competition}]
})
return data

gorin.jpのページ構成として<div class="list-in">で各試合が囲われていたのでそのブロックを抽出し、iterate()でとって配列として変数に入れます。
で、その配列をmapして1つずつ処理。matchで中身に日本が含まれていなかったらそこで処理を止める。(男子バスケだけはアメリカ戦も気になるので、米国も含む)
そこからも必要なブロックをParserの機能を使って抜き出します。

スプレッドシートに抜き出した情報を記入する

const SHEET_NAME = 'GorinJpから出力するシート'
const data = [{
  class: CLASS.MEN,
  calendar: getCalender(URL.MEN),
  url: URL.MEN
},
{
  class: CLASS.WOMEN,
  calendar: getCalender(URL.WOMEN),
  url: URL.WOMEN
},
{
  class: CLASS.MEN_3x3,
  calendar: getCalender(URL.MEN_3x3),
  url: URL.MEN_3x3
},
{
  class: CLASS.WOMEN_3x3,
  calendar: getCalender(URL.WOMEN_3x3),
  url: URL.WOMEN_3x3
}]
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
sheet.clear() // シートの中身を消す
sheet.appendRow(['種別', '開始時間', '対戦', 'URL'])
data.map((data) => { // dataを1つずつ処理
  data.calendar.map((calender) => { // data.calenderを1つずつ処理
    sheet.appendRow([data.class, calender.time, calender.competition, data.url])
  })
})

GorinJpから出力するシートというシートを作って、それを取得。処理時まず一旦シートの中身を消して、それから必要な情報を入れる。下記を参考にappendRowで最終行に自動で追加できる。
https://blog.8basetech.com/google-apps-script/gas-spleadsheet-setvalue/

スプレッドシートの情報を元にGoogleカレンダーにイベントを登録

const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
const calendar = CalendarApp.getCalendarById(CALENDER_ID); // 対象のカレンダーをCalenderオブジェクトとして取得

シートは同様に取得。下記を参考にカレンダーも取得。

https://tonari-it.com/gas-calendar-first-step/

カレンダーのIDは世に公開せず使うだけなら生で書いておけばよいのですが、Githubにあげておきたかったので下記を参考に環境変数として登録しておく。ちなみになぜかスクリプトプロパティの登録は新しいエディタでは出来ず以前のエディタではないと出来ないので、そのときだけ以前のエディタに戻す必要がある。

https://qiita.com/Massasquash/items/2209ff367d65c5dd6181

const CALENDER_ID = PropertiesService.getScriptProperties().getProperty('CALENDER_ID')

そしてシートから情報を取得。行ごとに情報を取得できる。

const data = sheet.getSheetValues(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn())

で、この情報を元にカレンダーに登録。

data.map((data, index) => {
  /** タイトル */
  const title = `東京オリンピック バスケットボール ${data[0]} ${data[2]}`;
  /** 開始時間 */
  const startDate = new Date(data[1]);
  /** 3x3ではないとき試合は2時間 */
  const gametimeHour = data[0] === CLASS.MEN || data[0] === CLASS.WOMEN ? 2 : 0;
  /** 3x3の場合試合は20分 */
  const gametimeMinutes =
    data[0] === CLASS.MEN || data[0] === CLASS.WOMEN ? 0 : 20;
  /** 終了時間 3x3かそうでないかで試合終了時間を計算 */
  const endDate = new Date(
    startDate.getFullYear(),
    startDate.getMonth(),
    startDate.getDate(),
    startDate.getHours() + gametimeHour,
    startDate.getMinutes() + gametimeMinutes
  );
  const url = data[3];
  if (calendar.getEvents(startDate, endDate, { search: title }).length > 0)
    return; // 既にイベント作ってあったら無視。開始時間から終了時間の間に同じタイトルのものがあれば無視
  calendar.createEvent(title, startDate, endDate, { description: `${url}` }); // イベント作成
});

下記参考に、calenderからoption付きでgetEventsして、開始時間と終了時間を絞った上でoptionではタイトルとして入れる予定の文字列でsearchをかけて既に同じイベントがないかを探し、あったらカレンダーのイベントは作らないようにする。
なぜこうしているかというと、毎日時間で動かしたいので。この処理がないと同じイベントが毎日作られてしまうため。

https://developers.google.com/apps-script/reference/calendar/calendar-app#geteventsstarttime,-endtime,-options

これでカレンダーに登録出来ます。

毎日同じ時間に動かす

トリガーから時間主導型で時刻を選択し、実行する関数を選ぶといけます。

Gitで管理

https://chrome.google.com/webstore/detail/google-apps-script-github/lfjcgcmkmjjlieihflfhjopckgpelofo?hl=ja

上記拡張機能を使うとgasのエディタ上から直接Githubにコミットしたりなど出来ます。

最後に

なんか間違っているとか指摘等諸々あれば教えて下さい

Discussion