Seleniumの待機処理、time.sleep()で済ませていませんか?
Webサイトのテスト自動化やスクレイピングでSeleniumを使っていると、頻繁に出会うのが「要素が見つからない」というエラー。ページの読み込みが完了する前に要素を探しにいってしまい、NoSuchElementException
が発生してしまう…というのは、多くの方が経験する「あるある」な悩みですよね。
「とりあえずtime.sleep(5)
を入れておけば動くから…」
そんな風に、固定秒数で処理を待機させるtime.sleep()
で解決している方も多いのではないでしょうか?確かに手軽ですが、この方法は処理の速度や安定性に大きな課題を残します。
実は、Seleniumにはもっとスマートで効率的な待機方法が用意されています。それがWebDriverWait
(明示的な待機)です。
この記事では、Seleniumの3つの待機方法(明示的な待機、暗黙的な待機、固定時間待機)の違いを明確にし、その中でも特に推奨されるWebDriverWait
の基本的な使い方から、現場で役立つ実践的なテクニックまでを分かりやすくご紹介します。待機処理をマスターして、より高速で安定したプログラムを目指しましょう。
Seleniumの3つの待機方法を徹底比較
Seleniumにおける待機方法には、主に3つの種類があります。「明示的な待機(Explicit Wait)」「暗黙的な待機(Implicit Wait)」そして「固定時間待機」です。これらは似ているようで、役割が全く異なります。それぞれの特徴を正しく理解しましょう。
- 明示的な待機 (Explicit Wait):
WebDriverWait
がこれにあたります。「指定した要素が出現するまで」「ボタンがクリック可能になるまで」といった特定の条件が満たされるまで、その場で待機します。条件がクリアされれば即座に次の処理へ移るため、効率的で安定性が高い、最も推奨される方法です。 - 暗黙的な待機 (Implicit Wait):
driver.implicitly_wait(10)
のように設定します。これはWebDriver全体に対する設定で、「find_element
等で要素を探す際に、見つからなければ指定した秒数を上限として待つ」という挙動になります。一度設定すれば以降のコード全体で有効になりますが、特定の複雑な条件(要素が非表示になるのを待つなど)では使えず、デバッグが難しくなることがあるため、Explicit Waitとの併用は推奨されません。 - 固定時間待機 (Fixed Wait):
time.sleep(10)
がこれです。Seleniumの機能ではなく、Pythonの標準機能で、文字通り「何があっても指定した秒数だけ処理を停止」させます。要素がすぐに表示されても時間いっぱい待ち続けるため非効率です。ただし、UIのアニメーション待ちなど、特定の条件がない場面で意図的に間隔を空けたい場合に限定的に使われることもあります。
3つの違いを以下の表にまとめました。
項目 | 明示的な待機 (WebDriverWait) | 暗黙的な待機 (Implicit Wait) | 固定時間待機 (time.sleep) |
---|---|---|---|
主な用途 | 特定の要素や状態をピンポイントで待つ | 要素が見つからない場合、全体的に待機時間を設ける | アニメーション待ちなど、条件がない固定時間の待機 |
待機方法 | 指定した条件が満たされるまで待つ | 要素が見つかるまで、設定した最大時間まで待つ | 指定した秒数、必ず待つ |
メリット | 処理が高速で効率的、コードの安定性が非常に高い | 一度の設定でコード全体に適用される | 実装が最も簡単 |
デメリット | 他の待機よりは記述が少しだけ複雑 | 複雑な条件では待機できず、デバッグが困難になる場合がある | 処理に無駄が多く遅い、環境によって動作が不安定 |
推奨度 | ◎ (基本的に常にこちらを使うべき) | △ (限定的な利用に留めるべき、Explicit Waitとの併用は非推奨) | △ (最終手段、または特定の意図がある場合のみ) |
基本的にはWebDriverWait
(明示的な待機) を使うのが、最も安定して効率的なコードを書くためのベストプラクティスです。
WebDriverWaitの基本的な使い方をマスターしよう
それでは、WebDriverWait
の具体的な使い方を見ていきましょう。基本的な構文は非常にシンプルで、一度覚えてしまえば様々な場面で応用できます。
WebDriverWait
を使うには、まず必要なライブラリをインポートします。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
WebDriverWait
本体、待機条件を指定するexpected_conditions
(慣例としてEC
という別名をつけます)、そして要素の検索方法を指定するBy
の3つが基本セットです。
基本的な構文は以下のようになります。
# WebDriverWaitのインスタンスを作成
# driver: WebDriverのインスタンス
# 10: 最大待機時間(秒)
wait = WebDriverWait(driver, 10)
# 指定した要素(IDが'my-element')が出現するまで待機
element = wait.until(EC.presence_of_element_located((By.ID, 'my-element')))
WebDriverWait(driver, 10)
で、「driverに対して最大10秒間待機する」という設定のインスタンスを作成。そして、.until()
メソッドの引数に「どんな条件で待つか」を指定する流れです。
この例で使っているEC.presence_of_element_located()
は、「指定した要素がDOM上に出現するまで待つ」という最も基本的な条件の一つ。このexpected_conditions
には、他にも様々な条件が用意されており、これらを使い分けることがWebDriverWait
を使いこなす鍵となります。
【コピペで使える】expected_conditionsの便利な条件10選
expected_conditions
には、様々な状況に対応できる便利な待機条件が数多く用意されています。ここでは、特に使用頻度が高く、覚えておくと非常に役立つ10個の条件を、具体的なコード例と共に紹介します。
1. presence_of_element_located
指定した要素がDOM上に出現するまで待機します。要素が非表示でも、DOMに存在すればOKです。
element = wait.until(EC.presence_of_element_located((By.ID, 'my-element')))
2. visibility_of_element_located
指定した要素がDOMに出現し、かつ画面上で見える状態(表示されている状態)になるまで待機します。
element = wait.until(EC.visibility_of_element_located((By.XPATH, '//button[@class="submit"]')))
3. element_to_be_clickable
指定した要素が表示され、かつクリック可能になるまで待機します。ボタンやリンクのクリック前に最適です。
button = wait.until(EC.element_to_be_clickable((By.NAME, 'login_button')))
button.click()
4. text_to_be_present_in_element
指定した要素内に、特定のテキストが含まれるまで待機します。非同期でテキストが更新される箇所で役立ちます。
wait.until(EC.text_to_be_present_in_element((By.ID, 'status-message'), '完了'))
5. alert_is_present
JavaScriptのアラートが表示されるまで待機します。
alert = wait.until(EC.alert_is_present())
alert.accept() # アラートをOKする
6. frame_to_be_available_and_switch_to_it
iframeが出現し、そのiframeに切り替わるまで待機します。
wait.until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'my-iframe')))
7. invisibility_of_element_located
指定した要素が見えなくなる(非表示になる)まで待機します。ローディング画面が消えるのを待つ場合などに使えます。
wait.until(EC.invisibility_of_element_located((By.ID, 'loading-spinner')))
8. presence_of_all_elements_located
指定した条件に一致するすべての要素がDOM上に出現するまで待機します。検索結果のリストなどを取得する前に便利です。
items = wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, 'list-item')))
9. title_contains
ページのタイトルに、指定した文字列が含まれるまで待機します。ページ遷移の確認に使えます。
wait.until(EC.title_contains('ダッシュボード'))
10. url_changes
現在のURLから変更されるまで待機します。ログイン後のリダイレクトなどを待つ際に使用します。
wait.until(EC.url_changes('[https://example.com/login](https://example.com/login)'))
これらの条件を使い分けることで、ほとんどの待機処理はスマートに実装できるはずです。
タイムアウト発生!TimeoutExceptionの原因と対処法
WebDriverWait
を使っていても、指定した時間内に条件が満たされなければTimeoutException
というエラーが発生します。これは「待ったけど、結局ダメでした」という合図です。このエラーに遭遇した際の、主な原因と対処法を見ていきましょう。
主な原因
- セレクタが間違っている:
By.ID
やBy.CLASS_NAME
で指定した値が、そもそもHTML上に存在しない、またはタイポしているケース。これが最も多い原因です。開発者ツールでセレクタが正しいか再確認しましょう。 - 待機時間が短すぎる: ネットワークが遅い、あるいは処理が重いサイトの場合、指定した最大待機時間内に要素が表示されないことがあります。少し長めに設定して試してみるのも一つの手です。
- そもそも要素が出現しない: 操作の前提条件が間違っていて、待っている要素が表示されるフローになっていない可能性もあります。例えば、ログインに失敗しているのにログイン後のページ要素を待っている、などです。
- iframe内に要素がある: 操作したい要素がiframeの中にある場合、先にそのiframeにコンテキストを切り替えないと要素を見つけることはできません。
対処法:try-exceptで例外処理
TimeoutExceptionが発生してもプログラムを停止させず、特定の処理を行いたい場合があります。例えば、「ポップアップが表示されたら閉じる、表示されなければ何もしない」といったケースです。このような場合は、try-except構文を使って例外を捕捉します。
from selenium.common.exceptions import TimeoutException
try:
# 5秒以内に広告ポップアップが表示されたら閉じる
close_button = WebDriverWait(driver, 5).until(
EC.element_to_be_clickable((By.ID, 'ad-close-button'))
)
close_button.click()
print("広告ポップアップを閉じました。")
except TimeoutException:
# 5秒以内に表示されなければ、何もしない
print("広告ポップアップは表示されませんでした。")
# 次の処理へ
# ...
このようにtry-except
で囲むことで、TimeoutException
をエラーとしてではなく、処理の分岐条件として利用できます。これにより、様々な表示パターンに対応できる、より堅牢なプログラムを作成することが可能になります。
【応用編】lambda式や関数で独自の待機条件を作る
expected_conditions
に用意されている条件だけでは対応できない、もっと複雑な条件で待機したい場合もあります。そんな時は、自分で待機条件をカスタマイズすることが可能です。
.until()
メソッドは、引数として受け取った関数を定期的に実行し、その戻り値がFalse
やNone
などでなくなる(Trueと評価される)まで待機を続けます。この仕組みを利用して、独自の待機条件を作りましょう。
Python 3.8以降の場合:lambdaとセイウチ演算子
Python 3.8で導入された代入式(セイウチ演算子 :=
)を使うと、lambda
式の中で効率的にカスタム条件を記述できます。
例えば、「特定のクラス名を持つ要素が3つ以上になったら次に進む」という条件は、以下のように書けます。
# 'item'というクラス名を持つli要素が3つ以上になるまで最大10秒待機
items = WebDriverWait(driver, 10).until(
lambda d: (elements := d.find_elements(By.CLASS_NAME, 'item')) if len(elements) >= 3 else None
)
print(f"{len(items)}個のアイテムが見つかりました。")
この方法では、find_elements
の実行結果を変数elements
に代入しつつ、その数を評価しています。これにより、要素の検索を1回で済ませることができ、非常に効率的です。
Python 3.7以前の場合:関数を定義する
セイウチ演算子が使えない古いバージョンのPythonでは、通常の関数を定義する方法が安全で分かりやすいです。
# 待機条件を判定する関数を定義
def wait_for_items(driver, min_count=3):
elements = driver.find_elements(By.CLASS_NAME, 'item')
# 指定した数以上の要素が見つかれば、その要素リストを返す
# 見つからなければNoneを返し、待機を継続させる
return elements if len(elements) >= min_count else None
# 作成した関数をuntilに渡す
items = WebDriverWait(driver, 10).until(wait_for_items)
print(f"{len(items)}個のアイテムが見つかりました。")
このように関数を外に定義することで、lambda
式でfind_elements
を2回呼び出してしまう非効率なコードを避けられます。コードの再利用性も高まるため、非常に良い書き方です。
初心者必見!グローバル変数とスコープの違いと使い分けを徹底解説【Python・JavaScript・C対応】
まとめ:WebDriverWaitを使いこなして安定した自動化を実現しよう
この記事では、Seleniumにおける3種類の待機処理の違いを明確にし、その中でも最も推奨されるWebDriverWait
の基本的な使い方から応用テクニックまでを解説しました。
この記事のポイント
- Seleniumの待機には明示的な待機(
WebDriverWait
)、暗黙的な待機(implicitly_wait
)、固定時間待機(time.sleep
)の3種類がある。 - 基本的には、最も高速で安定した
WebDriverWait
を使うことが推奨される。 expected_conditions
には便利な待機条件が豊富に用意されており、状況に応じて使い分けることが重要。TimeoutException
はtry-except
で捕捉することで、柔軟なエラーハンドリングが可能になる。lambda
式や通常の関数を使えば、独自の複雑な待機条件も効率的に作成できる。
不安定な待機処理は、自動化プログラムが失敗する大きな原因の一つです。それぞれの待機方法の特徴を正しく理解し、特にWebDriverWait
を使いこなすことで、あなたの書くSeleniumのコードは格段に安定し、信頼性の高いものになるはずです。ぜひ、今日からWebDriverWait
を活用してみてください。