#初めに
AtCoderのコンテストが不定期に開催されているが、時々予定に入れ忘れたりして、家族に迷惑をかけそうだったので自動化することにした(できるかは分からない)。
実行にはGoogleColaboratoryを使用した。
プログラミング初心者なのでコードが煩雑だったり間違ってたりするかもしれません。ごめんなさい。
#AtCoderからGoogleCalendarまで
###手順
- AtCoderからPythonのrequestsやBeautifulSoupなどを使ってスクレイピング
- スクレイピングしたデータをPythonのgspreadなどを使いGoogleSpreadSheetsにまとめる
- GoogleSpreadSheetsからGASを使ってGoogleCalendarに予定を作成
##1. スクレイピング
実際のコード
AtCoderの開催予定コンテストの名前、開催日、リンクのみを、AtCoderホームページの「コンテスト一覧」から取得してlistにする。
from bs4 import BeautifulSoup # BeautifulSoupのインポート
import requests # requestsのインポート
import datetime
import re
url = "https://atcoder.jp/contests/"
response = requests.get(url).text
soup = BeautifulSoup(response, 'html.parser') # BeautifulSoupの初期化
tags = soup.select("tbody a") # tbody下のaタグを全て選択
l=[]
l_n=[]
l_link=[]
l_n1=[]
now=datetime.datetime.now().strftime('%Y-%m-%d %H:%M') # 現在の年、月、日、時間を取得
for i in tags:
l.append(i.text) # aタグ内のテキストを取得
l.append(i.get("href")) # aタグに付いてるリンクを取得
l.remove("practice contest")
l.remove("/contests/practice")
l.remove("AtCoder Library Practice Contest")
l.remove("/contests/practice2")
l=[l[i:i + 4] for i in range(0,len(l), 4)]
for i in range(len(l)):
l[i][0]=l[i][0][0:16]
del l[i][1]
if (l[i][0][0:4] > now[0:4]) or (l[i][0][0:4] == now[0:4] and l[i][0][5:7] > now[5:7])
or (l[i][0][0:4] == now[0:4] and l[i][0][5:7] == now[5:7] and l[i][0][8:10] >= now[8:10]): # 過去のコンテストをリストから削除
l_n.append(l[i])
for i in range(len(l_n)):
l_link.append("https://atcoder.jp"+l_n[i][2]) # 相対リンクを絶対リンクに変更
コンテストの開始時間、終了時間、Ratedやペナルティなどは取得しづらかったので、それぞれのリンクに飛んで取得した。
for i in l_link:
url_n=i
response_n = requests.get(url_n).text
soup_n = BeautifulSoup(response_n, 'html.parser') # BeautifulSoupの初期化
tags2=soup_n.select("span.mr-2")+soup_n.select("small.contest-duration") # Ratedやペナルティ、開催時間などを取得
for j in tags2:
l_n1.append(j.text)
l_n1=[l_n1[i:i + 4] for i in range(0,len(l_n1), 4)]
for i in range(len(l_n1)):
l_n1[i][1]=l_n1[i][1][13:]
l_n1[i][1]=l_n1[i][1].replace("-","~")
l_n1[i][2]=l_n1[i][2][9:]
l_n1[i][3]=re.sub("\n","",l_n1[i][3])
l_n1[i][3]=re.sub("\t","",l_n1[i][3])
l_n1[i][3]=l_n1[i][3][54:60]
del l_n1[i][0]
for i in range(len(l_n)):
l_n[i]+=l_n1[i]
(参考:https://dividable.net/programming/python/python-scraping)
##2. データをスプレッドシートへ
取得したデータをスプレッドシートに貼り付ける。
from google.colab import auth
from oauth2client.client import GoogleCredentials
import gspread
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())
worksheet = gc.open('AtCoderNewContestList').get_worksheet(0) # AtCoderNewContestListという名前のスプレッドシートの1シート目を指定
for i in range(len(l_n)):
if worksheet.update_acell("B"+str(i+2),l_n[i][1]) in worksheet.range('B2:B10'): # コンテストがもう追加されていたら処理をしない
continue
else:
worksheet.update_acell("B"+str(i+2),l_n[i][1])
worksheet.update_acell("C"+str(i+2), l_n[i][0][0:4]+"/"+l_n[i][0][5:7]+"/"+l_n[i][0][8:10])
worksheet.update_acell("D"+str(i+2),l_n[i][0][11:])
worksheet.update_acell("E"+str(i+2),l_n[i][-1])
worksheet.update_acell("F"+str(i+2),l_n[i][3])
worksheet.update_acell("G"+str(i+2),l_n[i][4])
worksheet.update_acell("H"+str(i+2),"https://atcoder.jp"+l_n[i][2])
worksheet.update_acell("A"+str(i+2),"")
結果が以下。
割とうまくいっている。
##3. GASで予定を追加
GASを使ってGoogleCalendarに予定を追加する。
諸々の事情から、新しいGoogleアカウントを作って、作成したスプレッドシートにアクセス、GASでデータを抜き取り予定を作成、とすることにした。
ら、初めに作ったアカウントにアクセスできなくなった。なんで?
スプレッドシートの「ツール」から「スクリプトエディタ」を選択するとGASのコードが書ける。
(GASはコードの内容は理解できるが書けはしないので、下のサイトのコードをコピーして編集した。)
(参考:https://qiita.com/cazimayaa/items/5fdfbc060dff7a11ee15)
function myFunction() {
// 今選択中のスプレッドシートのシートを取得
var sheet = SpreadsheetApp.getActiveSheet();
// 取得したシートから、セルの中身を取得
var values = sheet.getDataRange().getValues();
var calendar = CalendarApp.getDefaultCalendar();
// ※var i の0番目はヘッダーになるので、1からスタートします。
for (var i = 1; i < values.length; i++) {
var status = values[i][0];
if (
status != "済" // 連携の欄が済になっていなかったら処理を行う
) {
// 予定日
var date = values[i][2];
// 開始時間
var startTime = values[i][3];
var startDateTime = new Date(date.getFullYear(),
date.getMonth(),
date.getDate(),
startTime.getHours(),
startTime.getMinutes(), 0);
// 終了時間
var endTime = values[i][4];
var endDateTime = new Date(date.getFullYear(),
date.getMonth(),
date.getDate(),
endTime.getHours(),
endTime.getMinutes(), 0);
// タイトル
var title = values[i][1]+values[i][8];
var options = {
description: values[i][7]
}
// var event = calendar.createEvent(title, startDateTime, endDateTime);
// 引数にoptionsを追加するだけ
var event = calendar.createEvent(title, startDateTime, endDateTime, options);
// カレンダーへ登録
sheet.getRange(i + 1, 1).setValue("済"); // 連携の欄を済にする
}
}
}
このままでもいいかもしれないが、スプレッドシートが更新されたらカレンダーも連動させたいので、トリガーを設定する。(参考:https://auto-worker.com/blog/?p=1646)
「現在のプロジェクトのトリガー」から、シート変更時に関数を実行するようにする。
実行後
カレンダー
スプレッドシート
やったぜ。
##4. 追記
まさかのコンテスト終了時間が1時になることでカレンダー追加できない問題発生。無理やり時間の方に24を足すことでどうにかならないかな...