Selenium + Pythonで手軽に完全自動出勤システムを作ってみた

新型ウィルスの騒動でテレワーク・リモートワークで勤務している人も多いと思います。
筆者の勤める会社でも緊急事態宣言に伴ってテレワークが開始されましたが、面倒なルールがあったのでSeleniumとPythonで手軽に自動化してみました。

ちなみに弊社は緊急事態宣言解除してすぐに基本出社、たまに在宅勤務という形になりました。

## あくまでも自己責任で

この記事で紹介することはあくまでも**自己責任**でお願いします。
そういうアプローチもあるよ～ぐらいの温度感で見ていただければと思います。
筆者がこれを使っているかどうかは想像にお任せします。

今回はあくまでも社内サーバーに設置されている社内向け独自システムを対象に話しています。
もちろん自動化が禁止されているサービスも多く存在するので、外部サービスで使う際には利用規約に違反しないことを確認してから自己責任でお願いします。

という前置きが済んだので早速自動化していきましょう。

## テレワークの環境とルール
世の中には様々なテレワークのルールがあると思いますが、今回は以下の環境とルールを前提に話していきます。

環境

- テレワーク時は自宅PCから会社の自席PCへVPN経由でリモートデスクトップを使って接続する
- 自席PCはテレワーク期間中はシャットダウンせず24時間稼働状態
- 各種社内システムには社内ネットワークからのみ接続可能
- 社内システムはブラウザで使え、APIなどは一切使用できない(そもそもAPIなど実装されていない説もある)

テレワーク勤務開始時のルール

- 社内チャットのテレワーク用チャンネルに本日の勤務予定時間を書き込む
- 社内メールで管理職や上司宛にチャットと同じ内容を送る
- 社内の勤務管理システムに始業時間を登録する

つまり、始業時間(AM9:00)ちょっと前にVPNに接続して、毎日同じような内容をチャットとメールで送り、社内の勤務管理システムに登録する必要があります。
これでは始業時間前に起きてPCを起動してVPNを繋いで・・・といろいろやらないといけないので通勤時間がなくなった割には、朝の余裕が増えた感じはしません。

~~なによりそんなことのために始業時間に起きるのが面倒です。~~

## SeleniumとPythonで自動化してみる

幸いにもすべての社内システムはブラウザからアクセスできるので、ブラウザの自動化でおなじみSeleniumをPythonで動かして自動化してみます。

### チャットの自動化
チャットに関しては書き込むチャンネルと文言が決まっているので、人間が操作するのと同じようにすれば簡単に自動化できます。
オンプレ向けチャットツール「Mattermost」を例に作ってみます。

```python
driver = webdriver.Chrome()
# マターモストにログイン
driver.get("http://192.168.0.100/system-dev/channels/remotework") # テレワークチャンネル
time.sleep(5)
driver.find_element_by_name("loginId").send_keys("shachiku-taro@test.com") # ユーザー名(メールアドレス)
driver.find_element_by_name("password").send_keys("Password") # パスワード
time.sleep(1)
driver.find_element_by_id("loginButton").click() # ログインボタン押下
time.sleep(5)
chatbox = driver.find_element_by_id("post_textbox") # チャット入力欄取得
# ここから書き込む内容
chatbox.send_keys("おはようございます。社畜です。")
chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
chatbox.send_keys("【本日の就業時間】")
chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
chatbox.send_keys("勤務予定時間：09:00 - 18:00")
chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
chatbox.send_keys("【昼休憩(1.0h)】")
chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
chatbox.send_keys("休憩予定時間：12:00 - 13:00")
chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
chatbox.send_keys("【その他】")
chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
chatbox.send_keys("なし")
# ここまで
chatbox.submit()
time.sleep(5)
driver.quit()
```

こんな感じで ブラウザ起動 → 指定チャンネルのURLへアクセス → ログイン → チャット書き込み を自動化しました。
`time.sleep`はページを読み込みんだ後になんとなく処理が走ってそうな部分で待つだけの処理です。
なくてもたぶん動きますが、保険ですね。

Seleniumでブラウザを起動すると、基本的にはシークレットウィンドウのようなIDとパスワードなどが記憶されていないまっさらな状態で起動するので、ログイン処理を入れる必要がありますが、それ以外は人の手でする作業と変わりません。

チャットの内容は改行して入力するのですが、私の環境ではShift + Enterで改行できるのでそうしています。
Ctrl + Enterで改行するツールとかもあると思うので、そこは適宜変えてください。

後はテストする時は1人だけのチャンネルを作るとかしてテストしてください。
ぶっつけ本番で失敗したら多分怪しまれます。

### メールの自動化

メールシステムにブラウザからアクセスでき、ブラウザ上からメールの受信・送信などが可能なので、自動化してみました。
ブラウザで利用できるメールシステムは他社が運営してるものですが、利用規約に自動化がダメとは書いていなかったので多分大丈夫なはず・・・。

ブラウザ起動 → メールシステムのURLへアクセス → ログイン → 新規メール作成 → メール内容入力 → 送信という感じの流れで自動化しました。
これも正直チャットとやっていることは変わりません。

コードは先程とさほど変わりませんが、メールには件名に日付を入れるというルールがあったので、そのあたりの処理をサンプルとして書いておきます。

```python
"【社畜プロジェクト】【就業開始】" + datetime.date.today().strftime("%Y%m%d") + " 進捗報告（社畜 太郎）"
```
件名は「【プロジェクト名】【就業開始】2020/10/01 進捗報告（氏名）」という感じにしないといけなかったので、実行時の日付を取得して自動化しました。

あとはメール作成画面は別ウィンドウで表示されるので、フォーカスするウィンドウ（操作対象のウィンドウ）を変える処理を追加しました。

```python
driver.switch_to.window(driver.window_handles[1])
```

ウィンドウはウィンドウハンドルという文字列で管理されているようですが、今回は1度だけポップアップするので`driver.window_handles[1]`と指定して、最初に開いたウィンドウ`driver.window_handles[0]`の次に開いたウィンドウといった感じで指定しています。

一番最後に開いたウィンドウを指定するには`driver.window_handles[-1]`とすれば良いみたいです。

### 勤務管理システムも同じ感じで自動化

もうここまで行くとだいたい一緒です。
URL叩く → ログイン → 操作する みたいなことを書いていくだけです。
操作するときには`id`か`class name`で要素を指定するだけなので、そこは開発者ツールで地味に拾っていくしかないですね。

## スケジュール実行する

Seleniumでの操作自動化が終わったら次はスケジュール実行を実装します。

具体的にはこれらの自動化操作を「あたかも出勤しているような時間」に実行する必要があります。

今回の例だとAM9:00ということになりますが、皆さんはAM9:00始業だったらAM9:00ピッタリに席についていますか？

AM9:00ちょっと前には席についていると思います。
逆に電車のちょっとした遅延とか、電車を1本乗り過ごしたりとかで数分遅れてしまうときもありますよね？
毎日同じ電車に乗っていても天気や気温によっては出勤時間に数分はばらつきは出るはず・・・。

つまり、AM9:00ちょっと前である程度ランダムな時間にチャットとメールが送られないと不自然なわけです。

ということで今回は8:50～9:00までのランダムな時間にスケジュールを実行する処理を実装します。

```python
chattime = ""

def timeset():
    chattime = random.randint(50, 60)
    if chattime < 10:
        # もし10分以下の場合はゼロ埋めする
        chattime = "0" + str(chattime)
    if chattime == 60:
        # もし60分の場合は9:00と解釈する
        schedule.every().day.at("09:00").do(job)
        print("09:00")
    else:
        schedule.every().day.at("08:" + str(chattime)).do(job)
        print("08:" + str(chattime))


schedule.every().day.at("08:00").do(timeset)

while True:
    schedule.run_pending()
    time.sleep(60)
```
こんな感じで出勤連絡する時間を毎日AM8:00にランダム関数で決めて、その時間になったら出勤連絡をするようにしました。

処理的にはかなり雑ですが、すべての関数を定義後に最後の`while True`で無限ループし、スケジュールをすべて実行 → 1分スリープを繰り返しています。

実行時は`schedule.every().day.at("08:00").do(timeset)`でAM8:00に`timeset`を実行するというスケジュールを登録しておき、`timeset`の中で50～60のランダムな数字を発生させてその数字に応じたスケジュールを追加で登録しています。

一応50～60以外にも対応するために、10分以下の場合は先頭を0埋めする処理を入れてあったり、60が出た時は9:00と解釈するようにしたりと適当な作りですが、要件は満たせたのでOKとします。

## コード全体
```python
#!python3.8
import time
import datetime
import schedule
import random
import sys
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait


def job():
driver = webdriver.Chrome()
    # マターモストにログイン
    driver.get("http://192.168.0.100/system-dev/channels/remotework") # テレワークチャンネル
    time.sleep(5)
    driver.find_element_by_name("loginId").send_keys("shachiku-taro@test.com") # ユーザー名(メールアドレス)
    driver.find_element_by_name("password").send_keys("Password") # パスワード
    time.sleep(1)
    driver.find_element_by_id("loginButton").click() # ログインボタン押下
    time.sleep(5)
    chatbox = driver.find_element_by_id("post_textbox") # チャット入力欄取得
    # ここから書き込む内容
    chatbox.send_keys("おはようございます。社畜です。")
    chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
    chatbox.send_keys("【本日の就業時間】")
    chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
    chatbox.send_keys("勤務予定時間：09:00 - 18:00")
    chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
    chatbox.send_keys("【昼休憩(1.0h)】")
    chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
    chatbox.send_keys("休憩予定時間：12:00 - 13:00")
    chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
    chatbox.send_keys("【その他】")
    chatbox.send_keys(Keys.SHIFT, Keys.ENTER)
    chatbox.send_keys("なし")
    # ここまで
    chatbox.submit()
    time.sleep(5)
    driver.quit()

    # メール送信
    driver = webdriver.Chrome()
    driver.get("https://webmail.example.com/login")
    time.sleep(5)
    # ログインID
    driver.find_element_by_name("login_id").send_keys("shachiku-taro@test.com")
    # パスワード
    driver.find_element_by_name("pass").send_keys("password")
    time.sleep(1)
    driver.find_element_by_xpath("//input[@value='ログイン']").click()
    time.sleep(5)
    # メール作成画面
    driver.find_element_by_id("smail").click()
    time.sleep(5)
    driver.switch_to.window(driver.window_handles[1])
    # メール作成
    driver.find_element_by_id("to").send_keys("shachiku-member@test.com")
    driver.find_element_by_id("subject").send_keys(
        "【社畜プロジェクト】【就業開始】" + datetime.date.today().strftime("%Y%m%d") + " 進捗報告（社畜 太郎）"
    )
    driver.find_element_by_id("content").send_keys(
        "各位\nお疲れ様です。社畜です。\n本日の勤務予定についてご報告致します。\n\n【本日の就業時間】\n勤務予定時間：09:00 - 18:00\n【昼休憩(1.0h)】\n休憩予定時間：12:00 - 13:00\n【その他】\nなし\n------------------------------------------------------------\n  株式会社 社畜 開発部\n  社畜 太郎 / Taro Shachiku \n  mail: shachiku-taro@test.com\n------------------------------------------------------------"
    )
    # メール送信
    driver.find_element_by_id("send_mail").click()
    time.sleep(10)
    driver.quit()

    # 勤怠システム入力
    driver = webdriver.Chrome()
    driver.get("http://192.168.0.200/kinmu/login")
    time.sleep(5)
    driver.find_element_by_id("userId").send_keys("114514")
    driver.find_element_by_id("password").send_keys("password")
    driver.find_element_by_class_name("login_button").click()
    time.sleep(10)
    yesterday = datetime.date.today() - datetime.timedelta(days=1)
    driver.find_element_by_id(
        "dailyList["
        + datetime.date.strftime(yesterday, "%d")
        + "].orderList[0].resultEnterTime"
    ).send_keys("0900")
    driver.find_element_by_name("update").click()

    time.sleep(10)
    alert = driver.switch_to.alert
    alert.accept()

    time.sleep(10)
    driver.quit()

    sys.exit()


chattime = ""


def timeset():
    chattime = random.randint(50, 60)
    if chattime < 10:
        chattime = "0" + str(chattime)
    if chattime == 60:
        schedule.every().day.at("09:00").do(job)
        print("09:00")
    else:
        schedule.every().day.at("08:" + str(chattime)).do(job)
        print("08:" + str(chattime))


schedule.every().day.at("08:00").do(timeset)

while True:
    schedule.run_pending()
    time.sleep(60)

```

## まとめ
ブラウザからできるルーチンワークはこんな感じで全部自動化できてしまいそうですね。

これで9時過ぎに起きて風呂に入ってからVPNに繋いだとしても、周りからは9時前からしっかりと仕事しているように見えます。

前日の退勤前に実行しておけば、翌日ちょっと起きるのが遅れてもぱっと見た感じは出勤しているように見えるので安心ですね！

とはいえ、ちゃんと仕事を開始する前にチャットとかが飛んできている可能性もあるので、用法用量を守って、程々にしておくのが一番大事だったりします。

これで「特に深い意味はないけどルールだから」という理由でやらされるクソ業務が少しでも減れば幸いです。