決まったら考えるよ。

思いついたことそのまま書く。髭剃り、読書、仕事、考えたこと、調べたことを備忘録代わりに。慶應通信で大学生もやっています。

kcc-channelのレポート提出状況確認をPython使って自動化して、なんか楽になった気がするけど毎日結果が通知されて自分を追い込むドMプログラムを作った件

f:id:siuso:20200322171747p:plain

慶應通信の一番楽しい時間

 慶應通信では、適宜学習レポートを提出することで学習を進めていくことになる。(厳密に言えばレポートを出して、試験を受けて単位が貰えるので試験までがワンセットだが)そして、そのレポート提出は郵送やらWEB提出やら色々と手法はあるが、結果確認はkcc-channelというWEBサイトで確認することも出来る。郵送でレポートを提出すると、「まだ提出状況には乗らないかー」「お!載った!添削中になった!」「あー、まだ添削中かー。早く結果出ないかしらー。」なんて形でちょっとドキドキしながら毎日覗くことになったりする。最近気づいたが、実はこの期間が一番楽しい。何故かって?そりゃアナタ。添削結果が「合格」なら喜びは最高潮だがその後の科目試験どうしよう…とか新しい不安が発生することになるし、もしそのレポートが「不合格」ならもうコメントもなかなか辛辣なものがついてたりする訳ですよ。そりゃあ自分の勉強不足が原因な訳で、先生は何一つ悪く無いけど、辛辣すぎるコメントが付いた日にはもう仕事のテンションも上がらいし、レポートの再提出とか二度としてやらねぇ!ってくらいに腹が立ったりもする。だから、レポートを提出して、kcc-channel上のレポート提出状況が「添削中」ってなってる期間が一番楽しい。

 でもなんか、毎日思い出してブラウザ起動して、kcc-channelにアクセスして、レポート提出状況をクリックしなきゃならんのはなんか面倒でもある。という訳で、流行りのPythonさんを利用して自動化してみた。

で、結果として毎日通知が来ることで「ああああレポート提出しなきゃ」って自分にストレスを与え続けるドM行為。



スポンサーリンク


状況確認

使用武器(エモノ)

 にわかPython学習者なのでAnacondaを突っ込んだ結果僕のPCに入っているものと、pipコマンドを使って導入したselenium、あとは適当にググったら出てきたseleniumChromeを動かす為のChromeDriver程度。


標的

https://kcc-channel.keio.ac.jp:10443/up/faces/login/Com00501A.jsp


jspページなのが曲者。最初はページ末尾を見ただけでもう辞めようかと思った。


対応方針

 通常であれば…

  1. ログイン
  2. 「テキスト科目」にマウスを乗せる
  3. 「レポート」にマウスを乗せる
  4. 「レポート提出状況照会」をクリック
  5. 対象発見!

という5stepで完遂する。そもそもChromeにIDとパスワードを覚えさせておけばChrome起動(1clikc)、ログインボタン押下(1click)、マウスオーバーマウスオーバーからのボタン押下(1click)の合計3clickで閲覧可能であり、そもそも自動化する意味無いんじゃないのってくらい楽だけどやりたいことをやる訳で、目をつぶる。そ、それに自動化が目的だからむなしくないもん!


ゴールイメージ

 ただ定期的に確認するだけ、というと本当にGoogle Chromeのスタートページをkcc-channelにしてアクセスするのが最高効率になってしまうので、せめてその毎日の2~3clickだけでも減らす。そんでもって、何か目に付く場所に通知が来ればベスト。という訳で最終出力先はslackに決定。つまり・・

  1. 定期的に結果確認(1日1回)
  2. 確認結果はslackで自分宛のDMとして飛ばす

といった形か。ではプログラムへ。


だらだらとコードを描いてみる

必要ライブラリのインポート

 seleniumを使ってChromeを動かす。そんでもって、json使ってslackにDMで飛ばす。あ、slackのWebhookは別途取得しておく必要があります。


import time
import datetime
import numpy as np
import requests
import json
import locale
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from bs4 import BeautifulSoup


なんか無駄な物がたくさんありそうだな。こうやって見るとrequestsは使ってないような…まぁいいか。


seleniumを使ってページアクセス~ログイン突破まで

# headlessモードの追加
options = Options()
options.add_argument('--headless')

# page表示、0.5秒停止
driver = webdriver.Chrome("c:/driver/chromedriver.exe", options=options)
driver.get('https://kcc-channel.keio.ac.jp:10443/up/faces/login/Com00501A.jsp')
time.sleep(0.5)

# userID,Password入力
id_box = driver.find_element_by_name("form1:htmlUserId")
id_box.send_keys('xxxxxxxx') # ここにIDを入力
ps_box = driver.find_element_by_name("form1:htmlPassword")
ps_box.send_keys('yyyyyyyy') # ここにパスワードを入力
time.sleep(0.5)

# log-in click
login_send = driver.find_element_by_name("form1:login")
login_send.click()


 自分用でググりながら適当に作ったプログラムなのでセキュリティとか一切の配慮が無い。kcc-channelにログインする為に必要な情報は平文でそのままぶち込むノーガード全力テレフォンパンチ。

 この部分で行っていることは、

  1. seleniumを使ってChromeを起動
  2. headlessモードを指定することによってChrome操作画面をオフ
  3. kcc-channelのid入力スペースとパスワード入力スペースをfind_element_by_name を使って検索
  4. そこにsend_keysメソッドを使ってそれぞれのぶち込んで、なんとなく0.5秒待ってみる。
  5. find_element_by_nameを使ってボタンを探して、ログインボタンをクリック

といったこと。一生懸命ソースコードを覗いて探した結果をまとめておくと、


場所 name
ID入力部分 form1:htmlUserId
PW入力部分 form1:htmlPassword
ログインボタン form1:login


こんな感じ。idが設定されていれば楽だったんだけど、idは存在せず。残念。

ここまでのコードを実行することで、kcc-channelにログイン出来たことになる。さて次、レポート提出状況のページ表示~情報取得へ。


レポート提出状況表示~ページ内容情報取得まで

 前段でkcc-channelにログイン出来て、内部的にはトップページが表示されている状況。ここからレポート提出状況のページにアクセスし、表示されたページをスクレイピングする。が、ちょっと問題が。jspページだからhtml取得してスクレイピングできない。なので、execute_scriptメソッドを使ってアクセスすることにする。


# execute_scriptを使って直接script実行
time.sleep(0.5)
driver.execute_script("clickMenuItem(30201,0);")


これでレポート提出状況のページにアクセスすることが出来た。で、ここからログイン時と同じようにfind_element_by_nameを使って抽出箇所を特定しようかな…という訳でまたhtmlソースとにらめっこしてみると面倒なことが発覚。


nameにめっちゃたくさん”:”が使用してあってfind_element_by_nameメソッドでうまく指定できない


なんだよもう…かっこいいname属性付けるのは良いけどさ、わかりにくいよ…もう。という訳で路線変更してxpathを使って探すことにする。


# xpathで要素を指定
xpath_Nm = "//*[contains(@id,'htmlListKamokSikenNm')]" # 科目名
xpath_Uk = "//*[contains(@id,':0:htmlListUketukeDate')]" # 受付日、:0:を加えて最新だけ抽出
xpath_Bk = "//*[contains(@id,':0:htmlListHensouDate')]" # 返却日、:0:を加えて最新だけ抽出
xpath_Hk = "//*[contains(@id,':0:htmlListHyoka')]" # 評価、:0:を加えて最新だけ抽出

# xpathを利用して対象要素を抽出
Nm_elems = driver.find_elements_by_xpath(xpath_Nm) #科目名
Uk_elems = driver.find_elements_by_xpath(xpath_Uk) #受付日
Bk_elems = driver.find_elements_by_xpath(xpath_Bk) #返却日
Hk_elems = driver.find_elements_by_xpath(xpath_Hk) #評価


 これで科目名、受付日、返却日、評価の最新情報を特定して抽出することが出来たので、整形してslackにぶん投げる。(とはいえ、これはあくまで簡易的なものなので、各科目の一番上の情報しか取得できていない。)


取得情報まとめ~平文化・整形まで

 前工程まででxpathで検索・抽出した結果が出に入ったので、slackにぶん投げられるように平文化、整形を行う。


# 使用listの定義
kcc_columns = ['科目名','受付日','返却日','評価']
kcc_Nm = []
kcc_Uk = []
kcc_Bk = []
kcc_Hk = []
kcc = [kcc_Nm,kcc_Uk,kcc_Bk,kcc_Hk]

# 抽出結果をそれぞれリストに格納
for k in Nm_elems:
    kcc_Nm.append(k.text)

for i in Uk_elems:
    kcc_Uk.append(i.text)

for j in Bk_elems:
    kcc_Bk.append(j.text)

for l in Hk_elems:
    kcc_Hk.append(l.text)

# seleniumで起動したブラウザを閉じる
driver.close()

# Numpy使って行列入替、listへ変換
kcc_list = np.array(kcc).T.tolist()


 ここまででそれぞれの結果を定義したリストにぶち込んで、さらにリストの入れ子にすることで全部の情報をひとつのリストにまとめることが出来た。でもこのままだと行列が逆なので、ちょっと小細工して行列変換しておく。あとは整形してslackにぶん投げるだけ。


# 現在日時を取得(いつ取得したデータか表示する為)
locale.setlocale(locale.LC_CTYPE, "Japanese_Japan.932")
dt_now = datetime.datetime.now()
dt_ymd = dt_now.strftime('%Y年%m月%d日 %H:%M:%S 時点の情報です\n')

# slackへの表示イメージ
# -------------------------------------
# ◆ 経済学(科目名)
# 受付日:2020/01/19 返却日:2020/02/07
# 評価:不合格
# -------------------------------------

# 取得したリストを平文化
result_kcc = dt_ymd + "--------------------------------------\n"
for i in range(0,len(kcc_list)):
    result_kcc = result_kcc + '◆ ' + kcc_list[i][0] + '\n'
    result_kcc = result_kcc + '受付日:' + kcc_list[i][1] + '  返却日:' + kcc_list[i][2] + '\n'
    result_kcc = result_kcc + '評価:' + kcc_list[i][3] + '\n'
    result_kcc = result_kcc + '--------------------------------------\n'

result_kcc = result_kcc + 'kcc-channel \n'
result_kcc = result_kcc + 'https://kcc-channel.keio.ac.jp:10443/up/faces/login/Com00501A.jsp \n'


 ここまでやると、何月何日に取得したデータです!ってのを先頭に表示しつつ、末尾まで確認した後にリンクをクリックすることで詳細情報へ…という流れにすることが出来る。何度かslackにリストのままぶん投げてみたり、pandas使ってDataFrameで投げたりしてみたけど表示出来なかったので(当然)、新しい表示用の変数を定義して、リストからひとつずつ整形してテキストにしてぶち込んでいった。なので、このあたりを整形するとお好きな形で出力できます。


slackぶん投げ~フィナーレ

 あとはもう単純で、slackの設定から手に入れたwebhookURLを使って、slackへ。


# slackに結果を飛ばす
# webhook URL
SLACK_URL = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' #ここにwebhookURLをぶち込む

# slackに飛ばす関数の作成
def send_slack():
    content = result_kcc
    payload = {
        "text": content,
        "icon_emoji":':snake:',
    }
    data = json.dumps(payload)
    requests.post(SLACK_URL,data)

send_slack()


 これでコードは完成。あとはタスクスケジューラに設定すれば起動する度でも、1日の決まった時間にでもslackに通知飛ばしてくれますヒャッホー


あとがき

 結局、誰しも毎日普通はブラウザを立ち上げる訳で、そんなに苦ではないのですよ。でもなんかPython使って自分の身近なことを一つでも自動化してみたくてやった。すげぇ時間は失われたけど結果楽しかったからおkってやつっすよ。あとはxpathとかよくわからんかったけどなんとかなったし、selenium使うとweb関係は結構幅広くいろんなことが出来るんだなぁ…って勉強になった。

 え?慶應通信の勉強しろ?いや、はい、おっしゃる通りで…明日から真面目にやります。明日から。