QR Send

スマホとPCのChrome間で、URLやテキストをQRコード1回のペアリングで送り合える自作Chrome拡張。アカウント不要・無料。

使ってみる →

Ruby+Seleniumでよく出るエラーと対処法まとめ(保存版)

Selenium・自動化

Ruby+Seleniumで自動化を書いていると、同じ種類のエラーに何度も出会う

Ruby+Seleniumでスクレイピングや自動操作のスクリプトを書き続けていると、対象のサービスがまったく違っても、遭遇するエラーの種類はある程度パターン化されてきます。ChromeDriverのバージョン不一致、クリックが弾かれる、要素の参照が失効する、要素が見つからない、iframeや別タブに阻まれる、ヘッドレス環境特有の崩れ、driver.getそのものが失敗する、長時間動かすと落ちる。どれも一度は踏んだことがある、という方も多いはずです。

この記事では、それぞれのエラーを症状別に整理し、原因と対処の要点だけを簡潔にまとめます。詳しい実装は各エラーごとの詳細記事にリンクしているので、今まさに同じエラーに当たっている方は、該当する見出しから詳細記事に飛んでください。

ChromeDriverのバージョン不一致

ある日突然、session not created: This version of ChromeDriver only supports Chrome version ...というエラーでスクリプトが動かなくなります。原因はシンプルで、Chromeは自動更新でどんどんバージョンが上がるのに、手元に固定したChromeDriverのバイナリだけが取り残されるためです。対処は、エラーメッセージの文字列判定で不一致を検知し、Chrome for TestingのJSON APIからインストール済みChromeに対応するChromeDriverを特定して自動更新→リトライする仕組みを組み込むことです。詳細はChromeDriverのバージョン不一致を自動修復するで解説しています。

element click intercepted

昨日まで問題なく動いていたelement.clickが、element click interceptedで急に失敗するようになることがあります。原因は一つではなく、通知バナーやモーダルが上に被っている、要素がまだビューポート外にある、アニメーション中で座標がずれている、要素がまだ操作可能な状態になっていない、などが絡み合っています。単一の対策では潰しきれないため、オーバーレイ除去とスクロール待機を前処理とし、JSクリック→ActionChains→座標クリック→通常クリックの順で試す4段フォールバックを組むのが現実的な対処です。詳細はelement click interceptedをフォールバック式クリックで解消するにまとめています。

StaleElementReferenceError

stale element reference: element is not attached to the page documentは、要素そのものが消えたわけではなく、一覧の絞り込みや非同期の再描画によって裏側のDOMノードが作り直され、保持していた要素の参照が無効になることで起きます。要素を変数に握って使い回すのがアンチパターンで、対処の基本は「使う直前に毎回findし直す」ことです。さらに、取得と操作をまとめてブロックにし、失効を検知したらブロックごとやり直すリトライヘルパーを用意すると安定します。ループで一覧をまとめて取得している場合は、配列を使い回さずインデックスやdata-item-idのような一意な識別子で毎回再取得するのがポイントです。詳細はStaleElementReferenceErrorを再取得パターンで根絶するを参照してください。

要素が見つからない・待機不足によるフレーキーな失敗

NoSuchElementErrorが実行するたびに出たり出なかったりする、いわゆるフレーキーな失敗の多くは、sleep(3)のような固定待機に頼っていることが原因です。ページが軽ければ無駄に待ち、重ければ待ち時間が足りずタイムアウトします。対処はWebDriverWait#untilで「何秒待つか」ではなく「何が成り立てば進んでよいか」を明示的に書くことです。標準の条件で足りない場合は、無視する例外とタイムアウト時のメッセージを指定できる自作の待機ヘルパーを用意しておくと、複雑な完了条件も同じ形で扱えます。詳細は固定sleepをやめて明示的待機で設計するにまとめています。

Ajax読み込み待ちがうまくいかない

非同期のAjaxリクエストが完了する前に次の操作に進んでしまい、要素が古いままだったり値が反映されていなかったりするケースもよく踏みます。基本的な考え方は前項の明示的待機と同じで、「何秒待つか」ではなく「読み込み完了を示す何らかの状態変化」を条件にして待つことです。この記事群では専用の詳細記事を近日公開予定のため、現時点では明示的待機の設計とあわせて参照してもらうかたちにしています。

iframe・別タブで要素を操作できない

find_elementでは要素が取得できているのに、clicksend_keysを呼ぶとNoSuchElementErrorElementNotInteractableErrorになる場合、目的の要素がiframeの中か、target="_blank"で開いた別タブにあることがほとんどです。iframeはswitch_to.frameで視点を切り替え、終わったらswitch_to.default_contentに戻します。別タブはクリック前後のwindow_handlesの差分をポーリングして捕捉し、switch_to.windowで切り替えます。どちらも「入る・処理する・戻る」をセットで扱い、戻る処理はensureに入れて例外時も必ず実行されるようにするのが安全です。詳細はiframe・別タブの切り替えを確実に戻す設計で解説しています。

ヘッドレスでの文字化け・レイアウト崩れ

ローカルでは問題なく動くのに、Linuxサーバーやコンテナ上でヘッドレスChromeとして動かした途端、スクリーンショットの日本語が豆腐だらけになったり、要素の座標がずれたりします。文字化けの正体はエンコーディングではなく、サーバーに日本語フォントが入っていないことがほとんどで、fonts-noto-cjkの導入とfc-cacheによるキャッシュ更新で解消します。レイアウト崩れは--window-sizeを明示していないことが原因になりやすく、あわせて--disable-dev-shm-usageを指定しておくとDocker環境でのクラッシュも防げます。詳細はヘッドレスChromeの文字化け・レイアウト崩れを直すにまとめています。

driver.getが失敗する

ページ遷移のdriver.getが例外を投げ続ける場合、単純に同じgetをリトライするだけでは直らないことがあります。ブラウザプロセスそのものが応答不能になっているケースでは、何度呼び直しても同じ場所で固まり続けるためです。対処は、失敗を検知したらdriver.quit(これ自体も失敗しうるのでrescueで囲む)でドライバを終了し、新しいドライバを作り直してからリトライすることです。リトライには上限を設け、恒久的な障害では例外を再送出して呼び出し元に異常を伝えます。詳細はdriver.get失敗時にドライバを再生成するパターンで扱っています。

長時間実行で落ちる(自己回復設計)

何十分・何時間も同じブラウザセッションを使い回して巡回を続けると、原因を一つひとつ特定するのが難しい「なんとなく不安定」な状態で必ずどこかが壊れます。ここでは「壊れないように作る」のではなく「壊れたら回復する」前提での設計が有効です。稼働時間に上限を決めて自分から終了しスケジューラに再起動を任せる、ドライバプロセスをkillしてから作り直す、JS実行エラーをメモリ肥大のサインとして扱いDOM間引きとGC促進をセットで行う、という3つのパターンを組み合わせます。詳細はSeleniumバッチを長時間動かし続けるための自己回復設計を参照してください。

まとめ

どれも個別に潰していくと時間がかかりますが、症状からこの記事に戻ってきてもらえれば、該当する詳細記事にすぐたどり着けるようにしています。

コメント

タイトルとURLをコピーしました