面倒な社内ルーティンワークをSeleniumとBeautifulSoupで自動化した話

## 概要
Seleniumを使ってログインなどの画面操作を行い、その後スクレイピングすることによってWeb上の情報を取得する方法を紹介します。
それぞれの個別の情報はネットで簡単に見つけられるのですが、どう組み合わせるのか少し迷ったので一連の流れを解説します。
全体のコードは最後に記載しています。

## 経緯
新人の頃、テスト環境のプロダクトのバージョンを確認して報告するというルーティンワークがありました。
確認する対象のプロダクトが結構な数あったため、面倒に思って自動化しようと思ったのがきっかけです。
その時に作ったコードのメイン部分を抜き出しています。

## 使用技術
以下のツールやライブラリを使用しています。言語はPythonです。

- 画面操作：Selenium、ChromeDrivr
- スクレイピング：BeautifulSoup
- 正規表現：re

### 必要なライブラリのインポート

```python
from selenium.webdriver import Chrome, ChromeOptions
from bs4 import BeautifulSoup
import re
import time
```

### Driverの設定

```python
options = ChromeOptions()
options.add_argument('--headless')
driver = Chrome(executable_path="/path/to/your/chromedriver",chrome_options=options)
```

1,2行目で、画面操作をヘッドレスモード(ブラウザを立ち上げずに画面操作する)で行うためのオプションを設定しています。
3行目で、ChromeDriverの実行を行なっています。
操作している様子を見たい場合は、2行目をコメントアウトしてください。

### ログイン操作

```python
#URLを開く
driver.get(LOGIN_URL)

#IDで要素を取得し、ユーザとパスワードを入力する
userNameField = driver.find_element_by_id("userId")
userNameField.send_keys(USER)

passwordField = driver.find_element_by_id("password")
passwordField.send_keys(PASS)

submitButton = driver.find_element_by_id("loginBtn")
submitButton.click()
```

4行目で、HTMLのタグであるIDを指定して入力ボックスの要素を取得します。
SeleniumにはIDタグだけでなく、CLASSタグや、CSSセレクタやxPathで指定できる関数もあるので見てみてください。
5行目で、入力ボックスに値を入力しています。
最後の行で、ログインボタンをクリックしています。

#### 参考
- Selenium Python Bindings 2 ドキュメント
    - https://kurozumi.github.io/selenium-python/locating-elements.html

### 画面が開くのを待機
```python
time.sleep(1)
```
実はこの書き方、本当はあまりよくないです。理由は以下です。

- 他の実行ファイルがあった場合、sleep関数はそれも停止してしまう
- 無駄な待ち時間が発生して、実行時間が長くなる

今回は他に何も実行していないのと、待つ時間が短い&頻度が少ないためでこの書き方にしましたが、
本来であればSeleniumの

- wait関数
- waitUntil関数

などを使用することをお勧めします。

### スクレイピングする
```python
#対象の画面を開く
driver.get(TARGET_URL)

#ソースを取得し、パーサーにかける
data = driver.page_source
soup = BeautifulSoup(data,"lxml")

#対象のソースを絞り込む
target_value = soup.find('meta', content=re.compile('xxx'))
print(target_value)
```

7行目では、HTMLのmetaタグのcontentの中身にある、xxxに該当する値を取得しています。
BeautifulSoupだけで取れる要素は限界があるため、対象をさらに絞り込むためには正規表現ライブラリであるreを使います。

reを使い慣れている人であれば、以下の方が使いやすいかもしれません。
取得方法が違うだけで、上記の7行目と同じです。お好みでどうぞ。

```python
target_value  = re.search('xxx', soup.meta['content'])
```

#### 参考
- Beautiful Soup Documentation (公式リファレンス)
    - https://www.crummy.com/software/BeautifulSoup/bs4/doc/
- PythonとBeautiful Soupでスクレイピング
    - https://qiita.com/itkr/items/513318a9b5b92bd56185

### Driverを終了する

```python
driver.close()
```
ブラウザが閉じ、Driverが終了します。

### コード全体

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from selenium.webdriver import Chrome, ChromeOptions
from bs4 import BeautifulSoup
import re
import time

LOGIN_URL = "xxx"
TARGET_URL = "xxx"
USER = "xxx"
PASS = "xxx"

options = ChromeOptions()
#options.add_argument('--headless')
driver = Chrome(executable_path="/path/to/your/chromedriver", chrome_options=options)

driver.get(LOGIN_URL)

userNameField = driver.find_element_by_id("userId")
userNameField.send_keys(USER)
passwordField = driver.find_element_by_id("password")
passwordField.send_keys(PASS)
submitButton = driver.find_element_by_id("loginBtn")
submitButton.click()

time.sleep(1)

driver.get(TARGET_URL)

data = driver.page_source
soup = BeautifulSoup(data,"lxml")
target_value = soup.find('meta', content=re.compile('xxx'))
print(target_value)

driver.close()
```

### 最後に
解説は以上です。間違いや不明な点などあればご指摘ください。
そのうち気が向いたら、Slackで報告する部分の自動化の方法も載せようと思います。