蛇ノ目の記

技術のことも。そうでないことも。

アイドルで理解するiCalendar

Webサイトをスクレイピングしてイベント情報を取得、iCalendar形式で出力してGoogleカレンダーにインポートできるようにした、という話。

リポジトリ

github.com

背景

3/13(土)に開催されたIDORISE!! FESTIVAL 2021で初めて観たCYNHNがあまりにかっこよく、楽曲にドハマリしたので現場に行きたいと思いスケジュールをチェック。しかしWebサイトに直接書かれているタイプだったのでなんとかしてGoogleカレンダーに登録したいという気持ちになった。そこでGoogleカレンダーにインポートできるicalendar形式に着目した。

『イナフイナス』がかっこよすぎる。綾瀬志希さん(5人がテーブルに座っているシーンでは真ん中)のダークさがすごくてよい。

www.youtube.com

『水生』もいいぞ。

www.youtube.com

なお、CYNHNは"スウィーニー"と読む。ロシア語で青の意(ロシア語では実際にはСиний と綴る)。余談だがロシア語ではSの発音はCで表し、Nの発音はHで表す。学生の頃に時間割に余裕ができてロシア語入門で1単位取ったがもはやキリル文字を読むのが限界。

現場に行きたいと思い

と書いたものの、4/10(土)に開催されるワンマンの一般チケットは発売と同時にアクセスしたが繋がらず敗北。プレイガイド各位はサーバーを強くしてくれという気持ちが強まった。

icalendar形式 is 何

macOSのカレンダーやGoogleカレンダーといったスケジュール管理アプリに対応したファイル形式。icalと略される。拡張子は.ical.ics。仕様はRFC 5545で定義されている。

構成は以下のような感じ。

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//会社名//プロダクト名//国名
BEGIN:VEVENT
# イベント情報を定義する
END:VEVENT
END:VCALENDAR

今回のコードではPRODID-//nao_y//CYNHN Unofficial Calendar//JPとしている。nao_yは会社名だった……?

イベント情報には

  • SUMMARY: イベント名称
  • DESCRIPTION: 概要
  • CATEGORY: カテゴリ
  • DTSTART: 開始時刻
  • DTEND: 終了時刻

などがある。

BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//nao_y//CYNHN Unofficial Calendar//JP
BEGIN: VEVENT
SUMMARY:CYNHN ONE MAN LIVE「Blue Spring」 東京・harevutai
DTSTART:TZID=Asia/Tokyo;VALUE=DATE-TIME:20210410T143000
DTEND:TZID=Asia/Tokyo;VALUE=DATE-TIME:20210410T170000
CATEGORY:LIVE
DESCRIPTION:DESCRIPTION:https://cynhn.com//contents/405411\n\nCYNHN ONE MAN LIVE 「Bl
 ue Spring」\n2021/4/10(土)\n会場:harevutai(東京都豊島区東
 池袋1-19-1 Hareza池袋 1F)\n【1部】開場14:30/開演15:00\n【2
 部】開場18:00/開演18:30 . . .
END:VEVENT
END:VCALENDAR

Pythonパッケージ

Pythonにおいてはicalendarがある。日本語の資料としてはtokibitoさんによる以下の記事がとてもわかりやすい。

tokibito.hatenablog.com

スクレイピング対象

CYNHN公式サイトのスケジュール及びイベント詳細ページ。

スケジュールは以下のようにページ内に描画されている。

f:id:Nao_Y:20210328164906p:plain

Headlessでカレンダーが取得できない問題

ぱっと見でJSで描画されていることがわかるのでSeleniumを使うことにした。いちいちブラウザが立ち上がるのも面倒なのでHeadless Chromeを使用。しかしいくら試してもカレンダーのDOMが取得できない。そこでHeadlessを諦めて普通にChromeを使うこととした。

カレンダーの日付がイベントに紐付いていない問題

見た目上は各マスにイベントが収まっているが、DOMを見るとマス内のイベントと日付が紐付いていないことがわかる。そこでイベントのリンクから詳細ページのURLを取得、詳細ページから開催日を取得することとした。

綾瀬志希さんの個展のように連続で開催されるイベントについては初日のみicalendarに登録した。

ラジオ番組の詳細ページ

毎週水曜にCYNHNメンバーが出演するラジオ番組があるが、この詳細ページは初回放送時のものに固定されており放送日が取得できない。そこでラジオ番組については詳細ページを参照せず、その月の木曜日を算出することとした。

calendarモジュールのmonthdays2calendar(year, month)を使うことで指定した年月の週のリストが取得できる。

cal = calendar.Calendar(today.year)
weeks = cal.monthdays2calendar(today.year, today.month)
radio_days = []
for week in weeks:
    for day in week:
        if day[1] == 3:  #木曜日は3で表現される
            . . .

今回のソースコードではadd_radio_scheduleという関数でやっている。 https://github.com/NaoY-2501/ical_with_idol/blob/master/ical_with_idol.py#L150L182

Pythonでicalendar形式

スクレイピングにおけるつらみをつらつらと書いたが、ようやく本題。

カレンダーに予定を追加する

基本的にはCalendarオブジェクトのインスタンスを作り、そこにEventオブジェクトのインスタンスを追加していくといった感じになる。

from datetime import datetime

from icalendar import Calendar, Event
import pytz

ical = Calendar()  # calendarという変数名にしたくなるがcalendarオブジェクトと被るので注意
ical.add('version', '2.0')
ical.add('prodid', '-//nao_y//Sample Calendar//JP')
event = Event()
event.add('summary', 'アイドルで理解するiCalendarを書き上げる')
event.add('description', '投稿するところまでやる')
event.add('category', 'ブログ執筆')
event.add('dtstart', datetime(2021, 3, 30, 19, 0, 0, tzinfo=pytz.timezone('Asia/Tokyo')))
event.add('dtend', datetime(2021, 3, 30, 20, 0, 0, tzinfo=pytz.timezone('Asia/Tokyo')))
ical.add_component(event)

これでひとまずカレンダーに予定を追加することができた。

Calendar.add()Event.add()の第一引数はiCalendar形式のVCALENDAR, VEVENTに定義する項目と一致するが、小文字であることに注意。

カレンダーを出力する

出力にはCalendar.to_ical()を使う。

with open('mycalendar.ics', 'wb') as fout:
    fout.write(ical.to_ical()

出力されるファイルはUTF-8だが、書き込むときにはバイナリとして扱う。

できあがったicsファイルはGitHubに置いておけばRAWの方のURLでGoogleカレンダーにインポートできるし、ローカルではmacOSのカレンダーアプリに登録できる。

今回作成したカレンダーは https://raw.githubusercontent.com/NaoY-2501/ical_with_idol/master/CYNHN_Unofficial_Calendar.ics から参照できる。もしよかったらGoogleカレンダーに登録してみてください。(記事執筆時点では)3/1~4/10までのCYNHNのスケジュールが見れます。

ところでGitHubに上げたicalファイルを更新したらGoogleカレンダーの方にも反映されるんだろうか。まだ試してないのでそのあたりがわかっていない。カレンダーを更新すればいけそうな気もするが。

今後の課題

数ヶ月先までのスケジュールの取得

Seleniumを使っているので翌月に送るボタンを押せばいいように思うが、スクレイピングが一筋縄でいかない構造のWebページなので苦戦しそう。

自動化

月次作業めいて手動で更新するのもいいが、面倒なのでEC2あたりにスクリプトを置いてicsファイルのpushまで自動化したい。 しかし実際にブラウザを開いているのでサーバー上でできるのか、という懸念がある。