0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PythonとGASでAtCoderのContest予定をGoogleCalendar上に作成する

Last updated at Posted at 2020-11-13

#初めに
AtCoderのコンテストが不定期に開催されているが、時々予定に入れ忘れたりして、家族に迷惑をかけそうだったので自動化することにした(できるかは分からない)。
実行にはGoogleColaboratoryを使用した。
プログラミング初心者なのでコードが煩雑だったり間違ってたりするかもしれません。ごめんなさい。
#AtCoderからGoogleCalendarまで
###手順

  1. AtCoderからPythonのrequestsやBeautifulSoupなどを使ってスクレイピング
  2. スクレイピングしたデータをPythonのgspreadなどを使いGoogleSpreadSheetsにまとめる
  3. 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),"")

結果が以下。
image.png
割とうまくいっている。
##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
「現在のプロジェクトのトリガー」から、シート変更時に関数を実行するようにする。

実行後
カレンダー
image.png
スプレッドシート
image.png
やったぜ。
##4. 追記
まさかのコンテスト終了時間が1時になることでカレンダー追加できない問題発生。無理やり時間の方に24を足すことでどうにかならないかな...

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?