同福

Python的selenium实现等待某个元素加载完成后返回结果【20210414】

介绍

介绍

福哥在使用selenium的时候遇到了一个问题,就是页面有些元素和数据是通过AJAX渲染的,而且采用的是异步加载的方式实现的AJAX功能,这样在selenium认为页面已经加载完成了的时候其实数据还没有渲染上,这个可愁坏福哥了!

经过研究发现selenium有两个功能就是可以实现福哥想要的效果,一个是WebDriverWait对象,另一个是implicitly_wait方法,它们都可以达到建立一个监视器,等待元素加载到页面上之后立即捕获并返回的目的。

另外福哥还发现使用time对象的sleep方法也可以实现这个效果,而且自由度非常高,特别适合处理复杂的情况。

这些技巧大家跟着福哥来一起学习一下吧~~

AJAX页面

福哥弄了一个AJAX异步延迟渲染的页面,保证正常情况下selenium绝对无法获得渲染后的内容,拿这个来做实验!

home/topic/2021/0414/22/7caf4932a1d3a41046ef6f3ebae90363.jpg

直接爬取

使用下面的代码直接爬取页面,很显然异步延迟的AJAX渲染的数据是拿不到的。

chrome.get("http://192.168.2.168/tfams/test.html")
print(chrome.find_element_by_css_selector("p#content-in-ajax").text)

home/topic/2021/0414/22/0270511123e499a594087b3d8d1e2b71.jpg

WebDriverWait

使用WebDriverWait对象来实现等待元素加载完毕的监视是一个比较理想的方法,我们可以设置一个最长等待时间,然后通过EC对象的presence_of_element_located方法进行预期元素的查找尝试,如果查找成功那么WebDriverWait的until方法就会立即返回,否则的话WebDriverWait的until方法会在达到最长等待时间之前一直等待着,超过最长等待时间的时候就会抛出异常错误。

看完WebDriverWait的工作原理之后,福哥惊呼这不就是我们想要的东西吗?

安装

需要引入三个库,都是selenium.webdriver下面的库。

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

示例

这是一个使用WebDriverWait实现等待AJAX加载完毕的示例。

chrome.get("http://192.168.2.168/tfams/test.html")
try:
    WebDriverWait(chrome, 10).until(EC.presence_of_element_located((By.CSS_SELECTOR, "p#content-in-ajax h3")))
except Exception as e:
    doNothing = e
print(chrome.find_element_by_css_selector("p#content-in-ajax").text)

看!这下内容爬取到了哦~~

因为示例当中的p标签的内容来自AJAX,而内容是复合型的HTML结构,我们无法判断p标签是否为空,只能抓p标签下面的一个子元素h3作为加载完毕的标志,再去取整个p标签了!

home/topic/2021/0414/22/8d535e0a80da81d2e22d8adb065151df.jpg

implicitly_wait

使用implicitly_wait方法来实现等待元素加载完毕也还可以,在通过implicitly_wait设置了等待时间之后,每一次通过find_xxx方法查找元素的时候都会进行查找尝试,并且在最长等待时间之前查找成功就可以。

也就是说,我们在设置了implicitly_wait之后再去调用find_xxx方法去查找元素的时候,如果元素没有找到find_xxx并不会返回None而是反复进行查找尝试,在通过implicitly_wait设置的最长等待时间到达之前查找成功就会立即返回,超过了通过implicitly_wait设置的最长等待时间的时候就会抛出异常错误。

示例

这是一个使用implicitly_wait实现等待AJAX加载完毕的示例。

chrome.implicitly_wait(10)
chrome.get("http://192.168.2.168/tfams/test.html")
try:
    chrome.find_element_by_css_selector("p#content-in-ajax h3")
except Exception as e:
    doNothing = e
print(chrome.find_element_by_css_selector("p#content-in-ajax").text)

看!这样也可以爬取到哦~~

因为示例当中的p标签的内容来自AJAX,而内容是复合型的HTML结构,我们无法判断p标签是否为空,只能抓p标签下面的一个子元素h3作为加载完毕的标志,再去取整个p标签了!

home/topic/2021/0414/22/8d535e0a80da81d2e22d8adb065151df.jpg

time.sleep

最后一个是使用time对象的sleep方法进行人工延迟,具体实现方法是通过循环语句进行计时,并且在每次循环里手动检查结果是否完成,如果结果完成了就跳出。

网上说这是最蠢的方案,福哥并不认同这个观点。

在实际的项目当中,如果场景是AJAX程序处理完毕后会动态渲染出一个元素出来,这种情况当然可以使用WebDriverWait对象或者implicitly_wait方法进行查找。但是,假如场景是AJAX程序处理完毕后会动态删除一个元素,又或是改变一个元素的属性、样式,那么通过WebDriverWait对象或者implicitly_wait方法就不灵了。

不过,这种情况下使用time.sleep却可以解决,因为我们可以用最原始的方式,每次计时循环通过execute_script方法去检查元素是不是被删除了,又或是通过execute_script方法去检查元素的属性、样式是不是改变了,这样就可以解决问题了!

安装

使用time.sleep需要导入time库,这个是系统自带的库,不需要单独安装。

import time

示例1

这是一个使用time.sleep实现等待AJAX加载完毕的示例。

chrome.get("http://192.168.2.168/tfams/test.html")
waitSecs = 10
while waitSecs > 0:
    try:
        chrome.find_element_by_css_selector("p#content-in-ajax h3")
        break
    except Exception as e:
        doNothing = e
    waitSecs = waitSecs-1
    time.sleep(1)
print(chrome.find_element_by_css_selector("p#content-in-ajax").text)

代码有点复杂是不是?没有关系,下面福哥再来一个只有time.sleep可以干的事情的例子。

示例2

这是一个使用time.sleep实现等待AJAX加载完毕的示例,条件是不能预判AJAX加载的内容包含什么元素,也就是说我们不能再去判断p标签下面有没有h3标签了。

这下可怎么办呢?

chrome.get("http://192.168.2.168/tfams/test.html")
waitSecs = 10
while waitSecs > 0:
    isEmptyTagP = chrome.execute_script("return $.trim($('p#content-in-ajax').text()) == '' ? true : false;")
    if isEmptyTagP == False:
        break
    waitSecs = waitSecs-1
    time.sleep(1)
print(chrome.find_element_by_css_selector("p#content-in-ajax").text)

看到了吗?福哥自定义了一个逻辑,在当前页面执行JS脚本判断p标签下面是不是空的,如果不是空的就证明AJAX渲染完成了,这个过程完全不需要知道AJAX会加载什么内容出来都可以实现。

总结

今天大家跟着福哥学会了在使用selenium抓取网页的时候,可以根据自己的情况在AJAX异步渲染的某个元素加载完成之后再返回结果,这样可以实现准确地判断那些异步加载数据的页面的加载完成时间,获得我们预期的结果。