立て続けの selenium ネタ。マイブームなの。別のもととなる本解について何も言ってないのに別解とはなんぞや、の巻。
ブラウザのダイナミックなコントロールで常に問題になるのが、人間がボタンを押すなどの操作をすることの模擬をしたなどによる応答結果としての「遷移」を如何にして待つか、ということだ、というのはたぶん異論ないよね。
wget でページを取得して静的ページを取るのと違って、selenium でブラウザをコントロールすることでページを開いた場合には、いわゆる「onload」イベントにまつわる処理が自動で実行されることになるわね。けれども「自動で実行されるぜ、さっすが selenium」てだけでは済まないでしょ、オートメーションのためには、必ずこの「onload による処理の完了」を待つ必要がある。同じく、何らかのボタンクリックを模擬する場合も、状態の遷移完了を待たなければならない。そうしなければ、なにがしかの価値ある処理は書けない。たとえばフルページスクリーンショットを実現するのだって、すべての画像ロードが済むのを待つなどしないといけないわよね。
url が変わる場合には悩まないであろうことは想像つくわね。それとともに、初学者には面倒に感じようと、「なんらかの行為によってなんらかの画面の変化がある」ならば、その変化を「待つ」のもきっと簡単だろうとわかるわけだ。なぜならば、まさに「エレメントが現れるのを待つ、などが可能だから」であり、なおかつ、インターネット検索するとこの例は本当にわんさか出てくるから。まぁこれが「別」でない方の解ね。
別解の方は、暫定措置として書いた「time.sleep」をちゃんとやりたくて探してて見つけて、「意味を理解しないまま使ってみてうまくいった」ヤツで、要約すると「’html’エレメントの id が変化するのを待つ」というアプローチ。これがまぁ非常にスッキリしてて、これが正しくていつでも使える方法だったらとってもありがたいよな、と思ったのだ。これは stackoverflow のこの回答で知った。というかまともに英語も読まずに試したらうまくいっちゃってその場では満足しちゃって、後から気持ち悪くなった、てこと。
この回答者自身がちゃんと説明してくれてた:
the solution relies on the fact that selenium records an (internal) id-number for all elements on a page, including the top-level <html> element. When a page refreshes or loads, it gets a new html element with a new ID.
So, assuming you want to click on a link with text “my link” for example:
この for example で掲示されてるコードはこの問題があって最新の selenium python では動作しない。動く例として書くならこんな感じ:
1 oldid = browser.find_element(By.TAG_NAME, 'html').id
2 # browser.find_element(By.ID, 'mybuttun').click() などなんらかの操作
3 while oldid == browser.find_element(By.TAG_NAME, 'html').id:
4 time.sleep(1)
この投稿者が書いてるコンテキストマネージャを動くように書き換えればかなりハッピーになれるので、是非ご自身で試してもらいたい。
「(internal) id-number for all elements on a page」が我々が期待するタイミングでアップデートされるのか、これは selenium (のドライバの)実装次第、てことだと思うので、どこまで信頼できるのだろうか、というのはないではないけれど、少なくともワタシが試している範囲内ではうまくいってるみたい。
この「別」解の何がありがたいのかってね。ユーザ操作によってダイナミックに innerHTML が変化していく際に、はっきりと特定可能な、とくに id で特定可能なものが登場してくれるような変化をいつでもしてくれるなら、全然 explicit-waits だけでいいわけ。けれども現実の WEB ページのかなり多くは、(人間の目で見ると激変していても)こうした機械処理が変化を判定しやすい変化をしてくれるわけではないってこと。このような場合に、こうやって具体を待つのではなく抽象的な待ちを出来るというのが助かる、てこと。
きゃーすてきー。で話が終われば今回のネタの価値はそんなにはないんだけれど、二つ気になって、その疑問が解消するなら、かなり価値の高いネタであろう、てことな。
一つはこれと implicit wait は同じなのか違うのか、ということと、もう一つはいわゆる「document.readyState」チェックとは同じなのか違うのか、てことね。
implicit wait は全然目的が違うわね。これは、find_element などに対するフックの設定で、似たものに例えるなら socket に対する timeout オプションのようなもの。名前から「明示的に特定要素を待つんではなくて」の意味での「implicit」なのかと思ったがそうではなくて。紛らわしいわよこのネーミング。
readyState の方は良くわからんなぁ。こちらは DOM の話で、html.id は selenium のドライバの実装の話なのよ、それはわかってる。わからんのは、readyState 状態と html.id が変化するタイミングの一致について。これは実際に実験してみないとわからなそうね…。今日はそこまでする気力がないのでやめとく。そのうち試してみるかも。