見出し画像

[SeleniumVBA]要素が見つからず困った場合の助け舟


はじめに

インストール不要でありながら高機能なSeleniumVBAを使用すれば、要素が見つからない場合でもあきらめる必要はありません

WEBスクレイピングを行うにあたって、自分自身困った経験を活かし、同様に困っている方を救う一助となれれば、と以下のとおり解決策となるExcelVBAコードを掲載しました。

【推奨】エラートラップの設定

エラー発生時の対応を行いやすくするため、VBEの「ツール」「オプション」「全般」のエラートラップの設定は、下記のとおりとすることを強力にお勧めします。

VBEの「ツール」ー「オプション」の画面
  • 「エラー発生時に中断」にしない理由は、SeleniumVBAのプログラムは至るところに「On Error GoTo 〜」が使用されており、その箇所で中断してしまうため。

  • 「クラスモジュールで中断」にしない理由は、独自に実装するプログラムは標準モジュールで行うことが想定され、クラスモジュールで中断してしまうとデバッグが行いづらくなるため。

ウインドウの切替/フレームの切替

指定は正しいのに要素がないと怒られて苦しんだ末、解決した経験があります。タイトルのついたウインドウが新たに現れたり、HTMLに<iframe>タグが入っている場合、それに該当します。

’【実際にあった事例】
 With driver
   Dim elmfound as WebElement
   Dim cnt As Long
   'ウインドウを切り替える(〇〇はウインドウのタイトル名)
    On Error GoTo Err_Wait
        .Windows.SwitchToByTitle ("〇〇")
    On Error GoTo 0
   'フレームを切り替える(メインフレーム)
   If .IsPresent(By.Xpath, "//iframe[@name='××']", 2000, , elmfound) = False Then Stop
   .SwitchToFrame elmfound
   'フレームを切り替える(サブフレーム)
   If .IsPresent(By.Xpath, "//iframe[@name='△△']",2000, ,elmfound) = False Then Stop
   .SwitchToFrame elmfound
   'ようやくボタンにたどり着いてクリックできた
   If .IsPresent(By.Xpath, "//input[@name='□□']", 2000, , elmfound) = False Then Stop
   elmfound.Click
   Exit Sub

Err_Wait:
    If cnt >= 20 Then Stop '最大1秒待機
    .Wait 50
    cnt = cnt + 1
    Resume
End With

上記のコードは、ウインドウとフレーム(メインとサブ)という3連続の切り替えを行ったことによりボタンがクリックできた事例です。
注意したいのは複数回の切り替えが発生する可能性があることです。

(追記)実務においてWindows.SwitchToByTitleの待機処理が必要であったため「On Error GoTo 〜」での待機処理を入れておりますが、そこまで必要でない方もいらっしゃるかもしれません。

ポップアップウインドウ

(1)モーダルウインドウ

前面にタイトルのない小窓が現れ、Chrome画面で小窓の箇所を右クリックするとメニューが現れる場合は、モーダルウインドウが疑われます。

解決法としては、HTMLに新たなタグや属性が現れるので、その箇所をXpathでキャッチしてあげるとうまくいくと思います。

Dim elmfound as WebElement 

With driver
  '新たなタグや属性(例:class='popoup')が現れた場合は、キャッチしてから検索する
  If .IsPresent(By.Xpath, "//div[@class='popup']//input[@name='〇〇']", 2000, , elmfound) = False Then Stop
  '見つかったボタンをクリック
  elmfound.Click

  '【別の方法】elmPopupとして要素を格納しておくと、ボタンが多い場合汎用性が効く
  Dim elmPopup as WebElement
  '新たなタグや属性(例:class='popoup')が現れた場合は、キャッチ
  If .IsPresent(By.Xpath, "//div[@class='popup']", 2000, , elmPopup) = False Then Stop  
  'ボタン〇〇をクリック
  If .IsPresent(By.Xpath, ".//input[@name='〇〇']", 2000, elmPopup , elmfound) = False Then Stop
  elmfound.Click 
 'ボタン××をクリック
  If .IsPresent(By.Xpath, ".//input[@name='××']", 2000, elmPopup , elmfound) = False Then Stop
  elmfound.Click

End With

Xpathについては、自在に活用できると利用の幅が広がります。以下の記事を御参考にしていただければと思います。

(2)確認ダイアログ

確認ダイアログは、Chrome画面で右クリックしても何も反応がない小窓が該当します。その場合、お決まり文句のSwitchToAlert.Acceptで「OK」にしています。

With driver
  'アラート処理
  If .IsAlertPresent(2000) = False Then Stop
  .SwitchToAlert.Accept
End With

シャドウルート(shadow-root)

(1)事前準備

いよいよ最強のラスボスと対面します。どのような手段を尽くしても要素が見つからない場合は、シャドウルートを疑います。

SeleniumVBAはシャドウルートにも対応しています。まず、以下のとおり大変ですが以下の公式ページを御参照ください。英語ですので翻訳したほうが良いです。
https://github.com/GCuser99/SeleniumVBA/wiki#navigating-shadow-roots

公式ページを見ても意味が分からない人もいるかと思います。今回は、事例として「Chromeの閲覧履歴データ削除ボタン」をクリックできるよう、攻略していきます。

まず、事前準備をします。Chromeブラウザでクリックしたいボタン部分にカーソルにあてながら、右クリック「検証」でデベロッパーツール画面を表示させ、完全な XPath をコピーします。(下図のとおり。図の部分をクリックすると拡大)

Chrome デベロッパーツール画面

その結果、以下のとおりXpathをみると「settings-ui//」のように「//」の部分が複数存在するので、クリックするボタンに行くまでに複数のシャドウルートが存在することになります。

’完全なXpathを貼り付けたもの。//が存在するか確認
/html/body/settings-ui//div[2]/settings-main//settings-basic-page//div[1]/settings-section[5]/settings-privacy-page//settings-clear-browsing-data-dialog//cr-dialog/div[4]/cr-button[2]

また、以下のとおりChrome画面から「F12」を押し、デベロッパーツール上部の歯車ボタン(上記Chromeデベロッパーツール画面を参照)を押すと以下の画面が現れます。

PreferencesタブのElementsの下の「Show user agent shadow DOM」の選択肢にチェックを入れます。

Chromeデベロッパーツールの設定画面

以上の設定をしたのちソース画面を見ますと、以下のとおりクリックしたいボタンが一番下の丸囲み部分となり、その上位層をみると、シャドウルートであることが分かる四角囲みの「#shadow-root(open)」が複数表示されていることが分かります。

このことから、クリックしたいボタンは、シャドウルートの深い階層下にあることが分かるかと思います。

Chromeデベロッパーツール ソース表示

(2)コード実装

そしていよいよ作業に入りますが、以下のコードとおり、SeleniumVBAの機能「GetShadowRoot」を利用して1階層ずつシャドウルートの中に潜っていくことができます。

GetShadowRootを記述したあとには、以下のコードのように、ピリオドでつなげて必ず「FindElement(By.~」で続けることになりますので、「FindElementBy〇〇(~」の形式が使用できません。御注意ください。

また、同様に私が愛用しているIsPresentもGetShadowRootとセットで使用できませんでしたので御注意ください。

それはともかくとして、この作業をボタンに到達するまで繰り返すと、よううやくボタンをクリックすることができ、ミッション終了となります。

’【事例】Chromeの閲覧履歴の削除ボタンをクリックしたい
'完全なXpathをコピーすると以下のとおりとなった
'/html/body/settings-ui//div[2]/settings-main//settings-basic-page//div[1]/settings-section[5]/settings-privacy-page//settings-clear-browsing-data-dialog//cr-dialog/div[4]/cr-button[2]
'上記の「//」の左部を参考にしてシャドウルートは、settings-ui、settings-main、settings-basic-page、settings-privacy-page、settings-clear-browsing-data-dialogであることが推定できる
’============================================
'待機処理エラーになる場合 On Error~で対応
'(GetShadowRootの処理完了前にFindElementが実行されるとエラーになるケース)
’============================================
With driver
  Dim webElem As WebElement
  Dim cnt As Long
  'シャドウルートの階層が深いため、順番にたどっていく必要がある
  .NavigateTo "chrome://settings/clearBrowserData/"
  Set webElem = .FindElement(By.TagName, "settings-ui")
  
  On Error GoTo Err_Wait
    Set webElem = webElem.GetShadowRoot.FindElement(By.TagName, "settings-main")
    Set webElem = webElem.GetShadowRoot.FindElement(By.TagName, "settings-basic-page")
    Set webElem = webElem.GetShadowRoot.FindElement(By.TagName, "settings-privacy-page")
    Set webElem = webElem.GetShadowRoot.FindElement(By.TagName, "settings-clear-browsing-data-dialog")
    'cr-dialogは「/]となっているが、ソースをみるとシャドウルートになっている
    Set webElem = webElem.GetShadowRoot.FindElement(By.TagName, "cr-dialog")
  On Error GoTo 0
  'ようやく「データ削除」ボタンをクリックできた
  Set webElem = webElem.FindElement(By.ID, "clearButton")
  webElem.Click
  Exit Sub

Err_Wait:
    If cnt >= 20 Then Stop '最大1秒待機
    .Wait 50
    cnt = cnt + 1
    Resume
End With

(3)待機処理及び留意事項

GetShadowRootの処理に時間がかかることがあり、処理完了前にFindElementが実行されるとno such shadow rootと表示され、要素が取得できずにエラーになる場合があることが分かりました。暗黙的待機を設定してもGetShadowRootの処理の待機には適用されませんでした。

その対応として「driver.Wait 1000」等、固定秒数の待機をしたくない方向けに、「On Error GoTo 〜」で最大1秒の待機処理を入れています。

また留意事項として、シャドウルート内で検索する際のお約束があり、便利なBy.Xpathが使用できなくなりますので、それ以外で代用することが必要となります。

今回の例では、By.TagNameでシャドウルートを特定し、By.IDでクリックしたいボタンを特定しています。もし、シャドウルート内でも複雑な検索をしたい場合は、By.CssSelectorが使えるのでBy.Xpathの代用になると思います(私は使ったことないですが・・)。

終わりに

最後のシャドウルートはなかなか手ごわいものでしたが、どうでしたでしょうか。SeleniumVBAが持っているポテンシャルを活かして、もっと利用する人が増えるといいな、と思っています。

この記事が気に入ったらサポートをしてみませんか?