【Python】Seleniumの待機処理でtime.sleepを絶対に使用してはいけないという教訓

【Python】Seleniumの待機処理でtime.sleepを絶対に使用してはいけないという教訓
SeleniumのVersionについて注意
2021年10月13日にSelenium 4がリリースされ、それに伴ってコードが多少変更されました。
pip install selenium」のコマンドを実行して、お使いのSeleniumのVersionを確認し、自身に合ったコードに変更してご使用ください。

Seleniumを使うときに「time.sleep()を使ってブラウザ待機処理をしている」という方、いませんか?

単発でしか動かさないプログラムならばそのままでも大丈夫です。
ですが、毎日のタスクであったり、業務で使用するプログラムする場合ならば、time.sleepで待機処理を行うのは絶対にやめたほうがいいです


その理由と対処法をこれから解説します。

time.sleepがいけない理由

初心者の方や自分用だけでSeleniumを使用している人はよくtime.sleepで待機処理をしています。

そのプログラムが単発での使用でしたら、動けば正義なのでtime.sleepでも良いでしょう。しかし、定期実行するタスクだった場合は安定稼働できるのでしょうか?

私の実体験からすると、答えはNoです。
もしもYesだとしても、time.sleepで多く時間を見積もる必要があるため、無駄な時間を過ごすことがでてきてしまいます。


time.sleepを使ってはいけないのには、以下のような理由が挙げられます。

time.sleepを使った時のデメリット
  • 安定して稼働するという保証がない
  • time.sleepで待っている時間がもったいない
  • サイト読み込みに時間がかかったときにエラーが出る

1番と3番はほぼ同等の意味ですね。

time.sleepで待機処理をしている場合の主な脅威は、[NoSuchElementException](要素が見つかりませんでしたエラー)か、もしくは「待機時間が長すぎること」です。


例えば、以下のようなSeleniumを使ったプログラムを実行したとします。


プログラムを実行する場合の前準備
以下のプログラムを実行したい場合は、コマンドプロンプトもしくはターミナルに、以下の2つをインストールしてください。

pip install selenium
pip install webdriver-manager
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager


# ドライバーを自動でインストールしてくれる
# Selenium3の場合
#driver = webdriver.Chrome(ChromeDriverManager().install()) 
# Selenium4の場合
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) 

# URLを設定する
url = "https://teru2teru.com"
try:
    # URLを開く
    driver.get(url)
    time.sleep(3)

    # headingというクラスがあるものを複数探して、そのうちのいちばん最初のものをクリックする。
    
    #elems = driver.find_elements_by_class_name("heading") # Selenium3の場合
    elems = driver.find_elements(by=By.CLASS_NAME, value="heading") # Selenium4の場合
    elems[0].click()

    time.sleep(3)
    #h1_text = driver.find_element_by_tag_name("h1").text # Selenium3の場合
    h1_text = driver.find_element(by=By.TAG_NAME, value="h1").text # Selenium4の場合
    print("「" + h1_text + "」のURLを開きました。")
    print("終了します。")
# エラーが発生した時はエラーメッセージを吐き出す。
except Exception as e:
    print(e)
    print("エラーが発生しました。")

# 最後にドライバーを終了する
finally:
    driver.close()
    driver.quit()

このプログラムには、上述したデメリットが全て含まれています

このコードではページ遷移されるごとにtime.sleepで5秒待機していますが、早く開きすぎたときは時間がもったいないですね。

じゃあもっと短くすればいいじゃん』と思いますが、 もしもサイトが更新されて重くなってしまった場合は、途中で止まってしまい最後まで実行できません。


それでは、どのように待機処理を変更すれば良いのか、2種類の方法を紹介します。

待機処理の正しい書き方その1

先程のプログラムを以下のように書き換えれば、Seleniumの速度も早くなり安定して稼働させることができます。

from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager

# /* ------- 追加部分 -------
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# -------------------------*/

# ドライバーを自動でインストールしてくれる
# Selenium3の場合
#driver = webdriver.Chrome(ChromeDriverManager().install()) 
# Selenium4の場合
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) 

# ------- 追加部分 -------
# 最大の読み込み時間を設定 今回は最大30秒待機できるようにする
wait = WebDriverWait(driver=driver, timeout=30)
# -------------------------*/

# URLを設定する
url = "https://teru2teru.com"

try:
    # URLを開く
    driver.get(url)

    # /*------- 変更部分 -------
    # 要素が全て検出できるまで待機する
    wait.until(EC.presence_of_all_elements_located)
    # -------------------------*/

    # headingというクラスがあるものを複数探して、そのうちのいちばん最初のものをクリックする。
    #elems = driver.find_elements_by_class_name("heading") # Selenium3の場合
    elems = driver.find_elements(by=By.CLASS_NAME, value="heading") # Selenium4の場合
    elems[0].click()

    # ------- 変更部分 -------
    # 要素が全て検出できるまで待機する
    wait.until(EC.presence_of_all_elements_located)
    # -------------------------*/

    #h1_text = driver.find_element_by_tag_name("h1").text # Selenium3の場合
    h1_text = driver.find_element(by=By.TAG_NAME, value="h1").text # Selenium4の場合

    print("「" + h1_text + "」のURLを開きました。")
    print("終了します。")
# エラーが発生した時はエラーメッセージを吐き出す。
except Exception as e:
    print(e)
    print("エラーが発生しました。")

# 最後にドライバーを終了する
finally:
    driver.close()
    driver.quit()

time.sleepの代わりに待機処理で用いているものは WebDriverWaitexpected_conditions as EC です。

time.sleep の部分を wait.until(EC.presence_of_all_elements_located) (全ての要素が表示されるまで待つ) と変更することで、ブラウザがきちんと読み込まれるまで待機することができます

こうすることでtime.sleepをしなくても、確実にブラウザを読み込んで実行することができます。


今回はexpected_conditionsの条件で、「ブラウザを全て読み込まれたら…」に設定しましたが、「特定の要素が読み込まれたら…」や「要素がクリックできる状態になったら…」などが存在します。
また、find_element_byの代わりにwait.untilを加えると、さらに頑健なプログラムとなります。

expected_conditionの条件について詳しく知りたい方はこちらからご覧ください。
参考リンク:待機 – Selenium Python Bindings

待機処理の正しい書き方その2(面倒くさい人向け)

その1ではWebDriverWaitを用いましたが、もっと簡単にtime.sleepの代わりになる魔法のコードがあります。

それが以下のコードです。

driver.implicitly_wait(10) # 引数には時間を入れる 今回は10秒にする

これを記入するだけで、time.sleepなしで待機処理をすることができます。

上記のコードは、「指定した待機時間の間、find_element_byで要素が見つかるまで繰り返し検索」をしてくれます。
時間内に見つからなかったり、そもそも指定したものが見つからなかった場合はエラーを吐き出します。


この魔法のコードを、以下のようにdriverの下に記入するだけです。

from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager


# ドライバーを自動でインストールしてくれる
# Selenium3の場合
#driver = webdriver.Chrome(ChromeDriverManager().install()) 
# Selenium4の場合
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) 


# ------- 追加部分 -------
# 指定した要素が見つかるまでの待ち時間を設定する 今回は最大10秒待機する
driver.implicitly_wait(10)

# URLを設定する
url = "https://teru2teru.com"

try:
    # URLを開く
    driver.get(url)

    # headingというクラスがあるものを複数探して、そのうちのいちばん最初のものをクリックする。
    #elems = driver.find_elements_by_class_name("heading") # Selenium3の場合
    elems = driver.find_elements(by=By.CLASS_NAME, value="heading") # Selenium4の場合
    elems[0].click()

    #h1_text = driver.find_element_by_tag_name("h1").text # Selenium3の場合
    h1_text = driver.find_element(by=By.TAG_NAME, value="h1").text # Selenium4の場合

    print("「" + h1_text + "」のURLを開きました。")
    print("終了します。")
# エラーが発生した時はエラーメッセージを吐き出す。
except Exception as e:
    print(e)
    print("エラーが発生しました。")

# 最後にドライバーを終了する
finally:
    driver.close()
    driver.quit()


とても簡単でメンテナンスが面倒な人にはおすすめな方法です。

まとめ

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

この記事を書くきっかけは、PCを起動した瞬間に勤怠をバックグラウンドで押してくれるSeleniumプログラムにエラーが出てしまい、出勤したにもかかわらず無断欠勤扱いされてめちゃくちゃ怒られたからです…(その月の勤怠表を見てから気づいたので当月分の出勤日が全てありませんでした。笑)

たまたまそのサイトが重くなっているときにプログラムが作動したので、time.sleepで間に合わずエラーになったのが原因でした。1ヶ月以上運用してきちんと動いていたので油断していましたね….

time.sleepでの待機を変えて、勤怠が押されたかどうかをslackに知らせるプログラムに書き直しました。ここ数ヶ月はエラーも誤作動も一切なくなりました。

Seleniumプログラムでtime.sleepを使用する場合は、動的なサイトだったり「それを使わなきゃ動かない!」という時にのみ採用すると良いです。


こちらの記事が、誰かのお役に立てたら幸いです。

Seleniumカテゴリの最新記事

%d人のブロガーが「いいね」をつけました。