PythonのSeleniumでWebを自動操作する

Chromeの操作を自動化したくてSelenium4を使ってみました。Selenium4を調べてみると結構機能が多かったので、私が使いそうなものだけ記載してあります。

# インストール

まずSeleniumで使用するChromeをインストールします。

```bash:
apt install google-chrome-stable
```

次にSelenium4をインストールします。

```bash:
apt install python3-selenium
```

# ライブラリのインポート

Seleniumのライブラリをインポートします。

```python:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import Select
```

※今後はこれらのライブラリがインポートされた前提でプログラムを記載します。

# Chromeを起動する

Chromeを起動するプログラムを記載します。

```python:
driver = webdriver.Chrome()
```

Chromeのウィンドウを表示したくない時はheadlessモードを使います。

```python:headlessモード
options = Options()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
```

Seleniumの動作を速くさせたい時は、画像の読み込みを行わないという選択肢もあります。その場合の下のようなオプションを付けます。

```python:画像を読み込まない設定
options = Options()
options.add_argument('--blink-settings=imagesEnabled=false')
driver = webdriver.Chrome(options=options)
```

※ここから下に`driver`変数が出てきますが、（上記のプログラムにより）webdriverから受け取ったオブジェクトを意味します。

# ウィンドウ／タブの追加・移動・削除

新しくウィンドウやタブを開きたい場合は、下のメソッドを実行します。

```python:
driver.switch_to.new_window('window')   # 新しくwindowを開く
driver.switch_to.new_window('tab')      # 新しくtabを開く
```

各ウィンドウ（タブを含む）のハンドルは、`window_handles`にリスト形式で入っています。

```python:全ハンドル（3個のタブやウィンドウ）を表示した例
print(driver.window_handles)
['CDwindow-46843BD41D0BD8B648BB157A02E18147', 'CDwindow-D9461BBC63C20292878DBBFF56E73487', 'CDwindow-1EDB91E5F4C7DF33742C2773CB7EB3FD']
```

現在アクティブなウィンドウ（タブを含む）のハンドルは、`current_window_handle`で取り出すことができます。

```python:アクティブなハンドルを表示した例
print(driver.current_window_handle)
'CDwindow-46843BD41D0BD8B648BB157A02E18147'
```

既存の別ウィンドウ（タブを含む）に移動するには、下のメソッドを実行します。

```python:
driver.switch_to.window('CDwindow-D9461BBC63C20292878DBBFF56E73487')   # ハンドルを入れる
```

アクティブなウィンドウ（タブを含む）を閉じる場合は、下のメソッドを実行します。

```python:
driver.close()
```

# ページを開く／移動する

ページを表示する、または別のページに移動するにはgetメソッドを使います。

```python:
driver.get('https://qiita.com/login')
```

![login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593076/ad0db65f-0872-c0df-b0a7-7e7240bbfd43.png)
※これ以降はこのQiitaのログイン画面を想定した操作になります。

# ブラウザ関連

ブラウザの「前に戻る」や「次に進む」や「再読み込み」は下のメソッドで実行できます。

```python:
driver.back()        # 前に戻る
driver.forward()     # 次に進む
driver.refresh()     # 再読み込み
```

ウィンドウのサイズを変えて、スクリーンショットをとるには、下のメソッドを実行します。

```python:
driver.set_window_size(800,600)        # ウィンドウサイズを800x600に変更
driver.save_screenshot('qiita.png')
```

下に少しだけスクロールをするには、下のメソッドを実行します。なお、`execute_script`はjavascriptを実行するメソッドです。つまりjavascriptのスクロール機能を使っています。

```python:
driver.execute_script("window.scrollBy(0,100);")    # 下に100px移動する
```

# 要素の操作

要素を検索するには、下の操作を行います。

```python:
element = driver.find_element(By.ID,'identify')              # id属性がidentifyである最初の要素を取り出す
element = driver.find_element(By.NAME,'commit')              # name属性がcommitである最初の要素を取り出す
elements = driver.find_elements(By.TAG_NAME,'a')             # aタグの全要素を取り出す
elements = driver.find_elements(By.PARTIAL_LINK_TEXT,'sign') # テキストにsignの文字が含まれる全要素を取り出す
```

要素のテキストや属性を表示するには、下の操作を行います。

```python:テキストの使用例
elements = driver.find_elements(By.PARTIAL_LINK_TEXT,'sign')
print(elements[0].text)
sign up
```

```python:属性の使用例
element = driver.find_element(By.NAME,'commit')
print(element.get_attribute('value'))
Log in to Qiita
```

（取り出した要素が`<input id="identify" type="text">`であった場合）その要素に文字を入れるには、下のメソッドを実行します。

```python:
element = driver.find_element(By.ID,'identify')
element.clear()             # 先に入っている文字を消す
element.send_keys('hoge')   # hogeを入力する
```

（取り出した要素が`<input name="commit" type="submit">`であった場合）その要素をクリックするには、下のメソッドを実行します。

```python:
element = driver.find_element(By.NAME,'commit')
element.click()
```

ただ`send_keys`は文字入力にタイムラグが発生するみたいで、文字入力の前にクリックが押される不具合が多々発生しました。そのため`send_keys`を使わず`execute_script`を使って直接文字を要素に入れた方が確実みたいです。

```python:
driver.execute_script("document.getElementById('identify').value='hoge';")
element = driver.find_element(By.NAME,'commit')
element.click()
```

# 暗黙の待機

ページ遷移した直後に検索を行う場合、要素の読み込みが終わってなく検索に失敗することがあります。これを回避する手段が2つあります。

1つは、ページの全ての要素の読み込みを待つ方法です。下にプログラムを記載します。

```python:
wait = WebDriverWait(driver,10)
wait.until(EC.presence_of_all_elements_located) # 全ての要素が配置されるまで最大10秒待つ(全要素が配置されればすぐに終了)
```

もう1つは、目的とする要素を検索する時に、存在していなければ指定した秒数まで検索を続ける方法です。そのプログラムを下に記載します。`implicitly_wait`メソッドを実行した後に行った検索は、要素が見つからなければ指定した秒数が過ぎるまで検索を続けます。

```python:
driver.implicitly_wait(10) # 以降の検索で、要素が見つかるまで最大10秒待つ(見つかればすぐに終了)
```

# 明示的な待機

ボタンがアクティブになるまで待機する場合は、下のメソッドを使います。

```python:
wait = WebDriverWait(driver,20)
wait.until(EC.element_to_be_clickable(By.NAME,"commit"))
```

# まとめ

最後に、Qiita.comの自動ログインのサンプルを記載します。

```python:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome()
driver.get('https://qiita.com/login')
wait = WebDriverWait(driver,10)
wait.until(EC.presence_of_all_elements_located)   # execute_scriptはimplicitly_wait未対応

driver.execute_script("document.getElementById('identity').value='hoge';") # ユーザ名またはメールアドレスの欄
driver.execute_script("document.getElementById('password').value='hoge';") # パスワードの欄
element = driver.find_element(By.NAME,'commit')                            # Qiitaにログインのボタン
element.click()
```

# 追記

いろいろ操作してみると、上記のサンプルでは足りない部分も出てきたので追記します。

現在のURLを取得するには、`current_url`を使います。

```python:現在のURLを表示した例
print(driver.current_url)
https://qiita.com/login
```

BeautifulSoup4を使い解析するサンプルを下に示します。

```python:BS4を使用した例
from bs4 import BeautifulSoup
soup = BeautifulSoup(driver.page_source.encode('utf-8'))
print(soup.find('a').get('href'))
/signup
```

マウスオーバーで要素が出る場合があります。それを実現するには`ActionChains`を使用します。

```python:
action = ActionChains(driver)
element = driver.find_element(By.NAME,'commit') # マウスオーバーする要素
action.move_to_element(element).perform()
```

プルダウンメニューを操作するプログラムを下に示します。

```html:HTMLの例
<select id="hoge">
 <option value="1">first</option>
 <option value="2">second</option>
</select>
```

```python:
element = driver.find_element(By.ID,'hoge')
select = Select(element)
select.select_by_visible_text('second')  # 表示している文字で選択
select.select_by_value('2')              # value属性で選択
select.select_by_index(1)                # 上からの番号で選択(0から始まる)
```