大学の講義資料のダウンロードをPythonで自動化してしまった話

<a href="https://twitter.com/riita10069"><img src="https://img.shields.io/badge/Author-riita10069-4FC08D.svg?logo=&style=plastic"></a>


## 動機
僕の通う東京工業大学っていう~~Fラン~~大学では、
講義資料や課題がOCWiっていう大学の管理してるウェブサイトに上がるんですけど
ログインするのに、毎回パスワードとマトリクスコードが必要です。
それだけでなく、~~クソみたいに~~古臭いWebサイトだし
めちゃめちゃ重いし、講義資料をダウンロードするだけでも一苦労なんですよ(汗...
ということで自動化してしまおうという発想に至りました。

まぁ、僕は設計しただけで、実装は友達がしたんですけど、同じような境遇にある大学生の役に立てればいいかなぁという事で。
ぜひ皆さんの大学でも実装してみてください。

# Iris(Interval request information service)
## 解決すべき課題（Issue to be Solved）

### 現状
- 更新された授業資料をダウンロードするのは手間
- 講義資料をダウンロードした人がSlackの各授業チャンネルにアップロードしている

### 課題
- めんどくせぇ

## UserStory

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309507/16aba7f7-3417-11c9-b7f7-e0b23099b0ef.png)

- IrisがOCWiから講義資料をとってくる
- GoogleDriveAPIを使ってポストする
- 講義資料がアップデートされていたらSlackに通知をとばす
- SlackとGoogleDriveは連動しているので、Slackから資料が見れる
- GoogleDriveから必要な資料を見れる

## 設計
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/309507/294152fc-9689-73dc-9a3b-e677d7c70f57.png)

### 使用するライブラリ
- selenium（認証突破）
- urllib（ファイルダウンロード）
- beautifulsoup（スクレイピング）
- google-api-python client
- PyDrive(google-api-python clientのラッパー)



## 実行の自動化
一般的にはcrontabがLinuxだと使われるみたいですが、
自動化するときは、自分でデーモンを作ってあげた方が使いやすいんですよ。

通知とか分散実行とかしたいときはdigdagとかAirflowとか使いますけどね。
そういった難しい事する記事はまた書くとして、
今回はシンプルにLinuxのデーモンを新しく作ってしまいます。

### 重要になるファイル
- `/etc/systemd/system/[デーモン名].service`
これがユニットファイルと呼ばれているもので、ここのディレクトリ下にファイルを置くだけで、systemctlにサービスだと認識してもらえます。

以下のような感じで作りました。

```:/etc/systemd/system/iris.service
[Unit]
Description = This is OCWi login service daemon
After=local-fs.target
ConditionPathExists=自動起動したいプログラムへのパス(/opt/[パッケージ名]/bin)

[Service]
ExecStart=定期実行するコマンド(これがそのまま実行されます)
Type=oneshot

[Install]
WantedBy=multi-user.target
```

`After=local-fs.target`はストレージのマウントが終わってから実行してくださいって意味です。
この辺はちょっとLinuxの(大学生にとっては)難しい話になるのですが、

` /usr, /opt, /var`の三つのディレクトリってLinuxにはあるじゃないですか。
あの3つは、ローカルファイルシステムのパーティションが別に区切られていることがあるんです。というか区切っていいとされているんです。

パーティションが分かれているということはすなわち、使用する際にマウントして使用するということである。
メリットは単純で、更新作業などがシステムへの影響を及ぼしにくかったり、
swap領域のパーティションを新たに設けたりできるということ。
/optはパッケージがもっさり入っていて、容量もでかいし、更新も頻繁だからパーティション分けてもいいよってされてるんですよ。

だから/opt以下を使用するときは、使っているときは気がつかないけれど、実はメモリの論理領域にアクセスしてマウントポイントを得てからマウントして使用しているってことです。
だから、今回はマウント前に実行してしまうとそんなディレクトリないよ〜ってエラーで返ってきちゃうので、念の為、マウントしてから実行してねって言ってます。


- `/opt/[パッケージ名]/bin/[デーモン名].sh`


systemdのユニットファイル中のExecStartのところに`Pyhton3 [ファイル名]`としてもいいのですが、
拡張性が担保されないことと, systemdとプログラムの切り分けができないことから
shell script側で実行してあげようと思います。

```shell:/opt/iris/bin/iris.sh
exec python3 /opt/iris/bin/iris.py
```
binと.shと.pyに`chmod755`と`chown root:root`しておくのは忘れないでください。
permission deniedで落ちるので。

```
$ sudo systemctl daemon-reload 
$ sudo systemctl enable iris.service
$ sudo systemctl start iris.service
```


```:/etc/systemd/system/iris.timer
[Service]
ExecStart=/opt/iris/bin/iris.sh
Type=oneshot

[Install]
WantedBy=multi-user.target
jrny@iris:/etc/systemd/system$ cat iris.timer
[Unit]
Description=timer of iris

[Timer]
OnCalendar=*-*-* 5:00
Persistent=true

[Install]
WantedBy=timers.target
```

タイマーは拡張子が .timer の systemd のユニットファイルです。

[Timer]以下のOnCalendarの部分で頻度を決めています。
`OnBootSec=15min`なら１５分おきに実行されます。
`OnCalendar=Mon,Tue *-*-01..04 12:00:00`毎月の1日から4日まで、月曜日か火曜日の場合にのみ、午後0時
`Sat *-*-1..7 18:00:00`毎月第1土曜日
`OnCalendar=*-*-* 4:00:00`毎朝四時
という感じで指定できます。
基本はcrontabと同じですね。

`sudo systemctl daemon-reload` で反映
`sudo systemctl enable iris.timer`で有効化
``

### よくある失敗
#### permisson error
`ls -l　filename`の一番左にシステムコールに関する権限が出るので確認してください。
知ってると思いますが、所有者、グループ、その他の順で出ます。
実行権限がなかったら、755とかにしておくと大丈夫です。

#### systemctl側の問題
`systemctl status iris.service`で確認できるので、確認してください。

#### プログラム側の問題
`journalctl -u ユニットファイル名`でログが見れるので確認してください。



## Seleniumを使ったログイン

```python
driver.find_element_by_name('usr_name').send_keys(secret.studentu_num)
driver.find_element_by_name('usr_password').send_keys(secret.password)
driver.find_element_by_name('OK').click()
```
Seleniumでは`element_by_name`で要素を取得して
値を代入することができるので、上記のような処理でSessionIDを取得できます。


あとは講義資料のあるページをスクレイピングして、資料をダウンロードするだけです。
https://qiita.com/memakura/items/20a02161fa7e18d8a693
この記事が非常にわかりやすくまとまっているのでご参考ください。

## GoogleDriveとIrisの接続

GCSって選択肢もあったんですけど、極力GCPに課金したくなかったので、Driveにしました。

認証はGoogleアカウントの鍵で繋げます。
ファイアーウォールとかポート開けたりしなくていいので簡単です。

```python
    gauth = GoogleAuth()
    gauth.LocalWebserverAuth()
    drive = GoogleDrive(gauth)
```
ってやるときにログインしろって言われるので言われるがままにしておけば、直下にcredential.jsonできてます。


ファイルのアップロード自体はupfilesという関数があるので、それ使うとファイルがアップされます。