学務情報システムを全自動で叩いてシラバスの更新を検知する

この記事は whywaita Advent Calendar 202020日目。遅刻なんだ。本当に申し訳ない。同じ大学に属しているので大学に関する話題でもしよう、ということで登録。

電気通信大学の学務情報システムにあるシラバス関連ページをスクレイピングして Slack へ更新を通知するスクリプトを書いたので、この記事ではその紹介と動機および学務情報システムをスクレイピングするときのコツについて記す。

紹介

このような通知を送ることができ、人力での更新チェックを減らすことができる。

ソースコードGitHub に MIT LICENSE で公開していて、https://github.com/nakanokurenai/dump-uec-campussquare-syllabus にある。JavaScript の処理をどうしても呼ばなければならず、TypeScript で書いた。

yarn と Node.js が入っていれば動かせる。ローカルの Node.js は v15.3.0 だったため、それなりに新しい Node.js である必要はあるかもしれない。 Slack の Incoming Webhook を受けとれる Slack アプリを用意したら、次のように実行するとまず動作させることができる。

git clone https://github.com/nakanokurenai/dump-uec-campussquare-syllabus ducs
cd ducs

# ログイン情報などの環境変数への設定
export DUS_USERNAME="ex2010001"
export DUS_PASSWORD="p@ssw0rd"
export SLACK_WEBHOOK_URI="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"

# 1. 履修しているシラバスの取得
# このスクリプトは ducs-bin/risyuu-syllabuses.json にデータを保存する
# ログインが必要。2要素認証のコード入力が促されるので入力する
yarn --cwd ./ducs-bin ts-node ./src/dump-registered-syllabus.ts

# 2. Slack へ通知
# 第2,3引数に与えられた日時 (JST) の範囲内に更新されたシラバスがあった場合に通知してくれる
# 第3引数は省略可能で、省略したときは syllabuses.json (dump-all-syllabuses.ts によって取得したときのファイル名) になる
yarn --cwd ./ducs-bin ts-node ./src/notify-updates-to-slack.ts '2020/09/01 00:00:00' '2021/01/01 00:00:00' risyuu-syllabuses.json

「おや、手動で2要素認証のコードを入力していたら全自動にできないのでは」と思うかもしれない。実は入力は回避可能で、後述する。

取得タイミングを実行ユーザーがどこかにメモして notify-updates-to-slack.ts に渡す日時の範囲を変更することで更新通知 bot として使えるデザインになっている。

取得中は次のようにせわしなくページを取りにいっていく。頑張っていてかわいい。
このように大量のページを取得するので負荷がそれなりに予想されるため、夜中やピークタイムを避けて実行すると怒られないで済むのではないかと思っている。

動機

まとめると、リモート授業の環境ではシラバスの更新をより敏感に追う必要が出てきてしまい、手動でやるのが苦痛になったから。もっと長く書くと次の通り。

あらゆる大学はシラバスと呼ばれるものを持っていて、UEC では CampusSquare というシステム上にシラバスのデータベースを持っている。 誰でも閲覧できる シラバスWeb公開システム およびログインしなければ閲覧できない 学務情報システム 内の二つの動線からアクセスできるようになっていて、学外からも授業の概要を掴むことができる。

ところで、古今の情勢でリモート講義のためシラバスに Zoom や Google Classroom の接続情報が記載されることがある。 このような接続情報を部外者に公開することは問題を引き起こすことがある。特に Zoom のものは「Zoom爆撃」と呼ばれ、記憶に新しいかもしれない。
そのため、こういった情報も含めた完全版は学務情報システム側からしか見れないように管理されることになる。

学務情報システムって奴は非常に面倒で、すぐにセッション期限が切れる。その上統合認証システムでのログインには去年度から学外からのログインに常に2要素認証が有効になっていることがより面倒さに拍車をかけてくる。困ったなあ。
といっても従来シラバスは講義を受ける前に一度確認すればおおかた問題なかったからそこまで問題ではなかった。講義に出ることができればその場で課題の通知もされるし、方針の変更があれば伝えられていたためだ。

しかし、パンデミックによってリモートが強制されてしまった状況ではこうもいかない。

突然やってきた事態への対応であることもあり講義ごとにやり方が異なり、またその方法を伝える方法も先生による。 全員が Google Classroom などの LMS を使ってくれるといいのだが、シラバスを直接更新して情報を伝えると宣言する先生もいる。 そうでなくとも授業そのもののゴールや評価方式が変化することが有り得るようになったのでちゃんと更新をチェックしたい。

ここで問題となるのが、シラバスの更新を通知する手段を学務情報システムが持っていないこと。 すなわち、カレンダーへの同期手段や履修生への更新を知らせるメールであるとか、そういった機能を一切持っていない。

そうなると学生側にその皺寄せが来る。人力ポーリングという形で。 ここで響いてくるのが履修一覧画面から直接シラバスを参照することができないこと。人力で時間割コードをコピーして貼りつける必要がある。 さらに人間が更新しているためにいつ更新されるかはわからず、同じ講義に対して毎週複数回チェックしなければならないことも手間だった。

もっとシステムがまともに作られていればこんな無駄な作業はなかったのにという思いに加え、定期的に同じことを繰り返すことが苦手な私にとってかなり苦痛な作業だった。

こういう辛さから一刻も早く逃れるためにスクレイピングすることで自動化できないか模索することにした。

学務情報システムをスクレイピングするときのコツ

遷移情報を持ったステートフルアプリケーションであること

普通の Web アプリケーションは認証を除けばステートレスで同じリクエストをしたら同じレスポンスが帰ってくる、すなわち URL などリクエスペイロード上にレスポンスを一意に特定するための情報が詰まっていることが多いと思う。 例えば検索結果をスクレイピングしたけば検索URLに必要なクエリをつけてリクエストするだけで取ることができる、という世界観である。

しかし学務情報システムは特定の操作を手順通り行なわなければ求めるページに辿りつけないようにできている。 ブラウザバックすると壊れる銀行のウェブサイトと同じだ。

こうなると頑張ってブラウザ上の動きを再現する必要が出てくる。

具体例をいくつか上げる。

メニューから「シラバス参照」を選択すると、毎回ランダムな _flowExecutionKey というクエリのついた URL に飛ばされる。正しくメニューから辿らないと求めているページに辿りつけない。

ランダムなクエリつきで遷移するのはどういうことなのかというと、クリックされたタイミングで moveFunc という関数を呼び出して毎回 POST リクエストを行なってページを遷移させているのである。パラメータは onclick の引数および linkForm という <form> 要素からどうにかして取り出さないといけない。

また、シラバス検索ページから詳細に飛ぶ際も、その検索ページからの遷移であることを示すフォームに POST する必要がある。
このために検索ページの期限切れで詳細ページの取得が弾かれることがあり、その際はメニューら検索ページに戻ってを再度繰り返す必要がある。

こういった場合、実際にブラウザを操作してページを表示できるライブラリを使い、HTMLElement.click でクリックして実際に遷移させるのが良いと思う。そういったライブラリは puppeteerplaywright がよく使われている。

ただ、学務情報システムは昔の 2ch のように <frame> タグを使い、画面を複数の HTML で構成する方法を取っているので扱いが難しいかもしれない。また、ブラウザを動作させるということは潤沢なリソースが必要かつブラウザとの通信コストもあり動作が遅くなることが想定できる。

そんなこともあって dump-uec-campussquare-syllabus ではブラウザを使わずに頑張った。頑張るにあたりさすがに JavaScript をパースしたくなかったので、eval できる JavaScript 上で書くという決定をした。

ただこういう汚ないコードをたくさん書くことになるので、ブラウザに任せられるならそのほうがいいと思う。

ログインする度に二要素認証しなければならない問題

必須であると入力が面倒である以上に、自動化するのに支障が出る。 なんとかして回避したい。

そのためには2つ手段が思いつく。

  1. なんらかの方法で二要素認証そのものを回避する
  2. 二要素認証のコードを生成するための鍵を取り出し、スクリプトから参照して認証コードを生成する

前者は最も簡単だが、できるかわからない。 後者は統合認証システムの二要素認証は RFC 6238 であったり Google Authenticator の Wiki に仕様が公開されている TOTP であることから、その生成手順を自前で踏むというもの。ただし面倒だし、事前に揃えておく情報が増えて面倒だ。

ここで二要素認証を必須とする告知を見直してみる。

変更内容 全ユーザーに対して統合認証システムの2要素認証を有効とします。
     これにより、学務情報システム等のウェブアプリケーション
     の学外からのログインには2要素認証が必須となります。
     学内からのログインには影響ありません。
https://www.cc.uec.ac.jp/blogs/news/2019/11/post-57.html

実は学内からなら回避できることが明記されている。 さらに驚くべきことに sol からのアクセスは学内として認識される。

つまり ssh の SOCKS5 プロキシを経由してアクセスすることで、回避できる。

dump-uec-campussquare-syllabus では ALL_PROXY という環境変数をセットするとプロキシ経由の通信になるようになっている。下準備を次のようにやればプロキシ経由の通信に切り変わる。

ssh -fND 1080 sol # .ssh/config に "Host sol" がある想定
export ALL_PROXY="socks5://localhost:1080"

おわりに

AdC に登録したときに「わいわいたさんの出身大学に関するサービスを作るかもしれない」と書いた通り、本当はサービスを開発して公開するつもりだった。具体的には時間割コードを登録すればシラバスの本文を含めたカレンダーデータ (iCalendar) が配布されるサービスを考えていた。しかし書いてる最中に著作者に許可を取らない再配布であることに気付きお蔵入りに。

それならこれまで書いてきたシラバススクレイピングに関する記事を残しておこうということでこのテーマになった。

もう少し気付くのが早ければ Twitter に更新通知を流すサービスにしてもよかったのだが、あまり気力が残っていなかったのでやれなかった。いつかやるかもしれない。もちろん OSS なので拡張してそういった bot をやってもらっても嬉しい。

dump-uec-campussquare-syllabus にはほかにも Google Calendar に取得したシラバスを登録するスクリプトがあったりする。現時点では実行のたびにカレンダーが作り直されるので面倒くさいということと、冬休みのような期中にある休みを全く考慮していない問題があるので、出来がよくなったら紹介するかもしれない。

おわり。 突っ込み・感想などあればどこでも受けつけているので、よろしくお願いします。