selenium webdriverを使ってWebページを自動操作する


# 概要
勤怠入力にAttendance Proというソフトを使っているのですが、日々の入力が面倒ということでseleniumのweb driverを使って自動化してみました。
非常に稚拙な方法ですが、あまりこういうことに慣れていないので、初めの一歩として。
また自分以外の人、Python環境のない人も使えるようにexe化します。

(8/21追記)
最初はChromeのwebdriverを使用していましたが、それだとブラウザが毎回立ち上がるため、ヘッドレスブラウザとしてPhantomJSを使用する形に変えています。
PhantomJSは[こちら](http://phantomjs.org/download.html)からダウンロードして、環境変数Pathにphantomjs.exeのあるフォルダのパスを指定しておく。

# 環境と準備
主なソフトウェア環境は以下。
OS: Windows 10
Python: 2.7.12
Selenium: 3.5.0
PyInstaller: 3.2.1
PhantomJS: 2.1.1

自分はselenium, pyinstallerなどがなかったので、pipでインストール。

```shell-session
pip install selenium
pip install pyinstaller
pip install ctype
```

# コード
出勤と退勤を別のファイル(exe)にしています。ここでは出勤だけですが、ほぼ一緒です。

```py:attendancepro_start.py
import sys
import os
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.support import ui
from selenium.webdriver.common.keys import Keys
import time
from ctypes import *

# exeで配布するために、ユーザ名とパスワードは都度指定する形に
username = sys.argv[1]
password = sys.argv[2]

user32 = windll.user32

def page_is_loaded(driver):
    return driver.find_element_by_tag_name("body") != None

# ブラウザのドライバを読み込む。
# driver = webdriver.Chrome("./chromedriver.exe") 元々はChromeで実施していたが以下に変更
driver = webdriver.PhantomJS(service_log_path=os.path.devnull)
# 対象ページを読み込ませる。まずはログインページ。
driver.get("https://attendance.cvi.co.jp/LogOn.aspx")
wait = ui.WebDriverWait(driver, 10)
wait.until(page_is_loaded)

# 読み込んだら、find_element_by_xpathで指定して要素を取得し、入力を与える。
# あらかじめ、ブラウザにてwebページのソースを表示して、入力したいフォームなどのIDを探しておく。
email_field = driver.find_element_by_xpath("//*[@id=\"txtUserID\"]")
email_field.send_keys(username)

company_field = driver.find_element_by_xpath("//*[@id=\"txtCompanyCode\"]")
company_field.send_keys("xxx")

password_field = driver.find_element_by_xpath("//*[@id=\"txtPassword\"]")
password_field.send_keys(password)

# 全部入力したら、画面遷移
password_field.send_keys(Keys.RETURN)
wait = ui.WebDriverWait(driver, 10)
wait.until(page_is_loaded)
time.sleep(5)

# 同様に、入力したい箇所のIDを指定して、入力を与える。
# ここではsubmit要素の状態(enabled/disabled)で場合分けしている。
# またMessageBoxでどちらの分岐になったか、わかるようにしている。
if driver.find_element_by_xpath("//*[@id=\"ctl00_ContentMain_btnWebStartTime\"]").is_enabled():
    driver.find_element_by_xpath(
        "//*[@id=\"ctl00_ContentMain_btnWebStartTime\"]").click()  # Send day start
    user32.MessageBoxA(
        0,
        "Start done",
        "AttendancePro",
        0)
else:
    user32.MessageBoxA(
        0,
        "Start already done",
        "AttendancePro",
        0)

# 終わったら、ブラウザとショートカット(後述)のウィンドウを閉じて終了。
driver.close()
os.system('taskkill /fi "WindowTitle eq attendancepro_start"')
sys.exit()
```

# exe化
以下のコマンドでexe化。icoは適当に。
`pyinstaller --clean -F --icon=xxx.ico attendancepro_start.py`

この時点で、できたexeを以下でテスト。
`attendancepro_start.exe <ユーザ名> <パスワード>`
Chrome driverを使う場合はexeと同じフォルダに配置しておく。

# 配布後に必要なアクション
いちいちコマンドプロンプトで実行するのは面倒なので、exeをショートカット化して実行する形を想定。
引数はショートカットのプロパティにて指定します。
![image](https://qiita-image-store.s3.amazonaws.com/0/159980/d1636715-1fea-4806-4137-f0e55faf0336.png)

この方法だと最後にコマンドプロンプトのウィンドウが残るため、最後にtaskkillでウィンドウを消しています。
たぶん普通こんな方法はとらないのだと思いますが、簡単にできる方法がわからなかったので、こうしています…。

# 雑感
かなり楽にはなりました。また、これでwebページをpythonで操作する、ということを覚えたので何かに応用できれば。
Python3.xでもほぼ大丈夫なのですが、途中で使っているMessageBoxAにて最初の文字しか表示されません。MessageBoxWなら大丈夫なのですが、今後は2.xで文字化けします。どうせexeでの使用を想定しているので、今回は目をつぶります。

# 参考
[Seleniumにてウェブページの要素を指定する方法](http://selenium-python.readthedocs.io/locating-elements.html#locating-by-xpath)

[exe化したものに後から変数を指定する方法](https://stackoverflow.com/questions/25984395/after-compiling-python-program-how-to-input-arguments)

[メッセージボックスのパラメータ](https://msdn.microsoft.com/ja-jp/library/cc410914.aspx)

[windowsのショートカットを実行後に、ウィンドウを閉じる](https://stackoverflow.com/questions/16599655/how-to-close-a-cmd-window-using-python-2-7)

[MessageBoxにて最初の文字しか表示されない](https://stackoverflow.com/questions/38302830/python-message-box-shows-only-the-first-letter-of-my-title-and-my-message)