介绍
介绍
福哥在使用selenium的时候遇到了一个问题,就是页面有些元素和数据是通过AJAX渲染的,而且采用的是异步加载的方式实现的AJAX功能,这样在selenium认为页面已经加载完成了的时候其实数据还没有渲染上,这个可愁坏福哥了!
经过研究发现selenium有两个功能就是可以实现福哥想要的效果,一个是WebDriverWait对象,另一个是implicitly_wait方法,它们都可以达到建立一个监视器,等待元素加载到页面上之后立即捕获并返回的目的。
另外福哥还发现使用time对象的sleep方法也可以实现这个效果,而且自由度非常高,特别适合处理复杂的情况。
这些技巧大家跟着福哥来一起学习一下吧~~
AJAX页面
福哥弄了一个AJAX异步延迟渲染的页面,保证正常情况下selenium绝对无法获得渲染后的内容,拿这个来做实验!
直接爬取
使用下面的代码直接爬取页面,很显然异步延迟的AJAX渲染的数据是拿不到的。
chrome.get("http://192.168.2.168/tfams/test.html") print(chrome.find_element_by_css_selector("p#content-in-ajax").text)
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标签了!
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标签了!
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异步渲染的某个元素加载完成之后再返回结果,这样可以实现准确地判断那些异步加载数据的页面的加载完成时间,获得我们预期的结果。