【Python】Seleniumの使用方法メモ

#Seleniumとは
Seleniumとは、Webの自動テストのためのライブラリです。
最近では、自動テストのため以外でもスクレイピングのために使用するひとも多いと思います。
かくいう、私もその一人です。スクレイピングのためにこれさえ知っていればできるということを紹介できればと思います。

#インストール
インストールは以下のコマンドでできます。

```
pip install selenium
```

#実装方法
##ブラウザの指定
Seleniumでは、PythonのコードからWEBブラウザを操作します。操作するためには、WebDriverが必要なのですがそれらは各ブラウザの公式サイトからダウンロードしてください。
今回サンプルでは、firefoxを操作する場合と、Chromeを操作する場合のサンプルを記述してみます。

```py
from selenium import webdriver

#Chromeを操作
driver = webdriver.Chrome()

#Firefoxを操作
driver = webdriver.Firefox()
```

このようにwebdriverのChromeインスタンス、Firefoxインスタンスをそれぞれ作成することによって各ブラウザを操作することが可能になります。
引数に何も指定しない場合、各ブラウザのwebdriverはPATHが通ったディレクトリに配置されていないと実行に失敗してしまうので注意してください。
webdriverを配置しているディレクトリにPATHを通したくない場合は以下のように、引数：executable_pathにwebdriverを指定してください。

```py
#Chromeを操作
driver = webdriver.Chrome(executable_path="D:\webDriver\chromedriver")

#Firefoxを操作
driver = webdriver.Firefox(executable_path="D:\webDriver\geckodriver")
```

##ページを開く
Seleniumを使用する基本となるWEBページを開くには、Seleniumのget関数を使用します。

```py
// フルパスを指定
driver.get("https://www.yahoo.co.jp/");
```

##要素の指定
seleniumでは要素を取得するための関数がwebdriverに多く準備されています。
このように、find_element_by_～という関数が多くあり、要素の指定方法の違いにとって関数が分かれています。
xpath, id, cssSelecter, classNameなどいろいろな方法で取得できます。

```py
from selenium.webdriver.common.by import By
#単一の要素を取得
driver.find_element_by_xpath("//*[@id='srchtxt']")
#このような指定方法もある
driver.find_element(By.XPATH, "//*[@id='srchtxt']")

#複数の要素を取得する場合
driver.find_elements_by_xpath("//*[@class='srchtxt']")
```

##要素を操作する1
上記で示した方法で要素を取得すると、WebElementが返却されます。
WebElementクラスには、send_keys、click、clear関数といった要素に対する操作関数が存在します。

####send_keys
その要素に対して、値を設定します。

####click
その要素をクリックします。

####clear
テキストを削除します。

####text
textを取得します。

##要素を操作する2
このWebElementクラスだけでも便利なのですが、このクラスではCtrl + cといったより複雑な処理を実施することができません。
そこで、Seleniumには、selenium.webdriver.common.action_chains.ActionChainsクラスが準備されています。
ActionChainsクラスは、Shiftを押下しながら入力といった特殊な処理はもちろん、F1やF3など特殊なキー操作を行うことができます。
もちろん、通常のクリック処理や値の設定も行うことができます。
ActionChainsクラスは、各メソッドを実行するごとにキューに操作を貯めます。そして、perform関数が実行された時点で今まで貯めていたキュー
の操作をまとめて実行します。また、ActionChainsインスタンスの関数は、自身を返却するのでメソッドチェーンで記述することができます。

```py
from selenium.webdriver.common.keys import Keys
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains

actionChains = ActionChains(driver)
#テキストボックスに値を設定して、検索ボタンをクリックする
actionChains.send_keys_to_element(driver.find_element_by_xpath("//*[@id='srchtxt']"), "TESTETST")\
    .click(driver.find_element_by_xpath("//*[@id='srchbtn']")).perform()

#Shiftを押下しながら、abcを入力
actionChains.key_down(Keys.SHIFT)\
    .send_keys_to_element(driver.find_element_by_xpath("//*[@id='srchtxt']"), "abc")\
    .perform()
```

##iframe、windowの変更
frameを変更するためには、Webdriverのswitch_to_frame関数を利用します。
この関数は、WebElement、id、nameのどれかを引数に取ります。frameを特定しやすいやり方を利用してください。

```py
driver.get("http://lab.agr.hokudai.ac.jp/useful/TAG/frame/Fram3-1HmM.htm")
frame = driver.find_elements_by_xpath("//frame")[2]
#Elementもしくは、id, nameの指定が可能
driver.switch_to_frame(frame)
brl = driver.find_element_by_class_name("fpt13")

#親フレームに戻る
driver.switch_to().parent_frame()
```

Windowの移動は、Webdriverのswitch_to_window関数を利用します。
この関数は、Window handleもしくは、WindowNameを引数に取ります。
Window handle自体は、WebDriverのwindow_handles関数で取得できますので利用してみてください。

```py
for window_handle in driver.window_handles:
    driver.switch_to_window(window_handle)
```

##待機
最近のWebサイトではajaxを使用してページを読み込んでから画面を書き換えるといったことが多く行われています。
Seleniumでは、要素が存在しないのに参照した場合、NoSuchElementExceptionを発生させます。
それを回避するために、Seleniumで待機処理が提供されていますので紹介します。

###WebDriverWait
WebDriverWaitクラスを利用して、一定期間待機します。

```py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver.get('https://www.yahoo.co.jp/')
element = WebDriverWait(driver, 10).until(
    expected_conditions.presence_of_element_located((By.ID, "myDynamicElement"))
)
```

この処理では、最大10秒間指定した要素が存在するまで待機します。
存在しない間、デフォルトでは0.5秒ごとに要素が存在するか確認しています。
until関数に指定するクラスは、expected_conditionsに多く定義されています。要素が配置されるまで以外にもタイトルが任意の値になるまでとか。

###デフォルト待機
指定した要素が見つからなかった場合、一定期間ポーリングを行うように設定する方法です。
デフォルトでは0になっていますが、設定すると各処理で待機処理をしなくて済むので便利かと思います。

```py
#秒
driver.implicitly_wait(10)
```

#最後に
やっぱりJavaのSelenideが一番使用しやすいように感じます。