设置元素等待

为什么需要设置元素等待?

  • 因为,目前大多数Web应用程序都是使用Ajax和Javascript开发的;每次加载一个网页,就会加载各种HTML标签、JS文件
  • 但是,加载肯定有加载顺序,大型网站很难说一秒内就把所有东西加载出来,不仅如此,加载速度也受网络波动影响
  • 因此,当我们要在网页中做元素定位的时候,有可能我们打开了网页但元素未加载出来,这个时候就定位不到元素,就会报错
  • 所以,我们需要设置元素等待,意思就是:等待指定元素已被加载出来之后,我们才去定位该元素,就不会出现定位失败的现象了

1. 强制等待

第一种也是最简单粗暴的一种办法就是强制等待sleep(xx)。
如果我们不设置元素等待,那怎么避免 因元素未加载出来而定位失败 的情况出现呢?

  • 答案很简单,就是调用 sleep() ,也叫强制等待
  • 但是缺点就是:如果指定的时间过长,即使元素已被加载出来了,但还是要继续等,这样会浪费很多时间

举个强制等待的栗子:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2022/1/2 12:34
# @Author  : shisuiyi
# @File    : 等待.py
# @Software: win10 Tensorflow1.13.1 python3.9
# !/usr/bin/env python
# -*- coding: utf-8 -*-
from time import sleep
from selenium import webdriver

driver = webdriver.Chrome()

# 访问网址
driver.get("http://www.baidu.com")

# ===强制等待3秒才执行下一步===
sleep(3)

# 找到搜索框
inputElement = driver.find_element('id', "kw")

这种叫强制等待,不管你浏览器是否加载完了,程序都得等待3秒,3秒一到,继续执行下面的代码,作为调试很有用,有时候也可以在代码里这样等待,不过不建议总用这种等待方式,太死板,严重影响程序执行速度。

2. 隐性等待

第二种办法叫隐性等待,implicitly_wait(xx)

举个隐性等待的栗子:

# -*- coding: utf-8 -*-
# @Time    : 2022/1/2 12:34
# @Author  : shisuiyi
# @File    : 等待.py
# @Software: win10 Tensorflow1.13.1 python3.9
# !/usr/bin/env python
# -*- coding: utf-8 -*-
from time import sleep
from selenium import webdriver

driver = webdriver.Chrome()

# ===隐性等待20s===
driver.implicitly_wait(20)

# 访问网址
driver.get("http://www.baidu.com")

# 找到百度一下的按钮
baidu = driver.find_element('xpath', "//*[@id = 'su']")

隐形等待是设置了一个最长等待时间,如果在规定时间内网页加载完成,则执行下一步,否则一直等到时间截止,然后执行下一步。注意这里有一个弊端,那就是程序会一直等待整个页面加载完成,也就是一般情况下你看到浏览器标签栏那个小圈不再转,才会执行下一步,但有时候页面想要的元素早就在加载完成了,但是因为个别js之类的东西特别慢,我仍得等到页面全部完成才能执行下一步,我想等我要的元素出来之后就下一步怎么办?有办法,这就要看selenium提供的另一种等待方式——显性等待wait了。

需要特别说明的是:隐性等待对整个driver的周期都起作用,所以只要设置一次即可。

3. 显性等待

什么是显式等待?

  • 需要定位某个元素的时候,但元素可能不可见,这个时候针对这个元素就可以使用显式等待了
  • 显式等待和隐式等待最大的不同就是:你可以它看成是局部变量,作用于指定元素
  • 配合该类的until()和until_not()方法,就能够根据判断条件而进行灵活地等待了。它主要的意思就是:程序每隔xx秒看一眼,如果条件成立了,则执行下一步,否则继续等待,直到超过设置的最长时间,然后抛出TimeoutException

显式等待的优势

相比隐式等待,显式等待只对指定元素生效,不再是在整个WebDriver生命周期内生效【仅对元素生效】

  • 可以根据需要定位的元素来设置显式等待,无需等待页面完全加载,节省大量因加载无关紧要文件而浪费掉的时间【针对元素设置,无需等待页面加载完成,节省加载时间】
  • 可以控制条件: 等待某个元素可以被点击了, 可以被看见了,在去输入等等。

举个显性等待的栗子:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2022/1/2 12:34
# @Author  : shisuiyi
# @File    : 等待.py
# @Software: win10 Tensorflow1.13.1 python3.9
# !/usr/bin/env python
# -*- coding: utf-8 -*-
from time import sleep
from selenium import webdriver
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

driver = webdriver.Chrome()

# ===隐性等待20s===
driver.implicitly_wait(20)

# 访问网址
driver.get("http://www.baidu.com")

# 显性等待
wait = WebDriverWait(driver, timeout=8)
mark = ('xpath', "//*[@id = 'su']")
when = expected_conditions.element_to_be_clickable(mark)
wait.until(when)

# 找到百度一下的按钮
baidu = driver.find_element('xpath', "//*[@id = 'su']")
baidu.click()

WebDriverWait源码解读

class WebDriverWait(object):
    def __init__(self, driver, timeout, poll_frequency=POLL_FREQUENCY, ignored_exceptions=None):
        """Constructor, takes a WebDriver instance and timeout in seconds.

           :Args:
            - driver - Instance of WebDriver (Ie, Firefox, Chrome or Remote)
            - timeout - Number of seconds before timing out
            - poll_frequency - sleep interval between calls
              By default, it is 0.5 second.
            - ignored_exceptions - iterable structure of exception classes ignored during calls.
              By default, it contains NoSuchElementException only.

           Example::

            from selenium.webdriver.support.wait import WebDriverWait \n
            element = WebDriverWait(driver, 10).until(lambda x: x.find_element(By.ID, "someId")) \n
            is_disappeared = WebDriverWait(driver, 30, 1, (ElementNotVisibleException)).\\ \n
                        until_not(lambda x: x.find_element(By.ID, "someId").is_displayed())
        """

WebDriverWait实例初始化传参

  • driver:WebDriver实例,传入前面声明的driver即可

  • timeout:最大超时时间;

  • poll_frequency:执行间隔,默认0.5s

  • ignored_exceptions:

    需要忽略的异常

    • 如果在调用 until() 或 until_not() 的过程中抛出这个元组中的异常, 则不中断代码,继续等待;
    • 如果抛出的是这个元组外的异常,则中断代码;
    • 忽略的异常默认只有 NoSuchElementException

通俗易懂的 WebDriverWait

WebDriverWait(driver实例, 超时时长, 调用频率, 忽略的异常).until(要调用的 方法, 超时时返回的信息)

WebDriverWait实例的两个方法

  1. until(self, method, message=‘’)

作用:每隔一段时间(上面的poll_frequency)调用method,直到返回值不为False或不为

method:需要执行的method

message:抛出异常时的文案,会返回 TimeoutException ,表示超时

注意:这个才是常用的,如:定位元素直到不返回空

  1. until_not(self, method, message=‘’)

作用:调用method,直到返回值False或

method:需要执行的method

message:抛出异常时的文案,会返回TimeoutException,表示超时

expected_conditions源码解读

expected_conditions的介绍

是selenium中的一个模块,包含一系列用于判断的条件方法

这里就只介绍三个在设置元素等待里面最常用的判断条件

其一:presence_of_element_located
def presence_of_element_located(locator):
    """ An expectation for checking that an element is present on the DOM
    of a page. This does not necessarily mean that the element is visible.
    locator - used to find the element
    returns the WebElement once it is located
    """

    def _predicate(driver):
        return driver.find_element(*locator)

    return _predicate

作用: 检查当前DOM树种是否存在该元素(和是否可见没有关系),只要有一个元素加载出来则通过

locator参数

传入一个元组,格式如下 (By.ID, “元素ID”)

  • 第一个参数:定位元素的方式,和那八种元素定位方式一样,只是这里需要引入 By 模块,然后再调用类属性
  • 第二个参数:和之前调用元素定位方法一样传参即可
  • 所以正确写法是: presence_of_element_located((By.ID, “kw”))
其二:presence_of_all_elements_located

源码几乎一样

def presence_of_all_elements_located(locator):
    """ An expectation for checking that there is at least one element present
    on a web page.
    locator is used to find the element
    returns the list of WebElements once they are located
    """

    def _predicate(driver):
        return driver.find_elements(*locator)

    return _predicate

唯一要注意的点就是

  • 因为调用的是 _find_elements ,会返回多个元素
  • 如果用这个条件类,必须等所有匹配到的元素都加载出来才通过
其三:element_to_be_clickable
def element_to_be_clickable(mark):
    """
    An Expectation for checking an element is visible and enabled such that
    you can click it.

    element is either a locator (text) or an WebElement
    """

    # renamed argument to 'mark', to indicate that both locator
    # and WebElement args are valid
    def _predicate(driver):
        target = mark
        if not isinstance(target, WebElement):  # if given locator instead of WebElement
            target = driver.find_element(*target)  # grab element at locator
        target = visibility_of(target)(driver)
        if target and target.is_enabled():
            return target
        else:
            return False

    return _predicate

是等待页面元素可见的时候操作,会设置一定范围的时间,如果在时间范围内,元素可见,就

执行操作,元素不可见,就会引发TimeoutException的异常。