Selenium for Python 画像が読み込まれるまで待機する


はじめに

Seleniumで処理を自動化しました。
しばしば画像の読み込みが完了する前に、処理を始めてしまい例外が発生していました。
そこで画像の読み込みが完了するまで待機する処理を考えてみました。

環境

Python 3.7.3
selenium 3.141.0
Firefox: 67.0.4 (64 ビット)
geckodriver 0.24.0 ( 2019-01-28)

ソース01

JavaScript

画像の読み込みが完了したかどうかを判定する処理は、JavaScriptで実装しました。
以下のようになります。
ここでは、Document配下の全ての画像を対象にしています。
適宜、修正してください。

JavaScript
const isLoadedAllImages = () => {
  const images = document.getElementsByTagName("img");
  let completed = true;
  for (const image of images) {
    if (image.complete === false) {
      completed = false;
      break;
    }
  }
  return completed;
}
return isLoadedAllImages()

Python

上で定義したJavaScriptをSelenium(Python)側から呼び出します。
単純な事ですが、重要なのは、画像が読み込まれるまでSelenium側でsleepをしてやることです。
理由は、

  • JavaScriptには、単純なSleepがない
  • JavaScript側がビジー状態になっていると画像の読み込みが完了しない

からです。
Sleepをしてくれるライブラリ等あればそれを使えば良いのでしょうが、それの設定をするのも面倒なのでこのような実装にしました。

Python
JavaScriptIsLoadedAllImages = '''
const isLoadedAllImages = () => {
  const images = document.getElementsByTagName("img");
  let completed = true;
  for (const image of images) {
    if (image.complete === false) {
      completed = false;
      break;
    }
  }
  return completed;
}
return isLoadedAllImages()
'''

def isLoadedAllImages(timeOut=300, interval=1):
  completed = False
  start = time.time()
  while time.time() - start < timeOut and completed == False:
    completed = driver.execute_script(JavaScriptIsLoadedAllImages)
    time.sleep(interval)
  return completed

ソース02

JavaScript

同じ関数を繰り返し定義しているためか挙動が不安定な事もあったので修正案です。
こちらはグローバル変数を変更するので注意が必要です。
関数定義

JavaScript
window.isLoadedAllImages = () => {
  const images = document.getElementsByTagName("img");
  let completed = true;
  for (const image of images) {
    if (image.complete === false) {
      completed = false;
      break;
    }
  }
  return completed;
}

関数呼び出し

JavaScript
return isLoadedAllImages();

Python

Python
JavaScriptIsLoadedAllImagesDefine = '''
window.isLoadedAllImages = () => {
  const images = document.getElementsByTagName("img");
  let completed = true;
  for (const image of images) {
    if (image.complete === false) {
      completed = false;
      break;
    }
  }
  return completed;
}
'''
JavaScriptIsLoadedAllImagesCall = "return isLoadedAllImages();"
driver.execute_script(JavaScriptIsLoadedAllImagesDefine)

def isLoadedAllImages(timeOut=300, interval=1):
  completed = False
  start = time.time()
  while time.time() - start < timeOut and completed == False:
    completed = driver.execute_script(JavaScriptIsLoadedAllImagesCall)
    time.sleep(interval)
  return completed

最後に

単純なサイトではこれでうまく行くと思います。
実際には、アクセスしているサイトの挙動によってはうまく行かないことも出てくるかと思います。

余談ですが、Selenium等での自動化にはまると楽しいですね。
これを仕事や収入に結び付ければ良いのでしょうが。