こんにちは、Heywaです。
PythonとSeleniumを使ったブラウザ自動化において、最も頭を悩ませる問題の一つが「要素の読み込み待ち」です。スクリプトが早すぎて要素が見つからずエラーになる、かといって長めに待つと全体の処理時間が間延びしてしまう…。
特に最近のWebサイトはJavaScriptで非同期にコンテンツを読み込むSPA(Single Page Application)が主流のため、この「待機処理」をどう設計するかが、自動化システムの安定性を左右する最大の鍵となります。
今回は、Seleniumの待機処理の決定版であるWebDriverWait(明示的待機)について、Claude Codeで生成した実用的なスクリプト実例とともに完全マスターを目指します。NoSuchElementException に悩まされている方は必見です。
Seleniumの3つの待機処理:なぜWebDriverWaitなのか?
Seleniumで要素の読み込みを待つ方法には、大きく分けて3つのアプローチがあります。システム思考的に、それぞれのメリット・デメリットを整理してみましょう。
| 待機方法 | 特徴 | メリット | デメリット |
|---|---|---|---|
| time.sleep() | 指定した秒数だけ無条件に処理を停止する | コードがシンプルで直感的 | 読み込みが早くても指定秒数待つため非効率。遅いとエラーになる。 |
| implicitly_wait() | 要素が見つかるまで最大指定秒数待つ(暗黙的待機) | 一度設定すれば全体に適用される | 複雑な条件(クリック可能になるまで等)には対応できない。 |
| WebDriverWait | 特定の条件を満たすまで最大指定秒数待つ(明示的待機) | 条件が満たされた瞬間に次へ進むため最速。柔軟性が高い。 | コードの記述量が少し増える。 |
結論から言うと、実運用に耐えうる堅牢なスクリプトを書くなら、WebDriverWait(明示的待機)一択です。time.sleep() はデバッグ時のみの使用に留め、本番環境では絶対に避けるべきです。
WebDriverWaitの基本と NoSuchElementException 対策
NoSuchElementException は、「スクリプトが要素を探しに行ったタイミングで、まだDOM上にその要素が存在しなかった」場合に発生します。これを防ぐための基本形が以下です。
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# 最大10秒間待機するWebDriverWaitオブジェクトを作成
wait = WebDriverWait(driver, 10)
# IDが 'submit-btn' の要素がDOM上に現れるまで待機
element = wait.until(EC.presence_of_element_located((By.ID, 'submit-btn')))
このコードの素晴らしい点は、「要素が1秒で現れれば1秒で次の処理に進む」ということです。無駄な待ち時間が発生しません。
よく使う expected_conditions (EC) カタログ
WebDriverWaitの真骨頂は、expected_conditions(EC)と組み合わせることで、様々な「状態」を待機できる点にあります。私がよく使う条件をまとめました。
EC.presence_of_element_located: 要素がDOM上に存在するまで待つ(見えていなくてもOK)EC.visibility_of_element_located: 要素が画面上に表示されるまで待つ(高さと幅が0より大きい)EC.element_to_be_clickable: 要素が表示されており、かつクリック可能になるまで待つ(ボタン操作前に必須)EC.text_to_be_present_in_element: 特定の要素内に、指定したテキストが含まれるまで待つ(ローディング完了の判定などに便利)
Claude Codeで生成:指数バックオフ付き最強待機スクリプト
ここからは実践編です。単にWebDriverWaitを使うだけでなく、ネットワークの瞬断やサーバーの一時的な高負荷にも耐えられるよう、「指数バックオフ(Exponential Backoff)を伴うリトライ処理」を組み込んだ最強の待機関数を作ります。
ターミナルでClaude Codeを起動し、以下のように指示を出して生成させたコードをベースにしています。
「PythonとSeleniumで、指定した要素がクリック可能になるまで待機し、クリックする関数を書いて。WebDriverWaitを使用し、TimeoutExceptionが発生した場合は指数バックオフ(1秒、2秒、4秒…)で最大3回リトライする堅牢な設計にして。」
import time
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.common.exceptions import TimeoutException, ElementClickInterceptedException
def click_with_retry(driver, by_locator, max_retries=3, base_timeout=5):
"""
指数バックオフを用いた堅牢なクリック処理関数
:param driver: WebDriverインスタンス
:param by_locator: (By.ID, 'element-id') のようなタプル
:param max_retries: 最大リトライ回数
:param base_timeout: 1回あたりの基本待機時間(秒)
"""
for attempt in range(max_retries):
try:
# 試行回数に応じてタイムアウト時間を少しずつ延ばすのも有効
current_timeout = base_timeout + attempt
wait = WebDriverWait(driver, current_timeout)
print(f"要素のクリックを試行中... ({attempt + 1}/{max_retries})")
# 要素がクリック可能になるまで待機
element = wait.until(EC.element_to_be_clickable(by_locator))
# クリック実行
element.click()
print("クリックに成功しました。")
return True
except TimeoutException:
print(f"TimeoutException: 要素が時間内にクリック可能になりませんでした。")
except ElementClickInterceptedException:
print(f"ElementClickInterceptedException: 別の要素が重なっています。")
# 重なっている要素を消すためのJavaScript実行などをここに挟むとさらに強力
# 最後の試行で失敗した場合は例外を投げる
if attempt == max_retries - 1:
raise Exception(f"最大リトライ回数({max_retries})に達しました。対象: {by_locator}")
# 指数バックオフによる待機(1秒 -> 2秒 -> 4秒...)
sleep_time = 2 ** attempt
print(f"{sleep_time}秒待機してからリトライします...")
time.sleep(sleep_time)
# 使用例
# click_with_retry(driver, (By.ID, 'login-button'))
まとめ:待機処理を制する者が自動化を制す
Seleniumを使った自動化スクリプトが「たまに落ちる」原因の9割は、この待機処理の甘さにあります。
time.sleep() の誘惑を断ち切り、WebDriverWait(明示的待機)と適切な例外処理(リトライ)を組み合わせることで、スクリプトの安定性は劇的に向上します。私のボートレース予測データ収集システムが2年間無停止で動いているのも、この設計のおかげです。
Claude Codeを使えば、このような少し複雑なロジックも一瞬で生成してくれます。皆さんもぜひ、この「最強の待機関数」を自分のプロジェクトに組み込んで、ストレスフリーな自動化ライフを手に入れてください。
