SeleniumでEラーニングの受講者情報更新を自動化

こんばんは。二度目の投稿の@0yanです。
今回はSeleniumでEラーニングの受講者情報更新を自動化しました（社内業務自動化の一環です）。
Seleniumでクリックできない問題にドハマったので、同じことで悩んでいる人の一助になれば幸いです。
なお、本記事は私と同じく、Selenium初心者の方を対象としております。

# やったこと
Seleniumで
1.Eラーニングの管理者ログインページに遷移
2.ログイン
3.受講者一覧ページに遷移
4.受講者詳細ページに遷移
5.上司という項目を更新、登録ボタン押下（受講者一覧ページに戻る）
6.上記4，5を約250回繰り返す
という作業を自動化しました。

### 自動化の背景
私の所属会社では、求人情報サイトを運営する某大手企業のEラーニングを利用しております。

- コンテンツが充実している（考え方やコンピテンシーからプログラミングや語学まで幅広いコンテンツが揃っている）
- 管理者（人事）側で、受講者毎にオススメ講座を設定出来る
- 受講報告メールがイケてる（「会社に対する提案」という項目があり、そこから組織改善につながるアイディアが得られる）

といった良い点が沢山あるにも関わらず、同業他社と比べて安価で非常に気に入っております。

しかしながら、この素晴らしいEラーニングにも一点だけ不満があります。それは**受講者一括更新機能の対象項目に、一部の項目が入っていない**こと。

問題の項目は（受講者の）**上司**という項目なのですが、これを設定しないと受講報告メールが上司に飛ばないのです。
受講報告メールが上司に飛ばないと困るので、当然、設定するわけですが、会計年度が切り替わるときに発生する大規模な体制変更の際は、上司の変更作業だけで一日が終わってしまいます。
しかも、クリックするだけの簡単なお仕事。価値を生まない、誰でもできる作業に段々嫌気が差してきます。
そこで今回、Python版Seleniumを使ってこの作業を自動化することにしたのです。

# 環境
Windows 10 Home（64bit）
Google Chrome 75.0.3770.90
Python 3.6.8（Anaconda3をインストール）
chromedriver-binary 74.0.3729.6.0
selenium 3.141.0

# ライブラリ周り
SeleniumとWebDriver（今回はChromeDriver）のインストール、Seleniumの基本操作については、@memakura さんの記事を参考にさせて頂きました。
> [Python + Selenium で Chrome の自動操作を一通り](https://qiita.com/memakura/items/20a02161fa7e18d8a693)

@memakura さん、ありがとうございました！

```python:boss_update.py
# coding: utf-8

import csv
import time

import chromedriver_binary
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
```

# コーディング
### やったこと（再掲）
Seleniumで
1.Eラーニングの管理者ログインページに遷移
2.ログイン
3.受講者一覧ページに遷移
4.受講者詳細ページに遷移
5.上司という項目を更新、登録ボタン押下（受講者一覧ページに戻る）
6.上記4，5を約250回繰り返す
という作業を自動化しました。

### 1.Eラーニングの管理者ログインページに遷移
管理者ログインページにアクセスします。

```python:boss_update.py
def main():
    driver = webdriver.Chrome()
    driver.get('管理者ログインページのURL')
```

### 2.ログイン
テキストボックスにログインIDとパスワードを入力し、ログインボタンを押下します。

```python:boss_update.py
    driver.find_element_by_xpath('ログインIDテキストボックスのxpath').send_keys('ログインID')
    driver.find_element_by_xpath('パスワードテキストボックスのxpath').send_keys('パスワード')
    driver.find_element_by_xpath('ログインボタンのxpath').click()
```


### 3.受講者一覧ページに遷移
管理者ページTOPから受講者一覧ページに遷移します。

```python:boss_update.py
    driver.find_element_by_xpath('受講者一覧ページへの遷移ボタンのxpath').click()
```

### 4.受講者詳細ページに遷移
受講者一覧ページは、下図のようなカードが沢山並んだ構造となっており、受講者氏名に受講者詳細ページへのリンクがはられています。
また、受講者氏名のxpathは「～[@id="student_受講者番号"]～」のようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373977/faa4528a-57d3-5ff9-0cb6-d9a2479d4ceb.png)

なので今回は、下図のようなCSVをフォーマットとし、xpath内の受講者番号を可変とすることで、受講者一覧ページ内の特定の受講者氏名をクリックできるようにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373977/c2005a9d-1b9e-036d-c69b-63f119605efe.png)

```python:boss_update.py
with open(file='.\\update_boss.csv', mode='r', encoding='utf-8-sig', newline='') as csv_file:
    reader = csv.DictReader(csv_file)
    for row in reader:
        try:
            student_number = row['受講者番号']
            # 受講者氏名を受講者番号で検索してクリック
            driver.find_element_by_xpath('～[@id="student_{}"]～'.format(student_number)).click()
        except:
            # 受講者番号が受講者一覧ページにない場合はログ残す
            print('受講者番号検索エラー({})'.format(row['受講者番号']))
```

### 5.上司という項目を更新、登録ボタン押下（受講者一覧ページに戻る）
with open ステートメントの続きです。エラーとなっていなければ、受講者詳細ページに遷移しています。
このページで苦戦しました・・・！

##### 苦戦した点
1.テキストボックスを選択できない
上司を入力するテキストボックスがページ最下部にあり、前ページから遷移してきた直後は見えない状態になっています。
それが理由なのかはわかりませんが、find_element_by_xpathメソッドだけではテキストボックスを選択できませんでした。
そのため、execute_scriptメソッドを使い、JavaScriptでページ最下部までスクロール。
これで解決できました。

2.候補者リストをクリックできない
テキストボックスに上司の氏名を入力すると、登録されている受講者リストから候補者の氏名が出てきます（下図参照）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/373977/b8210a04-07da-299f-06f4-03e75c0cf5de.png)

しかし、なぜか候補者リストをクリックできませんでした。
ページのソースを見ると、selectタブになっていたため、「プルダウンリストのように、Selectクラスのインスタンスを生成して選択する必要があるのか？」と考え、それを試しましたがうまくいかず・・・。
四苦八苦しましたが、候補者リストが出てきてから1秒待ったらクリックできました。

```python:boss_update.py
        try:
            # JavaScriptでページ最下部までスクロール
            driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

            # 現在の上司を消す（余裕をみて3回バックスペース）
            for i in range(3):
                driver.find_element_by_xpath('～[@id="edit_student_{}"]～'.format(student_number)).send_keys(Keys.BACK_SPACE)

            # 上司を入力し、候補者リストを表示させる
            boss = row['上司']
            driver.find_element_by_xpath('～[@id="edit_student_{}"]～'.format(student_number)).send_keys(boss)

            # 候補者リストをクリック（1秒以上待たないとエラーになる）
            time.sleep(1)
            driver.find_element_by_xpath('候補者リストのxpath').click()

            # 登録するボタンをクリック
            driver.find_element_by_xpath('～[@id="edit_student_{}"]～'.format(student_number)).click()
        except:
            print('上司更新エラー({})'.format(row['受講者番号']))

        # 一時停止
        time.sleep(1)
```

# さいごに
Qiitaへの記事投稿をTwitterでもつぶやいております。
フォローして頂けますと幸いです（ユーザー名は@0yan_sです）。

長文にも関わらず、最後までご覧頂き誠にありがとうございました。