首先,了解元素等待需要先知道浏览器的工作方式。浏览器在解析HTML文件时,需要从第一行按顺序往下解析。不仅仅解析HTML文件如此,解析JS文件也是这样,但是浏览器解析JS文件就比解析HTML文件要复杂地多的多。对于许多页面来说,页面需要加载数据来驱动视图,数据没加载完成,页面就不会显示对应的节点,又或者监听用户的行为,根据用户的行为添加、显示和删除页面相应的节点。页面加载数据的方式有很多种:加载本地文件、自定义AJAX、使用JQuery、或者是客户端请求数据库返回数据。在上述任何一种情况下当想要操纵的节点没有加载完成时,python脚本又已经开始执行,那么结果只能是报错。顺着这个问题往下想,还有许许多多地情况都会阻碍自动化脚本的执行。为了解决这样类似的问题,有人提出了元素等待的概念,简单的来说就是让脚本等待页面元素的出现。
元素等待的三种方式
- 强制等待
- 显式等待
- 隐式等待
强制等待sleep()
强制等待是指让脚本等待一定的固定时间再执行,需要说明的是,强制等待sleep()
方法由Python的time
模块提供
from time import sleep
sleep(10)
print('等待10秒之后执行')
当脚本执行到第三行时,会等待10秒的事件,然后再往下执行,参数就是要等待的时间。
比如下面的例子,在很多地方都需要使用到强制等待sleep()
方法
from time import sleep
browser = webdriver.Chrome()
browser.get('https://weibo.com/')
sleep(10)
微博的首页繁冗且杂长,需要等待一定的时间再去获取相应的元素,否则直接去查找还未加载出来或者需要特定操作才可以出现的元素,会抛出NoSuchElementException
的异常。
显式等待
显式等待和隐式等待都是由WebDriver提供的,显式等待是在一段固定时间内,每隔一定的时间检测一次相应的元素是否存在,如果在这段固定时间内想要检测的元素依然不存在的话,会抛出TimeoutException
的异常。
显式等待WebDriverWait的使用
WebDriverWait
类是由WebDriver
提供的方法,具体格式如下:
WebDriverWait(driver,timeout,interval,err)
-
driver
:浏览器的实例 -
timeout
:设置等待的总时间 -
interval
:设置间隔的时间 -
err
:超时后的异常信息,上面已经提到了,默认抛出TimeoutException
的异常。
WebDriverWait()
一般由until()
或者until_not
配合使用,具体格式如下:
from selenium import webdriver
#从selenium导入webdriver包
from selenium.webdriver.common.by import By
#从selenium.webdriver.common.by 导入By包进行元素定位
from selenium.webdriver.support.wait import WebDriverWait
#从selenium.webdriver.common.wait 导入WebDriverWait包进行显式等待
from selenium.webdriver.support import expected_conditions as Ec
#从selenium.webdriver.common.wait 导入expected_conditions类
#并通过关键字as将expected_conditions重命名为Ec
WebDriverWait(driver,timeout,interval,err).until(
method,message=""
)
#或者
WebDriverWait(driver,timeout,interval,err).until_not(
method,message=""
)
#message=""可以忽略,但是忽略时method外是两层括号,下面会继续提到message
#from selenium.webdriver.support.wait import WebDriverWait有的文档并不是这个写的
#而是from selenium.webdriver.support.ui import WebDriverWait,其实ui和wait并没有什么差别,随心写就好
上述代码中,until
和until_not
都是以method
作为参数,直到until
的返回值为true或者直到until_not
的返回值为false
,method
方法是由expected_conditions
类提供的内置方法,具体方法如下:
内置方法示例前提条件:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as Ec
browser = webdriver.Chrome()
browser.get('https://weibo.com/')
sleep(10)
-
title_is
判断当前页面的标题是否等于预期
element = WebDriverWait(browser,10,1).until(
Ec.title_is(('微博随时随地发现新鲜事')),message='没有找到字符串'
)
值得注意的是,如果超时(until
的返回值为false),脚本会抛出TimeoutException
,同时将message
传入异常,下面的很多种方法都是如此,下图就是将message
传入异常并抛出的例子
-
title_contains
判断当前页面的标题是否包含预期字符串
element = WebDriverWait(browser,10,1).until(
Ec.title_contains(('京东'))
)
-
presence_of_element_located
判断元素是否被加在DOM树里,不代表该元素一定可见
element = WebDriverWait(browser,10,1).until(
Ec.presence_of_element_located((By.ID,'loginname'))
)
-
visibility_of_element_located
判断元素是否可见(可见代表没有隐藏,并且宽高都不为0)
element = WebDriverWait(browser,10,1).until(
Ec.visibility_of_element_located((By.ID,'loginname'))
)
#可见返回true,不可见抛出TimeoutException异常
-
invisibility_of_element_located
判断某个元素是否不存在与DOM树中或者不可见
element = WebDriverWait(browser,10,1).until(
Ec.invisibility_of_element_located((By.ID,'loginname'))
)
#和上一个方法相反不可见返回true,可见抛出TimeoutException异常
-
visibility_of
与上一个方法相同,但是上一个方法的参数为定位,该方法的参数为定位后的元素
loginModel = browser.find_element_by_id('loginname')
element = WebDriverWait(browser,10,1).until(
Ec.visibility_of((loginModel))
)
#值得注意的是,visibility_of的参数必须为一个经过定位之后找到的元素,而不是元素的定位
-
presence_of_all_elements_located
判断DOM树中是否存在这个元素,可以存在一个或多个,只要存在一个就返回true
element = WebDriverWait(browser,10,1).until(
Ec.presence_of_all_elements_located((By.ID,'loginname'))
)
-
text_to_be_present_in_element
判断某个元素中的text是否包含了预期的字符串
element = WebDriverWait(browser,10,1).until(
Ec.text_to_be_present_in_element((By.CLASS_NAME,'enter_psw'),'密码')
)
#这个方法的使用格式和其他的方法有点不一样
-
text_to_be_present_in_element_value
判断某个元素中的value是否包含了预期的字符串
element = WebDriverWait(browser,10,1).until(
Ec.text_to_be_present_in_element_value((By.CLASS_NAME,'enter_psw'),'密码')
)
-
element_to_be_clickableo
判断某个元素是否可见并且是可以点击的
element = WebDriverWait(browser,10,1).until(
Ec.element_to_be_clickable((By.ID,'login_form_savestate'))
)
-
staleness_of
等待一个元素从DOM树中移除
node = browser.find_element_by_id('loginname')
element = WebDriverWait(browser,50,1).until(
Ec.staleness_of((node))
)
-
element_to_be_selected
判断某个元素是否被选中,一般用于下拉列表
#因为微博首页没有下拉列表,所以此测试在12306的注册页面上
browser.get('https://kyfw.12306.cn/otn/regist/init')
sleep(10)
node = browser.find_element_by_css_selector("[value='0']")
element = WebDriverWait(browser,20,1).until(
Ec.element_to_be_selected((node))
)
-
element_selection_state_to_be
判断某个元素的选中状态是否符合预期
#因为微博首页没有下拉列表,所以此测试在12306的注册页面上
browser.get('https://kyfw.12306.cn/otn/regist/init')
sleep(10)
node = browser.find_element_by_id("idTypeCode_wgjzz")
element = WebDriverWait(browser,20,1).until(
Ec.element_selection_state_to_be(node,True)
)
# True表示预期选中,false表示预期不选中
-
element_located_selection_state_to_be
和上面一样判断元素的选中状态是否和预期一样,只不过仔细看就会发现,此方法的参数为定位,而上面的方法的参数是定位后的元素
#因为微博首页没有下拉列表,所以此测试在12306的注册页面上
browser.get('https://kyfw.12306.cn/otn/regist/init')
sleep(10)
element = WebDriverWait(browser,20,1).until(
Ec.element_located_selection_state_to_be((By.ID,"idTypeCode_wgjzz"),True)
)
-
alert_is_present()
判断页面上是否存在alert
element = WebDriverWait(browser,10,1).until(
Ec.alert_is_present()
)
至此,expected_conditions
类提供的所有的方法的实例都已经举例完毕了
隐式等待
如果 WebDriver
没有在 DOM 中找到元素,将继续等待,超出设定时间后则抛出找不到元素的异常,
换句话说,当查找元素或元素并没有立即出现的时候,隐式等待将等待一段时间再查找 DOM,默认的时间是 0
implictly_wait()方法具体格式
browser.implicitly_wait(10)
#参数为秒数,默认为0
实例:
from selenium import webdriver
browser = webdriver.Chrome()
browser.implicitly_wait(10)
browser.get('https://baidu.com/')
browser.find_element_by_id("kw").send_keys('selenium')
上述代码表示在脚本查找元素时,即使当前没有找到相应的元素也不会立即报错,而是等待10秒的时间继续查找相应的元素,如果依然未找到,则超时TimeoutException
报错
总结:显式等待和隐式等待的区别
- 显式等待更节省时间
- 显式等待有等待条件,而隐式等待没有
- 隐式等待是一个全局等待,前面设置了隐式等待时间,那么后面的元素找不到的话也都会进行隐式等待
- 显式等待可以针对特定的元素等待,而隐式等待不是,当隐式等待执行测试时,如果没有找到元素将继续等待,超出时间会抛出异常
网友评论