暇なので『RPAツール』を作る #3　selenium編　ログインの自動化

#はじめに
皆さん、また一ヶ月ぐらい間が空いてしまいました。enp（えん）です。メガネかけてます。
この記事は『RPAツール』を作ろうとする人の進捗報告になります。
RPAやRPAツールの作り方が書いてあるものではございませんので、ご了承ください。（もしかしたら、ソースは少しだけ載せるかもしれません。気分次第です）
今回はseleniumと仲良くなろうPart.1です。やったこととしては『ログイン操作』だけですが、結構奥深い内容でした。
また長い記事になってしまいますが、お付き合いいただければ幸いです。

#seleniumって何ができるの？
まずはseleniumについてザックリ知りましょう。
seleniumは__プログラムで直接ブラウザを操作するツールです。__
マウスポインタを利用してブラウザを操作するのではなく、
seleniumがブラウザに命令を出して操作を行うようなイメージですね。
そのためseleniumでブラウザを動かしている間、他の作業を行うことが可能になるでしょう。
（長い動作時間のものを試していないため、実際どうなのかは未検証です）

#seleniumでログインをしてみよう　　ニコニコ動画編
はい。ここからが本題、__seleniumでログインを自動化します。__
なぜログインを自動化するのかというと、__簡単そうでseleniumの練習にぴったりと思ったからです。__
テキストボックスに入力する動作やボタンをクリックする動作など基本的な動作でログインはできますしね。
しかし、<font color=red>__そう甘くはありませんでした。そのお話は後程。__</font>

では、早速ログインを実行していきましょう。手始めにニコ動へログインします。
下記はニコ動のログイン画面です。
![ニコ動のログイン画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641928/ec1e1529-4480-2588-e7ab-9e9accc7bc52.png)
まぁ、こんな感じですよね。下にgoogleでログインなどもありますが今回は使わないので載せていません。
（ロゴの著作権なども怖かったです）

流れとしては、
　　__1. ログインページへ飛ぶ。__
　　__2. 上のテキストボックスにメールアドレスを入力__
　　__3. 下のテキストボックスにパスワードを入力__
　　__4. ログインボタンをクリック__
ですよね。

私の実装法は__『自分が普段行っている手順をそのままプログラミングに落とし込む』__というものです。
なので__上記の流れをそのまま実装していきます。__

コードは以下の通りです。

```python:slogin_niconico.py
# ドライバーインポート
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import psutil

chrome = webdriver.Chrome(r".\driver\chromedriver.exe")
 
# ログインページへ移動
chrome.get("https://account.nicovideo.jp/login?site=nicoaccount&next_url=%2Fmy%2Faccount%3F&time=1593735684")

# ログインに必要な変数
user_name = "user_name"
password = "password"

# テキストボックスやログインボタンの取得
element_user = chrome.find_element_by_id("input__mailtel")
element_password = chrome.find_element_by_id("input__password")
element_logbtn = chrome.find_element_by_id("login__submit")

# テキストボックスへアドレスとパスワードを入力
element_user.send_keys(user_name)
element_password.send_keys(password)

# ログインボタンを押す
element_logbtn.click()

# chromedriverのプロセスを終了する
p = psutil.Process(chrome.service.process.pid)
p.terminate()
```

こんな感じですね。
本当にログインページへ飛んで、テキストボックスに入力して、ログインボタンを押しているだけです。
最後のひと手間は仕様上プログラムが終了してもドライバーは動き続けてしまう問題の対処です。
やっていることは、プロセスIDを使ってドライバーのプロセスをお釈迦にしています。
その他、気になるところありましたらご自分でお調べください！　あくまでも進捗報告ですので。

#seleniumでログインしてみよう　Amazon編
さぁ、ニコ動にログインできたので、今度はAmazonにログインしてみましょう。
__ん？　ニコ動と何が違うの？__　と思われるかもしれません。
__流れは確かにニコ動の時とほぼ同じ__です。しかし、アマゾンはひと手間加えてきます。
![amazonのログイン画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641928/2da4470a-36e0-2a2b-af02-497b2a873091.png)
<font color=red>__そう！　２段階認証！__</font>　ワンタイムパスワードが必要なのです。
（ニコ動でも２段階認証はありますが、今回は無バージョンとして使わせて頂きました）
このワンタイムパスワードを切り抜ける方法を考えるために一ヶ月かかりました。
もちろん selenium に２段階認証を切り抜ける有難い機能がある訳ではありません。これは困った……。
という訳で、ここからの話は２段階認証突破奮闘記、豪華二本立てでお送りします。

###作戦.1　ワンタイムパスワードを生成してくれるアプリの利用
この作戦は、__Google の Google Authenticator などの外部アプリから subprocess モジュールを使ってワンタイムパスワードを取得しようという作戦__です。
ですが、Google Authenticator などの .exe で動くアプリを扱うのは難しいので、__コマンドプロンプト上で動くアプリを使って実装__しようとしました。
そこで見つけたのが下記の二つです。

- oath-toolkit
- authenticator

この二つのどちらかで実装を行う訳ですが、私が選んだ方は __authenticator__。
理由は__ライセンス的に使いやすかったから__です。MITライセンス最高。
しかし、authenticator にも欠点が一つあります。それは __Ctrl-C で終了しないと永遠に終わらないというところです。__
永遠に終わらないと __authenticator のアウトプットを取得することができません。__
そこで役に立つのが __subprocess モジュールの terminate メソッド。__
terminate メソッドを使用すれば __authenticator を強制終了することが可能です。__
終了すればアウトプットも取得できるようになるので、これはやるしかありません。
そして、authenticator と subprocess モジュールの合わせ技で取得した結果が下記になります。

```
Enter passphrase:
```

####なぜじゃ！！　なぜワンタイムパスワードが出ないんじゃ！
理由としては __中断した部分のアウトプットは取得できない__ようです。
Enter passphrase の部分だけ取得できるのは、入力待ちが後に入るからだと思われます。違うかもしれません。
まぁ、ここで重要なことは <font color=red>__subprocess モジュールでは authenticator が生成するワンタイムパスワードを取得できない__</font>ということ。
平たく言うと__作戦失敗__ですね。せっかく subprocess モジュールの使い方学んだのにな。約一ヶ月。

###作戦.2　もう自分で生成しちゃいなよ You
この作戦は、__RPA が自らワンタイムパスワードを生成する__という作戦です。
そうです。__もう面倒だから authenticator みたいなヤツを RPA に組み込んじまおうぜという脳筋発想です。__
RPA 自身がワンタイムパスワードを生成することができれば万事解決。外部アプリなど必要ありません。
そこで登場するのが __pyotp ライブラリ。__
このライブラリは２段階認証をPythonで実装するときに使われるのですが、今回は２段階認証を突破するために使います。
どのように使うのかというと、以下の通りです。

```python
# pyotp ライブラリをインポート
import pyotp

# ワンタイムパスワードの生成
totp = pyotp.TOTP("Google Authenticator を設定するときに入力する英数字")
totp.now()
```

####驚くほど簡単！
これでワンタイムパスワードが突破できると言うんだから驚きです。
余計、一ヶ月の努力が悲しみとして残ります。
実際に使って動くことも確認済み。見事、__作戦成功です！__　他力本願という奥の手を使わずに済みました。


以上で２段階認証突破奮闘記終了です。
いやぁ、やっていて奥深い内容でした。特に使わなかった subprocess が。
さて、ここまで来ればあとはプログラムを書くだけです。
書いたプログラムは下記になります。

```python:slogin_amazon.py
#ドライバーインポート
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import psutil
import pyotp

chrome = webdriver.Chrome(r".\driver\chromedriver.exe")

# ログインページへ移動
chrome.get("https://www.amazon.co.jp/ap/signin?openid.pape.max_auth_age=0&openid.return_to=https%3A%2F%2Fwww.amazon.co.jp%2F%3Fref_%3Dnav_signin&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.assoc_handle=jpflex&openid.mode=checkid_setup&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&")

# ログインに必要な変数
user_name = "user_name"
password = "password"

# １ページ目のテキストボックスを取得
element_user = chrome.find_element_by_name("email")

# メールアドレスを入力（Enterで決定）
element_user.send_keys(user_name)
element_user.send_keys(Keys.ENTER)

# ２ページ目のテキストボックスを取得
element_password = chrome.find_element_by_name("password")

# パスワードを入力（Enterで決定）
element_password.send_keys(password)
element_password.send_keys(Keys.ENTER)

# ワンタイムパスワードの生成
element_totp = chrome.find_element_by_name("otpCode")
totp = pyotp.TOTP("Google Authenticator を設定するときに入力する英数字")

# ワンタイムパスワードを入力（Enterで決定）
element_totp.send_keys(totp.now())
element_totp.send_keys(Keys.ENTER)

# chromedriverのプロセスを終了する
p = psutil.Process(chrome.service.process.pid) 
p.terminate()
```

こんな感じになります。
今回はボタンをクリックするのではなく、Enter を押すタイプで実装しました。
これで今回やった事は以上になります。

#RPA ツールを実装する上で必要なこと
ログインの自動化を一通り行い、selenium の仕様が少しは理解できました。
この項目では今回やった中で RPA ツール実装にかかわる内容を列挙したいと思います。
以下、箇条書きが続きます。

- __ドライバーを指定するためにブラウザを選択してもらう必要がある。__
- __テキストボックスなどの取得のため class 名や id 名などを入力してもらう必要がある。__
- __テキストボックスなどの取得に使用するものが何か選択してもらう必要がある。__
- __入力する内容を各テキストボックスで指定してもらう必要がある。__
- __２段階認証が必要な場合、アカウントごとに一度 Google Authenticator の設定みたいな手順を踏んでもらう必要がある。__
- __プログラミングができない人も使えるようにするため、class 名などが何か開発者側で発信する必要がある。__
- __ドライバーを使い終わったら、そのプロセスを消す必要がある。__

今思いつく限りではこれぐらいです。

#最後に
最後までお付き合いただきありがとうございます。
私が書いたソースを初めて載せましたが、如何だったでしょうか？
今回行ったのは『ログインの自動化』だけでしたが、やりごたえのある内容でした。
次回の記事は『seleniumと仲良くなろう Part.2』になると思いますので、よろしくお願いいたします。
以上、enpがお送りいたしました。

【追記】
ソース修正のご指摘を受け、採用させていただきました。
shiracamus 様ありがとうございます。
raw 文字列にしないと n などで始まるディレクトリなどがあった場合、
普通の \ と認識されず動作しない恐れが……。
基礎なのかもしれませんが、私にとって大変貴重なご意見でした。