NoSuchElementExceptionへの対応方法

NoSuchElementExceptionへの対応方法 プログラミング

「NoSuchElementExceptionエラーが出て困っている・・・」
「Seleniumで要素が見つからない場合にエラーが出てしまう・・・」

このような場合には、この記事の内容が参考となります。
この記事では、NoSuchElementExceptionエラーを解決する方法を解説しています。

本記事の内容

  • NoSuchElementExceptionエラーとは?
  • 明示的な待機
  • 暗黙的な待機

それでは、上記に沿って解説していきます。

NoSuchElementExceptionエラーとは?

Seleniumでスクレイピングをしていると、次のようなエラーが出ることがあります。

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element:

「そんな要素はありません」というメッセージです。
スクレイピング対象のページが、複雑であればあるほどこのエラーは出ます。

複雑とは、以下のような特徴のあるページです。

  • Ajaxで動的にコンテンツを表示している
  • 表示内容が固定化されていない

これらについて説明しておきます。

Ajaxで動的にコンテンツを表示している

正確には、JavaScriptでコンテンツを表示している場合ですね。
ページの読み込み完了(loadイベント)以降に、要素が追加されることがあります。

そして、そのような要素をスクレイピングの対象にすることもあります。
Amazon、Twitter、Instagram、Tiktokなどはすべてそうです。

ある程度の大手サイトであれば、Ajaxの利用は一般的でしょう。
パフォーマンスであったり、技術的なことがその理由にあるはずです。

しかし、スクレイピング対策がその理由という場合もあるでしょう。

表示内容が固定化されていない

静的なページの場合でも、要素があったりなかったりはあります。
それは、単純に表示するデータの有無によります。

例えば、商品ページを考えてみましょう。
次のようなデータ項目があるとします。

  • 商品名(必須)
  • 価格(必須)
  • 詳細(任意)

詳細が登録されないと、詳細を表示する要素が存在しないことがあります。
具体的に言うと、以下コードにおける「id=”description”」の要素が存在しないということです。

<div id="title">商品名</div>
<div id="price">100円</div>
<div id="description">商品の説明がここに入ります。</div>

もちろん、登録の有無に関わらず要素が存在することもあります。
それは、サイト・ページによって異なることでしょう。

まとめ

今どきのサイトであれば、複雑なページがデフォルトと考えるべきですね。
したがって、このエラーを避けて通ることはできないと言えます。

このエラーを回避するには、主に次の対応方法があります。

  • 明示的な待機
  • 暗黙的な待機

それぞれの対応方法を下記で説明していきます。

以上、NoSuchElementExceptionエラーについて説明しました。
次は、明示的な待機を説明していきます。

明示的な待機

明示的な待機とは、条件付きの待機になります。
その条件が満たされるまで、待機し続けることになります。

具体的にコードを用いて説明します。
例えば、次のようなWebページがあったとします。

index.html

<!DOCTYPE html>
<html>
  <head>
  <script>
    window.addEventListener("load", function() {
      elem_add();
    });

    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    async function elem_add() {
      await sleep(8000);
      var newElement = document.createElement("p");
      newElement.textContent = "Hello from JavaScript!";
      newElement.id = "add_p";
      document.body.appendChild(newElement);
    };
  </script>
  </head>
  <body>
  </body>
</html>

このページでは、loadイベント発生後の8秒後に要素が追加されます。
「id=”add_p”」の要素です。

そして、このページを次のPythonコードでスクレイピングするとします。
(以降では、元スクリプトと呼びます)

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome import service as fs

CHROMEDRIVER = "/usr/bin/chromedriver"
URL = "http://localhost/etc/selenium/index.html"

# ドライバー指定でChromeブラウザを開く
chrome_service = fs.Service(executable_path=CHROMEDRIVER)
driver = webdriver.Chrome(service=chrome_service)

# アクセス
driver.get(URL)

# 要素を特定する
elem = driver.find_element(By.ID, "add_p")

# ブラウザを閉じる
driver.quit()

上記を実行すると、次のようなエラーが出ることになります。

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="add_p"]"}

明示的な待機により、このエラーを出ないようにします。
まずは、元スクリプトに次のimport文を追加します。

from selenium.webdriver.support.ui import WebDriverWait

そして、元スクリプト上における要素の特定を次のコードに置き換えます。

# 要素を特定する
elem = WebDriverWait(driver, 10).until(lambda x: x.find_element(By.ID, "add_p"))

元スクリプトを変更した後のコードは、以下のようになります。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome import service as fs
from selenium.webdriver.support.ui import WebDriverWait

CHROMEDRIVER = "/usr/bin/chromedriver"
URL = "http://localhost/etc/selenium/index.html"

# ドライバー指定でChromeブラウザを開く
chrome_service = fs.Service(executable_path=CHROMEDRIVER)
driver = webdriver.Chrome(service=chrome_service)

# アクセス
driver.get(URL)

# 要素を特定する
elem = WebDriverWait(driver, 10).until(lambda x: x.find_element(By.ID, "add_p"))

# ブラウザを閉じる
driver.quit()

上記コードを実行すると、エラーが出なくなりました。
WebDriverWaitによって、条件を満たすまで待機しています。

WebDriverWaitの定義は、以下。

timeoutの指定は、必須です。
上記コードでは、timetoutに10を設定しています。

そのため、最大で10秒間は待機することになります。
10秒もあれば、さすがに要素は表示されるでしょう。

今回は、8秒間は待機することになっています。
index.htmlの下記コードにより、この時間は調整できます。

await sleep(8000);

なお、WebDriverWait以外のSeleniumに関するコードは次の記事内容を前提にしています。

以上、明示的な待機について説明しました。
次は、暗黙的な待機について説明します。

暗黙的な待機

暗黙的な待機とは、条件無しの待機になります。
具体的な条件は指定しません。

そして、指定した時間を最大で待機します。
要素が見つかったら、その時点で待機するのを止めて処理を続けます。

簡単に言うと、お手軽な待機と言えます。
また、複数の要素を対象する場合、暗黙的な待機を利用することが多いです。
WebDriverWaitを複数記述する手間を省くためですね。

対応方法は、非常に簡単です。
元スクリプトに次のコードを追加するだけです。

driver.implicitly_wait(10)

アクセスより前に記述します。

driver.implicitly_wait(10)

# アクセス
driver.get(URL)

元スクリプトを変更した後のコードは、以下のようになります。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome import service as fs

CHROMEDRIVER = "/usr/bin/chromedriver"
URL = "http://localhost/etc/selenium/index.html"

# ドライバー指定でChromeブラウザを開く
chrome_service = fs.Service(executable_path=CHROMEDRIVER)
driver = webdriver.Chrome(service=chrome_service)

# 暗黙的な待機
driver.implicitly_wait(10)

# アクセス
driver.get(URL)

# 要素を特定する
elem = driver.find_element(By.ID, "add_p")

# ブラウザを閉じる
driver.quit()

以上、暗黙的な待機について説明しました。

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