最近Seleniumを試していたので、使い方を忘れてもまた思い出せるよう、Seleniumの使い方をメモしておこうと思います。
インストール
Selenium
pip install selenium
ブラウザとそのブラウザを駆動するWebDriverは別途インストールする必要がある。
WebDriver
- WebDriverはブラウザのバージョンに合ったものをインストールする必要がある。
- 手動で行うと手間なのでwebdriver_managerを使って自動インストールする。
pip install webdriver-manager
webdriver_managerの使い方
from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())
サンプル
例)Googleで「test」と検索し、結果の2ページ目のソースを表示する。
from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from webdriver_manager.chrome import ChromeDriverManager driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) driver.implicitly_wait(30) driver.get('https://www.google.com/') driver.find_element(By.CSS_SELECTOR, 'input[name="q"]').send_keys("test\n") driver.find_element(By.CSS_SELECTOR, '#pnnext').click() print(driver.page_source) driver.quit()
要素(選択)
ブラウザ操作の流れは、対象となるdom
を取得し、そのdom
に対して操作の繰り返しになる。
自動待機
dom
取得時にそのdom
がまだ生成されれていないことがある。- 対象
dom
が生成されるまで自動で待つよう設定する。 - 設定時間まで待って
dom
が見つからない場合はエラーになる。
# selector auto-wait 30sec driver.implicitly_wait(30)
CSSセレクター
from selenium.webdriver.common.by import By driver.find_element(By.CSS_SELECTOR, 'button').click()
セレクター拡張
セレクターから更にセレクターを呼ぶと、子供のdom
を検索する。
# same as 'div input' driver.find_element(By.CSS_SELECTOR, 'div')\ .find_element(By.CSS_SELECTOR, 'input').click()
複数要素
セレクターが複数要素にマッチする場合がある。
driver.find_element()
は最初に見つかった要素を返すdriver.find_elements()
は見つかった要素全てのlist
を返す
elems = driver.find_elements(By.CSS_SELECTOR, 'a') for elem in elems: print(elem.get_attribute('href'))
要素(操作・属性)
操作
webelement.click()
- クリック
webelement.send_keys(<string>)
- 文字入力
input
の場合value
をセットしてくれる。input
のtype
がfile
の場合、ファイルパスをセットする。
webelement.clear()
- 入力文字列クリア
ラジオボタン・チェックボックス・セレクトのオプションの選択はwebelement.click()
で行う。
スクロール
ウィンドウ内の1番下の要素を取得してwebelement.location_once_scrolled_into_view
を参照すると、その要素が見えるまでスクロールしてくれる。
webelement.location_once_scrolled_into_view
はプロパティなので参照するだけでよい- 内部的には要素に対して
scrollIntoView()
を呼び出している
driver.find_element(By.CSS_SELECTOR, '#bottom-elem').location_once_scrolled_into_view
属性
webelement.text
webelement.get_attribute(<attribute_name>)
- 属性値取得
- 属性例
- name
- id
- value
webelement.is_selected()
- ラジオボタン・チェックボックス・セレクトのオプションの選択状態を取得
例(オプション)
# inverse option options = driver.find_elements(By.CSS_SELECTOR, 'select option') for option in options: if option.is_selected(): option.click()
ナビゲーション
遷移
driver.get(<url>)
driver.refresh()
driver.quit()
- ブラウザを終了する時は必ず呼び出す
ページロード完了は、driver.find_element()
を使って、ページの特定要素が見つかったかで検知する。
属性
driver.title
driver.current_url
driver.page_source
フレーム
driver
は1つのフレームに紐づくhtml
内にあるiframe
の中の要素を操作するには、driver.switch_to.frame(<frame_elem>)
で、driver
が指すフレームを切り替える- 元のフレームに戻すには
driver.switch_to.default_content()
を使う
frame = driver.find_element(By.CSS_SELECTOR, "iframe") driver.switch_to.frame(frame) driver.find_element(By.CSS_SELECTOR, "button").click() driver.switch_to.default_content()
タブ(window)
<a target="_blank">
で新しいタブ(window)が開く場合の扱い方。
- タブが作成されるとwindowが生成される。つまり、
tab
とwindow
は同じ driver
の操作対象は1つのwindow
- タブ一覧は
driver.window_handles
にある - 現在の
window_handle
はdriver.current_window_handle
で取得する driver
の操作対象のwindow
を切り替えるにはdriver.switch_to.windoe(<window_handle>)
を使う
例
新しく開かれたタブ内の要素を操作し、その後元のタブに戻って再度要素を操作する。
- タブが生成され
window_handle
の個数が増えるまで待つ - タブが生成される前後の
window_handle
一覧の差分から、新しいタブのwindow_handle
を取り出す
from selenium.webdriver.support import expected_conditions as EC handle_count = len(driver.window_handles) before_handles = driver.window_handles original_handle = driver.current_window_handle # open new tab driver.find_element(by=By.CSS_SELECTOR, value='#new_tab').click() # wait for new window created WebDriverWait(driver, 30).until( EC.number_of_windows_to_be(handle_count+1)) # find new window handle after_handles = driver.window_handles diff_handles = list(set(after_handles) ^ set(before_handles)) # switch new tab driver.switch_to.window(diff_handles[0]) driver.find_element(By.CSS_SELECTOR, 'button').click() # switch original tab driver.switch_to.window(original_handle) driver.find_element(By.CSS_SELECTOR, 'button').click()
accept-language
Seleniumの機能ではできない。ブラウザの起動オプションで設定する。
options = webdriver.ChromeOptions() options.add_experimental_option('prefs', {'intl.accept_languages': 'ja'}) driver = webdriver.Chrome( ChromeDriverManager().install(), options=options, )
ブラウザの状態保存
通常、ブラウザは毎回新しい状態で立ち上がる。
ブラウザの起動オプションで、ユーザーデータ保存先を指定することにより、ブラウザの状態を保存し再利用できるようになる。
多要素認証のページを開く場合などに用いる。
options = webdriver.ChromeOptions()
options.add_argument('--user-data-dir=<user_data_dir>')
driver = webdriver.Chrome(
ChromeDriverManager().install(),
options=options,
)
ファイルダウンロード
ファイルダウンロードは、Seleniumの機能だけではできず、ブラウザ毎に固有の実装が必要になる。
Seleniumで出来ないこと
- ダウンロードしたファイルのパスの取得
- ダウンロード完了の状態取得
手順
Google Chrome
- ブラウザの起動オプションでダウンロード先のフォルダを指定する
- ダウンロード先のファイルの更新をウォッチして、ダウンロード完了とダウンロードされたファイルのパスを取得する
- ファイル更新検知にwatchdogを使用
- ダウンロード時にテンポラリファイルが作られるので検知対象から除外する
from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from webdriver_manager.chrome import ChromeDriverManager from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler import os import re import time class ChromeDownloadHandler(FileSystemEventHandler): def __init__(self, observer): self.result = None self.observer = observer self.tmpfiles = \ [re.compile('^\.com\.google\.Chrome\..+$'), re.compile('^.+\.crdownload$')] def on_closed(self, event): if event.is_directory == True: return basename = os.path.basename(event.src_path) for tmpfile in self.tmpfiles: if re.match(tmpfile, basename) != None: return self.result = event.src_path self.observer.stop() # setup browser download_dir = os.path.abspath('./download') options = webdriver.ChromeOptions() options.add_experimental_option('prefs', { 'download.default_directory': download_dir, }) driver = webdriver.Chrome( service=Service(ChromeDriverManager().install()), options=options, ) driver.implicitly_wait(30) # setup watchdog observer = Observer() handler = ChromeDownloadHandler(observer) observer.schedule( handler, download_dir, recursive=False ) observer.start() # file download driver.get('https://the-internet.herokuapp.com/download') driver.find_element(By.CSS_SELECTOR, '#content a').click() # wait for file download finish try: while observer.is_alive(): time.sleep(1) except KeyboardInterrupt as e: observer.stop() observer.join() raise (e) # file download finished observer.join() # print download file path print(handler.result) driver.quit()
ファイルアップロード
フォーム
<input type="file" />
にアップロードするファイルの絶対パスをwebelement.send_key()
で設定する
driver.find_element(By.CSS_SELECTOR, 'input[type="file"]').send_key(<full_file_path>) driver.find_element(By.CSS_SELECTOR, 'input[type="submit"]').click()
Seleniumでできないもの
下記のタイプのファイルアップロードはSeleniumの機能だけでは再現できない。
- クリックするとファイル選択ダイアログが出て、ファイル選択してアップロード
- ファイルをドロップしてアップロード
これらの場合、ページ内に非表示のフォームが設置されている場合があるので、前述のフォームと同じ方法を試してみる。
JavaScript
基本
driver.execute_script()
でブラウザ上でJavaScriptを実行できるreturn
で戻り値を返すことができる- 戻り値は、スカラー、もしくはJSON、もしくはブラウザ上でのJavaScriptのオブジェクトへの参照
- 戻り値がJSONの場合は
dict
になる
res = driver.execute_script(""" return 'abc'; """) # abc print(res) res = driver.execute_script(""" return { val1: 123, val2: 'abc' }; """) # {'val1': 123, 'val2': 'abc'} print(res)
引数
- JavaScript文の後に引数を置くと、JavaScriptに引数を渡すことができる
- 値はJavaScript側の
arguments
配列に格納される
res = driver.execute_script(""" return { val1: arguments[0], val2: arguments[1] }; """, 123, 'abc') # {'val1': 123, 'val2': 'abc'} print(res)
JavaScriptオブジェクトの参照
- 戻り値にJavaScriptオブジェクトを渡した時は、JavaScriptオブジェクトへの参照が戻る
- JavaScriptオブジェクトへの参照を
driver.execute_script()
に渡すと、JavaScript内で参照できる
例
html
<input name="test" value="before" />
code
# print->"before" print(driver.find_element(By.CSS_SELECTOR, 'input[name="test"]').get_attribute('value')) obj = driver.execute_script(""" return document.querySelector('input[name="test"]'); """) # print->"<selenium.webdriver.remote.webelement.WebElement (session="...", element="...")>" print(obj) driver.execute_script(""" arguments[0].value = 'after'; """, obj) # print->after print(driver.find_element(By.CSS_SELECTOR, 'input[name="test"]').get_attribute('value'))
driver.find_element()
driver.find_element()
で返されるのもJavaScriptオブジェクトへの参照なので、前述例と同様、driver.execute_script()
の引数に渡して、JavaScript内で参照できる
# print->"before" print(driver.find_element(By.CSS_SELECTOR, 'input[name="test"]').get_attribute('value')) obj = driver.find_element(By.CSS_SELECTOR, 'input[name="test"]') # print->"<selenium.webdriver.remote.webelement.WebElement (session="...", element="...")>" print(obj) driver.execute_script(""" arguments[0].value = 'after'; """, obj) # print->after print(driver.find_element(By.CSS_SELECTOR, 'input[name="test"]').get_attribute('value'))
ドキュメント
- https://www.selenium.dev/
- 公式ページ
- ドキュメントのWebDriverをざっくり読めばSeleniumの基本的な使い方は分かる
- https://www.selenium.dev/selenium/docs/api/py/index.html
- リファレンス
- このページを起点に、クラス・関数・変数・引数を調べる
- リファレンスで特によく参照するページ
- https://www.selenium.dev/selenium/docs/api/py/webdriver_remote/selenium.webdriver.remote.webdriver.html
driver
のリファレンスdriver
の関数とその引数の説明が載っている
- https://www.selenium.dev/selenium/docs/api/py/webdriver_remote/selenium.webdriver.remote.webelement.html
webelement
のリファレンスwebelement
の関数とその引数の説明が載っている
- https://www.selenium.dev/selenium/docs/api/py/webdriver_remote/selenium.webdriver.remote.webdriver.html
リファレンスを見る際の注意
基底クラスが記載されていない
[source]
をクリックして、ソースを見て調べる。
例えば、selenium.webdriver.chrome.webdriver
の基底クラスは、クラスの定義がclass WebDriver(ChromiumDriver):
なので、ChromiumDriver
となるのが分かる。
ChromiumDriver
はどこにあるかは、import
を見るとfrom selenium.webdriver.chromium.webdriver import ChromiumDriver
なので、selenium.webdriver.chromium.webdriver
にあるのが分かる。
基底クラスの関数・変数が、派生クラスのリファレンスからは分からない
[source]
で地道に基底クラスを見つけ、そのリファレンスを辿っていく。
感想など
Chromeのヘッドレスモードで動かすと挙動が異なるサイトがあるので、ヘッドレスモードの使用は避けた方がいいかも。