第四章 等待与延迟

延时问题

先来回顾下之前的通过百度访问白月黑羽在线教程的例子,代码如下:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from selenium import  webdriver
import time

# Load webdriver
driver = webdriver.Chrome(r"G:\Education\BYHY\Courseware\Improvement\Selenium\chromedriver.exe")

# open URL
driver.get('https://www.baidu.com/')

# search input box element by id and return a web element instance object
eleObj_keyword = driver.find_element_by_id('kw')

#input characters
eleObj_keyword.send_keys('白月黑羽')

#find search button
eleObj_search_button = driver.find_element_by_id('su')

# click the button
eleObj_search_button.click()

#==================================================
time.sleep(1)

eleObj = driver.find_element_by_link_text('python教程 - 白月黑羽教Python').click()

#==================================================

input('Please input any key to continue......')
# Quit
driver.quit()

大家可以看到上面的程序在执行到下面的语句之后

# click the button
eleObj_search_button.click()

紧接着执行

time.sleep(1)

程序执行到在这里,强制让解释器等待1秒后再继续执行后面的程序,那为什么要强制等待呢?因为解释器执行的很快,click操作不会等界面刷新完成才返回,而是立即返回,但是,这时搜索的结果还没有出现,如果继续执行后面find_element的语句,就会出现了NoSuchElementException的异常。

延时建议

那是不是像这种需要等待返回结果,或者页面刷新的时候,就可以用sleep等待一段时间呢?我们的建议是,虽然sleep可以解决大多数问题,但不是最好的方法,也不建议大家这样做,为什么呢?主要基于以下考虑:

  1. 无法确定需要等多长时间。像百度网站反应可能比较快,设置等了一秒钟就可以了,但是谁知道下次他的反应是不是还这么快呢?百度也曾经出现过服务器瘫痪的事情的。
  2. 无法确定是网络的原因还是网站服务的原因。比如整个网络不稳定,或者小区网速较慢,这时候就不能确定多久才会响应

Selenium的延时方案

selenium 提供了的解决问题的办法是: 不管前面的操作是否会导致页面刷新,在后面定位元素的时候, 当发现元素没有找到的时候, 不立即返回错误,而是周期性(每隔半秒钟)重新寻找该元素,直到该元素找到(返回),或者超出指定最大等待时长(返回空列表或者抛出异常)。

比如下面的代码:

eleObj = driver.find_element_by_id('1')

这种方式,就不管点击是否会导致页面更新,也不管更新花了了多长时间(只要是小于指定的最大时长),后面的寻找元素的操作都可以正常进行。这种周期性等待的方案,selenium里面有两个实现方法

隐式等待

隐式等待是利用WebDriver 对象的implicitly_wait方法。 这个方法只有一个参数,该参数用来指明等待的最长时间。比如

driver.implicitly_wait(3)

因为这个是一个全局的设定,设定好了以后,后面所有选择元素的代码都不需要单独的设定周期性等待了,对于后面每个操作来说,像隐含了这种等待的行为, 所以叫隐式等待。

这是我强烈推荐的方法,只要创建webdriver对象,就可以加上implicitly_wait。如果用隐式等待,上面的例子我们就可以这样:

driver = webdriver.Chrome(r"G:\Education\BYHY\Courseware\Improvement\Selenium\chromedriver.exe")
driver.implicitly_wait(3)

去掉程序中所有的

time.sleep(1)

显式等待

假如我们在对web服务进行验证的时候,里面有一个很特别的操作,是需要等待很长时间才能得到web服务的响应,比如统计服务器全年各个用户的访问次数,这个就需要很长时间,特别是用户上百万级的时候,就需要更长的时间。这里假设需要20分钟,才能有返回结果,如果我们仍然用隐式等待,将其设为20分钟,那显然是很不合理,那如何解决这种应用场景呢?

Selenium也专门为这种提供了一种方法来专门指定等待时间,我们称之为:显式等待,代码如下:

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
eleObj = WebDriverWait(driver, 60*20).until(EC.presence_of_element_located((By.ID,'username')))

但实际的工作中,遇到这种情况,还可以采用隐式等待,但是不是设置全局等待时间,而是临时改变显式等待的时间的方法来实现延迟,也是可以的,类似这样

driver.implicitly_wait(60*20)
ele = driver.find_element_by_id('1')
driver.implicitly_wait(3)

项目实战

  1. 访问百度
  2. 搜索“天气预报”
  3. 获取周边城市的最新天气情况
  4. 找出周边城市气温最高的城市
#! /usr/bin/env python
# -*- coding: utf-8 -*-

from selenium import  webdriver
import  selenium, time

driver = webdriver.Chrome(r"G:\Education\BYHY\Courseware\Improvement\Selenium\chromedriver.exe")
driver.implicitly_wait(3)
# ================================================
# open URL
driver.get('https://www.baidu.com/')

element_keyword = driver.find_element_by_id('kw') .send_keys('天气预报')
element_search_button = driver.find_element_by_id('su').click()
#print(driver.title)

eleObj = driver.find_element_by_id('1')
eleObj = driver.find_element_by_partial_link_text('中国天气网').click()
#print(driver.title)

# 切换窗口
print('before: ' + driver.current_window_handle)
print(driver.title)
mainWindow = driver.current_window_handle
for handle in driver.window_handles:
    print(handle.title())
    driver.switch_to.window(handle)
    if '天气预报一周' in driver.title:
        break
print('after: ' + driver.current_window_handle)
print(driver.title)

eleObj = driver.find_element_by_id('around')

aroundCityHighTemList = []
eleObjList = eleObj.find_elements_by_tag_name('li')
for li in eleObjList:
    cityName = li.find_element_by_tag_name('span').text
    tem = li.find_element_by_tag_name('i').text
    if cityName == '':
        continue
    print(f"{cityName}: {tem}")
    highTem = int(tem.replace('°C','').split('/')[0])
    lowTem = int(tem.replace('°C', '').split('/')[0])

    aroundCityHighTemList.append([cityName,highTem])

highestTem  = 0
highestTemCitys = []
for one in aroundCityHighTemList:
    if one[0] == '':
        continue
    curCityName = one[0]
    curCityHighTem = one[1]

    if highestTem < curCityHighTem:
        highestTem = curCityHighTem
        highestTemCitys = [curCityName]
    elif highestTem == curCityHighTem:
        highestTemCitys.append(curCityName)
print(f'温度最高的城市为:{highestTemCitys},最高温度为:{highestTem}')

#================================================
input('Please input any key to continue......')
# Quit
driver.quit()


本文还不错? 分享给你的朋友吧

上一页 下一页