认识Python Selenium的等待功能

411 阅读5分钟

有时,当我们试图用seleniumfind 方法从HTML代码中找到一个WebElement时,我们可能会在终端上看到一个NoSuchElementExceptionUnable 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_is
  • title_contains
  • presence_of_element_located
  • visibility_of_element_located
  • visibility_of
  • presence_of_all_elements_located
  • text_to_be_present_in_element
  • text_to_be_present_in_element_value
  • frame_to_be_available_and_switch_to_it
  • invisibility_of_element_located
  • element_to_be_clickable
  • staleness_of
  • element_to_be_selected
  • element_located_to_be_selected
  • element_selection_state_to_be
  • element_located_selection_state_to_be
  • alert_is_present

哪一个是最好的?

在基于Ajax的Web应用程序中,元素已经出现在网页上,但由于其耗时的页面加载过程还不可见,具有一定条件的显式等待可能是最好的解决方案。在ajax驱动的复杂web应用中,它总是工作得很好。

在显式等待的情况下,你需要在每次需要时都使用expected_conditions ,而用隐式等待编写的一行代码 就足以满足整个脚本的需要。如果你不是在处理复杂的结构化网页,那么建议你使用隐式等待,因为你不需要在一个脚本中多次编写这些代码。