selenium × python開発TIPS集

# はじめに
ブラウザポチポチ作業をselenium * pythonで自動化したいときのtips集。

seleniumは4系を利用。
```powershell
PS > pip list | Select-String selenium

selenium           4.27.1
```

# 導入

・pythonにseleniumのライブラリをインストール
```powershell
pip install selenium
```

・利用するブラウザに応じたWebDriverをDLして配置

手元では、使っているchrome のバージョンに合わせて128.0.6613.138を利用。
https://developer.chrome.com/docs/chromedriver/downloads/version-selection?hl=ja にも
https://googlechromelabs.github.io/chrome-for-testing/ にも見つからないので
https://storage.googleapis.com/chrome-for-testing-public/128.0.6613.138/win64/chromedriver-win64.zip
を直接ブラウザに打ってDL。


# seleniumからchromeを起動

DLしたchromeのドライバを指定して、seleniumからchromeを起動する。
```python
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions()
options.add_experimental_option("detach", True)

service = ChromeService(executable_path="./chromedriver-win64/chromedriver.exe")
driver = webdriver.Chrome(service=service, options=options)

driver.get('https://www.google.co.jp/')
```


# 要素の取得・操作

chrome起動後は、
・要素を取得
　→要素の操作(クリック・値の入力・ボタン押下など)
を何度も繰り返していくことになる。

特に前者は、「欲しい要素が持つid, name, class, text() 等から、その要素を一意に特定できるものを選んで指定し、要素を取得する」という動きを繰り返しすることになる。

どうやって一意に指定するか、という部分が肝｡
明らかに一意なid(本来一意のはず), classが振られていれば、それを指定すれば済むが、
それが難しい場合、XPATHでフルパスを指定するか、and条件を使うなど工夫することになる。



要素の取得方法は以下などを参照。

https://qiita.com/mochio/items/dc9935ee607895420186

https://qiita.com/VA_nakatsu/items/0095755dc48ad7e86e2f


### sample_idというid属性を持つ要素をクリックする
```python
sample_id= driver.find_element(By.ID, "sample_id")
sample_id.click()
```

例えば以下が引っかかる。

```html
<button id="sample_id" class="btn-class">ボタン</button>
```

※find_elementだと、仮に複数見つかっても最初の一つしか返さない。
　find_elementsにすると、リストで返してくれる。
　※単一のつもりが誤ってfind_elementsを指定してしまっていて、思っていた返り値と違う・・・というミスが発生しがち。

### LoginIdというname属性を持つinputフィールドに値を入れる

```python
input_login_id = driver.find_element(By.NAME, "LoginId")
input_login_id.send_keys("alphajinsei")
```

例えば以下が引っかかる（値入力後）
```html
<input name="LoginId" type="text" value="alphajinsei" class="input-class">
```


### XPATHをフルパスで指定して要素を指定・クリックする

```python
wariai = driver.find_element(By.XPATH, "/html/body/form[1]/div/div[11]/table[1]/tbody/tr/td[3]/a")
wariai.click()
```

XPATHのフルパスの指定は開発者ツールから。


### 「サンプルテキスト」という文字列を含む要素をクリックする
```python 
driver.find_element(By.XPATH, '//*[contains(text(),"サンプルテキスト")]').click()
```

たとえば以下のような要素が引っかかる
```html
<span>サンプルテキスト</span>
```

```html
<button class="btn-class">サンプルテキスト</button>
```


### ボタンのタグで、「選択」というテキストを含んでいて、かつname属性がselectButtonである要素を取得する

```python
driver.find_element(By.XPATH, '//button[contains(text(),"選択") and @name="selectButton"]').click()
```

例えば以下が引っかかる
```html
<button type="button" name="selectButton">選択</button>
```


##### XPATHの表記について
上記のXPATHの表記`'//button[contains(text(),"選択") and @name="selectButton"]`は、

* `//` : 本来XPATHは `/html/body/form[1]`のようにフルパス表記するが、ワイルドカード的に省略して`//`とできる。
* `button[]` :  buttonタグである要素。ここも省略して`//*[XXX]`のようにしてもよい。
* `contains(text(),"選択")` : 持っているテキストが「選択」を含んでいること。決め打ちでよければ`//button[text()='選択']`と書いても良い
* `@name="selectButton"` : name属性にselectButtonと表記されていること を指定。属性(`<button name="XX">`のようにタグ内に書かれているやつ)は@で指定する

以下に詳しい。

https://techblog.zozo.com/entry/xpath

### 複数のclassが指定されている要素を全て指定して選択する

以下のように、classが複数指定されていて、指定されているclassの全量("aaa bbb ccc")でひっかけたい場合、
```html
<div class="aaa bbb ccc">　★これを取得したい
<div class="aaa bbb ddd">
```

以下のように（`By.CLASS_NAME`ではなく）XPATHで複数クラスを全量指定することで取得できる。
```python
aaa_bbb_ccc = driver.find_element(By.XPATH, '//div[@class="aaa bbb ccc"]')
```

※`driver.find_element(By.CLASS_NAME, 'aaa bbb ccc')`とは書けない。

### `find_element`で取得したWebElementに対して、さらに子要素をXPATHで指定したい

https://www.selenium.dev/ja/documentation/webdriver/elements/finders/#%E8%A6%81%E7%B4%A0%E3%81%8B%E3%82%89%E8%A6%81%E7%B4%A0%E3%82%92%E6%A4%9C%E7%B4%A2

より、XPATHの指定の先頭に、`.`を付ける。

```python
##get elements from parent element using XPATH
##NOTE: in order to utilize XPATH from current element, you must add "." to beginning of path

    # Get first element of tag 'ul'
element = driver.find_element(By.XPATH, '//ul')

    # get children of tag 'ul' with tag 'li'
elements  = driver.find_elements(By.XPATH, './/li')
```

# selenium開発TIPS集


### 途中まで動かしたあと、対話モードで自由に確認したい

```shell
python -i selenium_test.py
```

とすることで、実行後にそのまま対話モードに移行する。
また、単に`python`で対話モードを起動したのち、
```python
exec(open("./selenium_test.py", encoding="utf-8").read())
```
とすることで、対話モード内で書いたスクリプトを実行させることができる。
この後も対話モードのままなので、スクリプトで利用した変数、WebElementの内容を確認することができる。


### 今driverが認識しているURL,htmlの内容を確認したい
```python
driver.current_url
driver.page_source
```

### 選択した要素のhtml表記を確認したい

`.get_attribute('outerHTML')`でhtml要素を確認できる

```python
date_item = driver.find_element(By.XPATH, "/html/body/div[4]/table/tbody")
date_item.get_attribute('outerHTML')
```

### 選択した要素が持つ特定の属性の値を確認したい

例えば以下のようなhtmlがあった際に、
```html
<input type="hidden" name="input_value" value="99" disabled="">
```

以下のように`get_property()`メソッドを利用することで、所望の属性の値(`value=99`)を取得することができる
```python
value = driver.find_element(By.NAME, "input_value").get_property("value")
# valueに99が入る
```

その他、WebElement(find_elementで取得できるクラス)に備わったプロパティ・メソッド集は下記。

https://www.selenium.dev/selenium/docs/api/py/webdriver_remote/selenium.webdriver.remote.webelement.html







# その他

selenium3 → 4に変わって、要素の選択の記述方法が微妙に変わっている点に注意。
```python
jsk_send_btn = driver.find_element(By.ID, "send_btn") # 新 (直近はこちらを利用)
jsk_send_btn = driver.find_element_by_id("send_btn") # 旧
```