「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()
以上、暗黙的な待機について説明しました。