Pythonを使ったWebブラウザ自動化において、多くのエンジニアを悩ませるのが「StaleElementReferenceException」というエラーです。
このエラーは、取得済みの要素がDOM(Document Object Model)から切り離され、参照が無効になった瞬間に発生します。
近年のモダンなWebアプリケーションでは、ReactやVue.jsなどのフレームワークによりページの一部が頻繁に更新されるため、この問題の重要性が増しています。
本記事では、2026年現在のWeb開発シーンに合わせた、StaleElementReferenceExceptionを確実に回避するためのリトライ処理の実装方法を詳しく解説します。
StaleElementReferenceExceptionが発生する主な原因
この例外が発生する最大の理由は、「要素を特定した時点」と「その要素を操作する時点」の間に、DOMの状態が変わってしまうことにあります。
例えば、JavaScriptによる非同期通信(Ajax)によってコンテンツの一部が書き換えられた場合、画面上は同じように見えても、内部的なオブジェクト参照は失われてしまいます。
具体的には、以下のようなケースで頻発します。
- SPA(シングルページアプリケーション)で、ページ遷移なしに要素が再描画された。
- JavaScriptのタイマー処理によって、定期的にリストやバナーが更新されている。
- フォームの送信後に、同じページ内でエラーメッセージや成功メッセージが動的に挿入された。
これらの処理が行われると、ブラウザ内では以前の要素(Node)が破棄され、新しい要素が生成されます。
Seleniumは古いNodeを操作し続けようとするため、参照が「Stale(古くなった、腐った)」と判断され、例外をスローするのです。
なぜExplicit Wait(明示的な待機)だけでは不十分なのか
Seleniumには、特定の条件を満たすまで待機するWebDriverWaitという機能が備わっています。
しかし、expected_conditions.presence_of_element_locatedなどの標準的な待機処理だけでは、StaleElementReferenceExceptionを完全に防ぐことはできません。
待機処理によって要素が見つかった直後に、JavaScriptの最終的な描画処理が走って要素が差し替えられる可能性を排除できないからです。
つまり、要素が存在することを確認したコンマ数秒後に、その要素が「古いもの」になってしまうケースが多々あります。
この微小なタイミングのズレを解消するためには、単なる待機ではなく、エラー発生時に要素を再取得する「リトライ(再試行)ロジック」を組み込む必要があります。
安定性を高めるリトライ処理の実装パターン
最も確実な対策は、要素の操作をtry-exceptブロックで囲み、特定の回数だけ再試行を行うラッパー関数を作成することです。
以下に、Pythonを使用した汎用的なリトライ処理の実装例を示します。
from selenium.webdriver.common.by import By
from selenium.common.exceptions import StaleElementReferenceException, NoSuchElementException
import time
def safe_click(driver, by, value, max_retries=3):
"""
StaleElementReferenceExceptionを考慮したクリック処理のリトライ
"""
for i in range(max_retries):
try:
# 要素の再検索を行う
element = driver.find_element(by, value)
element.click()
print(f"Success: 要素 {value} のクリックに成功しました。")
return True
except (StaleElementReferenceException, NoSuchElementException) as e:
# エラーが発生した場合は少し待機してリトライ
print(f"Retry {i+1}: 要素が古い、または見つからないため再試行します... ({e})")
time.sleep(1)
print("Error: 最大リトライ回数を超えました。")
return False
# 使用例
# driver = webdriver.Chrome()
# safe_click(driver, By.ID, "submit-button")
Retry 1: 要素が古い、または見つからないため再試行します... (Message: stale element reference: stale element not found)
Success: 要素 submit-button のクリックに成功しました。
このコードのポイントは、ループの各回で必ず find_element を実行している点です。
例外をキャッチした後に再度検索を行うことで、最新のDOMツリーから有効な要素参照を改めて取得し直しています。
WebDriverWaitと組み合わせた高度なリトライ
Seleniumの組み込み機能であるWebDriverWait自体に、リトライ処理を組み込む方法もあります。
WebDriverWaitは指定した例外を無視して処理を継続する設定が可能なため、よりスマートに記述できます。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import StaleElementReferenceException
def wait_for_element_and_click(driver, locator, timeout=10):
"""
StaleElementReferenceExceptionを無視しながら待機してクリック
"""
# ignored_exceptionsを指定することで、待機中に発生しても処理を中断しない
wait = WebDriverWait(
driver,
timeout,
ignored_exceptions=(StaleElementReferenceException,)
)
try:
element = wait.until(lambda d: d.find_element(*locator))
element.click()
return True
except Exception as e:
print(f"失敗しました: {e}")
return False
# 使用例
# wait_for_element_and_click(driver, (By.CSS_SELECTOR, ".dynamic-btn"))
この手法のメリットは、「要素が表示されるまで待つ」というロジックと「エラー時の再試行」を一つにまとめられることにあります。
ignored_exceptions 引数に StaleElementReferenceException を含めることで、until 内の関数が例外を投げても、タイムアウトまで繰り返し実行を続けてくれます。
リトライ戦略の比較表
紹介した各手法の特性を、以下の表にまとめました。
| 手法 | メリット | デメリット | 推奨シーン |
|---|---|---|---|
| 単純なtry-exceptループ | 実装が直感的で、細かい待機時間を制御しやすい。 | コードが冗長になりやすく、タイムアウトの管理が面倒。 | 特定の重要なボタン操作など。 |
| WebDriverWait (ignored_exceptions) | コードがシンプルで、タイムアウト管理が標準機能で可能。 | 例外の種類が増えると記述が複雑になる。 | モダンなSPAの全般的な操作。 |
| Page Object Modelへの組み込み | 保守性が非常に高く、修正箇所を局所化できる。 | 初期のアーキテクチャ設計に工数がかかる。 | 大規模な自動テストプロジェクト。 |
まとめ
Seleniumによるブラウザ自動化において、StaleElementReferenceExceptionは避けて通れない課題です。
しかし、リトライ処理を適切に実装することで、スクリプトの実行成功率は劇的に向上します。
基本となるのは、「操作に失敗したら要素を再取得して再試行する」というシンプルな考え方です。
プロジェクトの規模に合わせて、独自のラッパー関数を作成するか、WebDriverWaitの機能を拡張するかを選択してください。
安定した自動化スクリプトを構築し、メンテナンスコストの低い運用を実現しましょう。
