有时,当我们试图用selenium的find 方法从HTML代码中找到一个WebElement时,我们可能会在终端上看到一个NoSuchElementException 或Unable to locate element 的错误。但从HTML代码中可以清楚地看到,该元素存在于该页面上。那么,为什么会发生这种情况呢?今天我们将尝试找出这背后的一个原因。
为什么是等待函数?
我试图在我的浏览器中运行下面的代码,但有几次都出现了NoSuchElementException 的错误。它可能在你的浏览器中正常运行,不需要任何额外的修改。但在我的例子中,WebDriver无法点击cookie政策中的 "我接受 "按钮,因此无法继续前进。

from selenium import webdriver
driver = webdriver.Chrome(executable_path = r'G:/chromedriver_win32/chromedriver.exe')
driver.maximize_window()
driver.get('https://www.nba.com/players')
cookies = driver.find_element_by_id("onetrust-accept-btn-handler")
cookies.click()
当selenium执行脚本并接连点击按钮时,浏览器需要一定的时间来处理请求。但是用Python编写的脚本不知道需要多少时间来找到按钮,或者有时它可能无法在一定时间内找到想要的按钮。
这时,它就会抛出一些脚本错误或意外的错误,因为当时没有找到这个元素,或者在一段时间后才找到。在这种情况下,我们需要等待一定的时间,让浏览器正确地处理我们的请求,并在先前的过程结束后准确地执行下一行代码。
这种情况的发生是因为近代的大多数网络应用都是用Ajax构建的。Ajax帮助浏览器逐步加载更多的人员。它不断地添加元素,甚至在网络驱动找到主页面之后,也会造成时间差。
Python特定的等待或硬等待 time.sleep()
为了避免这个问题,我们可以使用python特定的 [time.sleep()](https://blog.finxter.com/manipulating-dates-and-times-in-python/ "Manipulating Dates and Times in Python")函数也被称为硬等待,无论浏览器是否被加载,它都会等待一定的时间。我们只需要在time.sleep() 函数中插入时间作为一个参数。我们需要导入Python [time](https://blog.finxter.com/get-the-current-time-python/ "How to Get the Current Time in Python?")模块。因此,代码将看起来像这样。
from selenium import webdriver
import time
driver = webdriver.Chrome(executable_path = r'G:/chromedriver_win32/chromedriver.exe')
driver.maximize_window()
driver.get('https://www.nba.com/players')
time.sleep(10)
cookies = driver.find_element_by_id("onetrust-accept-btn-handler")
cookies.click()
希望错误能被修复,但无论浏览器找到元素的速度有多快,它都会准确地等待10秒。
隐式等待
有时你的应用程序可能需要10秒来识别元素,但你已经等待了6秒,在这种情况下,你将不会在终端获得适当的输出。
在相反的情况下,你可以将你的硬等待设置为20秒,但你将在3秒内得到响应。在这种情况下,即使你的脚本在3秒内找到了元素,你也必须等待20秒,也就是你设置的硬等待的时间。所以,如果我们在一个脚本中多次使用这个硬性等待,我们就会延迟执行,损失大量的时间,这不是一个好的做法。这就是为什么Selenium提供了两个功能,叫做隐式等待和 显式等待.
根据文档,在隐式等待的情况下,当我们运行脚本时,Selenium将等待找到元素所需的确切时间。在找到元素的时候,它将开始执行代码,不再拖延。
from selenium import webdriver
driver = webdriver.Chrome(executable_path = r'G:/chromedriver_win32/chromedriver.exe')
driver.maximize_window()
driver.get('https://www.nba.com/players')
driver.implicitly_wait(20)
cookies = driver.find_element_by_id("onetrust-accept-btn-handler")
cookies.click()
这里,我们设置了隐式等待20秒。这里发生的情况是,驱动程序想每隔半秒就连接到元素。它不断尝试,直到找到元素。每当它找到元素,它就进入下一行,开始执行下一行的代码。因此,我们不需要像硬性等待那样等待额外的时间。如果它在20秒的时间范围内没有找到元素,那么它将抛出一个 "NoSuchElementException"。
显式等待
显式等待是指你在一个WebDriver ,并在进行下一步代码之前等待该特定条件的发生。在显式等待中,我们同时在做两件事。我们正在设置一个特定的时间限制,同时,我们正在等待某些条件的发生。要使用这种方法,我们需要从selenium.webdriver.support.ui 中导入WebDriverWait 模块,从selenium.webdriver.support 中导入expected_conditions 模块。
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome(executable_path = r'G:/chromedriver_win32/chromedriver.exe')
driver.maximize_window()
driver.get('https://www.nba.com/players')
cookies = WebDriverWait(driver,10).until(EC.presence_of_element_located(('id','onetrust-accept-btn-handler')))
cookies.click()
在这里,在 "cookies" 变量中,我们设置了10秒的最大时限,而 "until" 是WebDriverWait 的另一个函数,它将条件语句作为参数。默认情况下,WebDriverWait 试图每500毫秒连接一个元素,如果在分配的时间内没有响应,它就抛出一个TimeoutException。
预期的条件
就像我们在'cookies'变量中使用的条件 "presence_of_element_located"一样,有一些常见的条件可以用来实现浏览器的自动化。Selenium API绑定通过一些可用的方法使它变得更容易,这样我们就不需要重复编码一个expected_condition 类。
title_istitle_containspresence_of_element_locatedvisibility_of_element_locatedvisibility_ofpresence_of_all_elements_locatedtext_to_be_present_in_elementtext_to_be_present_in_element_valueframe_to_be_available_and_switch_to_itinvisibility_of_element_locatedelement_to_be_clickablestaleness_ofelement_to_be_selectedelement_located_to_be_selectedelement_selection_state_to_beelement_located_selection_state_to_bealert_is_present
哪一个是最好的?
在基于Ajax的Web应用程序中,元素已经出现在网页上,但由于其耗时的页面加载过程还不可见,具有一定条件的显式等待可能是最好的解决方案。在ajax驱动的复杂web应用中,它总是工作得很好。
在显式等待的情况下,你需要在每次需要时都使用expected_conditions ,而用隐式等待编写的一行代码 就足以满足整个脚本的需要。如果你不是在处理复杂的结构化网页,那么建议你使用隐式等待,因为你不需要在一个脚本中多次编写这些代码。