【Excel VBA×Selenium】StaleElementReferenceエラーの原因と対処法|要素を再取得する方法

みなさんこんにちは。

Excel VBA×SeleniumでWebページを操作していると、要素を正しく取得できていたはずなのに、クリックや画面更新のあとで突然エラーになることがあります。

そのときに表示されることがあるのが、StaleElementReferenceに関するエラーです。

XPathやidが間違っているわけではないのに、少し前まで操作できていたWebElementが急に使用できなくなるため、原因が分かりにくいエラーかと思います。

今回は、StaleElementReferenceエラーが発生する仕組みと、DOM更新後に要素を再取得する方法、ループ処理で要素を持ち回らない書き方、待機と組み合わせる方法についてご紹介したいと思います。


この記事はこんな人におすすめ

  • VBA×SeleniumでStaleElementReferenceエラーが発生している人
  • 取得済みのWebElementがクリック後に使えなくなる人
  • JavaScriptで画面が動的に更新されるWebサイトを操作している人
  • ループ処理中に同じWebElementを繰り返し使用している人
  • 要素の再取得と待機処理の考え方を知りたい人

StaleElementReferenceエラーとは

Seleniumでは、FindElementByIdやFindElementByXPathなどで要素を取得すると、その時点でWebページ上に存在している要素への参照をWebElementとして保持します。

しかし、ページの再読み込みやJavaScriptによるDOMの更新によって、その要素が削除されて新しい要素へ置き換わることがあります。

画面上では同じボタンに見えていても、Seleniumから見ると「以前取得した要素」と「DOM更新後の要素」は別のものです。このように、以前取得したWebElementが現在のDOMから切り離されて参照できなくなった状態をstaleと呼びます。

Selenium公式ドキュメントでも、ページ更新やDOMの動的変更、別ページへの移動、iframeなどのコンテキスト変更によってStaleElementReferenceExceptionが発生する可能性があると説明されています。

StaleElementReferenceエラーが発生する例

Dim elm As Selenium.WebElement

'現在のDOMから要素を取得する
Set elm = driver.FindElementById("btnSearch")

'クリックによってDOMが更新される
elm.Click

driver.Wait 1000

'更新前に取得したelmを再利用する
elm.Click

クリックによって対象ボタンがDOM上で作り直された場合、以前取得した elm は現在のDOM上の要素を参照していないため、StaleElementReferenceエラーになる場合があります。

Excel VBA×SeleniumでDOM更新前のWebElementを再利用してStaleElementReferenceエラーが発生した画面

対処方法は要素を再取得する

基本的な対処方法は、DOMが更新されたあとに対象となる要素をもう一度取得することです。

Dim elm As Selenium.WebElement

Set elm = driver.FindElementById("btnSearch")

elm.Click

driver.Wait 1000

'DOM更新後に要素を再取得する
Set elm = driver.FindElementById("btnSearch")

elm.Click

同じidを持つボタンが画面上に表示されていたとしても、DOMが更新されたのであれば更新後の要素を新しく取得し直します。

Selenium公式でも、DOM変更によって要素がstaleになった場合は、現在のDOMから対象要素を再取得する方法が基本的な対処として説明されています。

Excel VBA×SeleniumでDOM更新後にWebElementを再取得して正常に操作できた画面

WebElementを長時間保持しない

動的に変化するWebページでは、WebElementを早い段階で取得して長時間保持するコードはStaleElementReferenceエラーが発生しやすくなります。

'先に要素を取得する
Set elm = driver.FindElementByXPath("//button[@id='btnNext']")

'この間にDOM更新が発生する処理
driver.FindElementById("btnSearch").Click
driver.Wait 1000

'以前取得した要素を使用する
elm.Click

この場合は、実際に操作する直前に要素を取得する方が安全です。

driver.FindElementById("btnSearch").Click

driver.Wait 1000

'操作する直前に現在のDOMから取得する
driver.FindElementByXPath("//button[@id='btnNext']").Click

ループ内でWebElementを持ち回らない

最初にFindElementsで複数要素を取得して保持したあと、クリックのたびにDOMが更新されるWebページでもStaleElementReferenceエラーが発生しやすくなります。

Dim elements As Selenium.WebElements
Dim elm As Selenium.WebElement

Set elements = driver.FindElementsByCss(".item-button")

For Each elm In elements

    elm.Click
    driver.Wait 500

Next

最初に取得したelementsは、DOMが更新されても自動的に最新の要素一覧へ置き換わるわけではありません。

そのため、クリックのたびにDOMが更新されるページでは、ループ内で対象要素を取得し直します。

Dim i As Long
Dim elm As Selenium.WebElement

For i = 1 To 10

    Set elm = driver.FindElementByXPath( _
        "(//button[contains(@class,'item-button')])[" & i & "]")

    elm.Click
    driver.Wait 500

Next i

なお、クリックによって要素数や並び順が変わるページでは、商品IDやdata属性など、クリック後も対象を特定できる値を使って要素を取得する方法も検討してください。

待機してから要素を再取得する

クリック直後にDOMが更新されるページでは、すぐに再取得すると新しい要素がまだ作成されていない場合があります。そのため、要素の再取得と待機処理を組み合わせると安定しやすくなります。

Selenium公式には要素がstaleになったことや、要素が存在することを待つExpected Conditionがあります。一方、SeleniumBasicを使用するVBAでは同じAPIをそのまま利用できない場合があるため、Waitや要素取得のリトライ処理を組み合わせる方法も使いやすいかと思います。

Dim elm As Selenium.WebElement
Dim count As Long

For count = 1 To 50

    Set elm = Nothing

    On Error Resume Next
    Set elm = driver.FindElementById("btnSearch")
    On Error GoTo 0

    If Not elm Is Nothing Then
        Exit For
    End If

    driver.Wait 100

Next count

If elm Is Nothing Then

    MsgBox "要素を取得できませんでした。", vbExclamation
    Exit Sub

End If

elm.Click

このコードでは、0.1秒ごとに現在のDOMから要素を取得し直し、最大50回確認します。

要素を取得できるまでループする方法については、過去の記事でもご紹介しています。

iframeを切り替えた場合にも注意する

StaleElementReferenceエラーはDOM更新だけでなく、操作対象となるiframeやウィンドウなどのコンテキストが変わった場合にも発生する可能性があります。

必要なiframeへ切り替えたあと、対象要素を現在のコンテキストから取得し直してください。

iframeの切り替え方法については、以下の記事でも詳しくご紹介しています。

再取得しても直らない場合に確認すること

  • 再取得するタイミングでもDOM更新が続いていないか
  • クリック後の画面読み込みが完了しているか
  • 同じXPathが別の要素を取得していないか
  • 対象要素がiframe内に存在していないか
  • ページ遷移後に古いWebElementを使用していないか
  • FindElementsで取得した要素一覧を長時間保持していないか

StaleElementReferenceエラーでは、「要素が存在するか」だけではなく、そのWebElementが現在のDOMから取得したものなのかという点を意識することが重要です。

よくあるコードと対処方法

状況対処方法
画面更新後に以前のWebElementを使用しているFindElementで再取得する
クリック後にDOMが再描画される待機してから再取得する
FindElementsの結果をループで使い続けているループ内で現在のDOMから取得する
iframeを切り替えた正しいiframeへ切り替えてから再取得する
ページ遷移した遷移先で改めて要素を取得する

参考情報

最後に

いかがでしたでしょうか。

今回は、Excel VBA×Seleniumで発生するStaleElementReferenceエラーの原因と対処法についてご紹介しました。

一度取得したWebElementは、ページ更新やDOMの再描画が行われても自動的に最新の要素へ更新されるわけではありません。

動的に変化するページでは、クリックや画面更新のあとに対象要素を再取得し、ループ処理でもWebElementを長時間保持しないことがポイントになります。

少しでも参考になれば幸いです。


開発依頼について

ココナラでWebスクレイピング開発サービスを出品しております。

自分で開発をしようと思ったけど、VBAでのネット記事が少なく困っている方も多いかと思います。

そんな方はいつでもお気軽にご相談ください。

また、本ブログからご依頼いただいた方については割引特典がございますので、

ご不明点と合わせてメッセージをいただけると幸いです。

Excelにてブラウザ操作自動化ツールを作成します その作業、webスクレイピングを使って自動化しましょう!

Webスクレイピング以外にもWebアプリの開発サービスも出品しております。

こちらについても、本ブログからご依頼いただいた方については割引特典がございますので、

お気軽にご相談ください。

スモールスケールのWebアプリ開発します 先ずはスモールスケールのWebアプリから始めませんか?

ご興味がある方はこちら!!

コメントを残す

メールアドレスが公開されることはありません。 ※ が付いている欄は必須項目です

CAPTCHA