Python爬虫第17节-动态渲染页面抓取之Selenium使用下篇
目录
引言
一、获取节点信息
1.1 获取属性
1.2 获取文本值
1.3 获取ID、位置、标签名、大小
二、切换Frame
三、延时等待
3.1 隐式等待
3.2 显式等待
四、前进后退
五、Cookies
六、选项卡管理
七、异常处理
引言
这一节我们继续讲解Selenium的使用下篇,在利用Selenium进行网页操作时,获取节点信息以及应对各种相关场景是关键环节。很多人起初习惯通过获取网页源代码,再用解析库提取信息,但其实Selenium自身就蕴含着诸多更便捷的方式。接下来,我们就深入了解下如何借助Selenium直接获取节点信息,以及处理Frame切换、等待、Cookies操作等一系列实用技巧。
一、获取节点信息
前文提到,通过page_source属性可获取网页源代码,进而使用正则表达式、Beautiful Soup、pyquery等解析库提取信息。
然而,既然Selenium已提供选择节点的方法,且返回的是WebElement类型,那么它必然也具备直接提取节点属性、文本等信息的方法和属性。如此一来,我们便无需通过解析源代码来获取信息,操作更加便捷。
接下来,让我们看看如何获取节点信息。
1.1 获取属性
如果想要获取网页中某个节点的属性值,我们可以运用`get_attribute()`方法来达成这一目的。不过需要注意的是,在使用这个方法之前,必须要先将目标节点选中才行。下面为你展示具体的示例:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChainsbrowser = webdriver.Chrome()
url = 'https://www.baidu.com/'
browser.get(url)# 旧版API
# logo = browser.find_element_by_id('zh-top-link-logo')
# 新版API
logo = browser.find_element(By.ID, 'lg')print(logo)
print(logo.get_attribute('class'))
当执行这段代码时,程序将自动启动浏览器并访问百度页面。随后,程序会在页面中定位到百度的logo节点,进而获取并打印出该节点的相关信息,以及它的`class`属性值。在控制台中呈现的输出结果如下:
使用`get_attribute()`方法时,只要在调用该方法的过程中传入你期望获取的节点属性名称,就可以顺利地获取到该属性所对应的值。
1.2 获取文本值
对于每一个`WebElement`类型的节点而言,它们都具备`text`这一属性。在实际操作中,我们仅需直接调用该属性,就能够获取到这个节点内部所包含的文本信息。这种获取文本信息的方式,和`Beautiful Soup`库中的`get_text()`方法,以及`pyquery`库中的`text()`方法的作用类似。下面给出具体的示例来进行说明:
from selenium import webdriver
from selenium.webdriver.common.by import By
import timebrowser = webdriver.Chrome()
url = 'https://www.zhihu.com/signin?next=%2F'
browser.get(url)# 等待页面加载
time.sleep(2)# 新版API
# 不能处理带空格,对于包含多个类名的元素,我们应该使用 CSS 选择器。
#button = browser.find_element(By.CLASS_NAME, '')# 新版API - 使用CSS选择器
button = browser.find_element(By.CSS_SELECTOR, '.Button.SignFlow-submitButton')print(button.text)
这段代码的执行逻辑是这样的:首先,它会启动并打开知乎的页面。页面加载完毕后,代码会在页面中找到“登录注册”按钮对应的节点。在成功获取该节点之后,代码便会将这个节点的文本值打印输出。
控制台打印结果为:
登录/注册
1.3 获取ID、位置、标签名、大小
除了前面提到的属性之外,`WebElement`节点还拥有一些其他非常实用的属性。就像`id`属性,通过它可以获取到节点对应的`id`标识;`location`属性则专门用来获取该节点在网页页面中所处的相对位置;`tag_name`属性能够让我们得知节点的标签名称;而`size`属性可以获取到节点的尺寸大小,也就是它的宽度和高度信息。下面通过具体的示例来展示这些属性的使用方法:
from selenium import webdriver
from selenium.webdriver.common.by import By
import timebrowser = webdriver.Chrome()
url = 'https://www.zhihu.com/signin?next=%2F'
browser.get(url)# 等待页面加载
time.sleep(2)# 新版API
# 不能处理带空格,对于包含多个类名的元素,我们应该使用 CSS 选择器。
#button = browser.find_element(By.CLASS_NAME, '')# 新版API - 使用CSS选择器
button = browser.find_element(By.CSS_SELECTOR, '.Button.SignFlow-submitButton')print(button.text)
print(button.id)
print(button.location)
print(button.tag_name)
print(button.size)
输出结果:
上述代码首先获取“登录注册”按钮这个节点,接着调用其`id`、`location`、`tag_name`、`size`属性来获取相应的属性值。通过这些属性,我们能更全面地了解节点在页面中的特征和位置信息,有助于更精准地进行页面元素的操作和分析 。
二、切换Frame
在网页里,有一种节点叫做`iframe`,也就是子Frame。它就像是页面里嵌套的子页面,有着和外部网页一样的结构。当使用Selenium打开网页时,默认是在父级Frame的环境下进行操作的。要是页面中有子Frame,直接操作的话,是没办法获取到子Frame里面的节点的。要是想操作子Frame里的内容,就得用`switch_to.frame()`方法来切换Frame。下面是具体的示例:
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementExceptionbrowser = webdriver.Chrome()
url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
browser.get(url)browser.switch_to.frame('iframeResult')
try:# 旧版API# logo = browser.find_element_by_class_name('logo')# 新版APIlogo = browser.find_element(By.CLASS_NAME, 'logo')
except NoSuchElementException:print('NO LOGO')
browser.switch_to.parent_frame()# 旧版API
# logo = browser.find_element_by_class_name('logo')
# 新版API
logo = browser.find_element(By.CLASS_NAME, 'logo')print(logo)
print(logo.text)
控制台输出:
上述代码以之前演示动作链操作的网页作为示例。一开始,利用`switch_to.frame()`方法进入子Frame,接着尝试查找子Frame里的`logo`节点。但实际上,子Frame中并没有这个节点,这就会触发`NoSuchElementException`异常。捕获到这个异常后,程序会输出`NO LOGO`。之后,使用`switch_to.parent_frame()`方法返回父级Frame,再次去获取`logo`节点,这时就能够成功获取到该节点,并且打印出节点及其文本内容。
由此可知,当网页包含子Frame时,如果要获取子Frame内的节点,一定要先使用`switch_to.frame()`方法切换到相应的Frame,之后才能继续进行后续操作。不然,可能会因为找不到节点而致使操作失败。
三、延时等待
在Selenium里,`get()`方法会在网页框架加载结束时执行完毕。不过,这时获取的`page_source`,有可能不是浏览器完全加载好的页面内容。要是页面有额外的Ajax请求,仅靠网页源代码或许无法成功获取相关数据。所以,得设置延时等待,保证所需的节点都已经加载出来。
延时等待的方式主要有两种,分别是隐式等待和显式等待。
3.1 隐式等待
当运用隐式等待开展测试工作时,要是Selenium在文档对象模型(DOM)里找不到指定的节点,它不会马上停止,而是会继续等待。要是超过了预先设定的时间,还是没找到节点,就会抛出节点未找到的异常。简单来讲,在查找节点时,如果这个节点没有立刻出现,隐式等待会让程序等待一段时间之后,再去查找DOM中的节点。需要注意的是,隐式等待的默认等待时间是0。下面是具体的示例:
from selenium import webdriver
from selenium.webdriver.common.by import By
import timebrowser = webdriver.Chrome()
browser.implicitly_wait(10)
url = 'https://www.zhihu.com/signin?next=%2F'
browser.get(url)# 新版API
# 不能处理带空格,对于包含多个类名的元素,我们应该使用 CSS 选择器。
#button = browser.find_element(By.CLASS_NAME, '')# 新版API - 使用CSS选择器
button = browser.find_element(By.CSS_SELECTOR, '.Button.SignFlow-submitButton')print(button)
在上述代码里,借助`implicitly_wait()`方法将隐式等待时间设定成了10秒。这意味着当Selenium查找节点却未能立即找到时,会持续等待10秒,若10秒内节点加载出来则继续执行后续操作,若超过10秒仍未找到节点,就会抛出节点未找到的异常。
3.2 显式等待
隐式等待在实际应用中效果并非总能尽如人意,这是因为它所设置的等待时间是固定的。然而,页面的加载时间会受到网络状况、服务器响应等多种因素的影响,固定的等待时间难以适配各种复杂情况。
相比之下,显式等待就要灵活得多。它允许我们明确指定要查找的节点,同时设定最长的等待时间。在规定的时间范围内,如果节点成功加载完成,程序就会返回该节点;但要是超过了设定时间,节点依旧没有加载出来,程序便会抛出超时异常。下面通过一个示例来具体说明:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ECbrowser = webdriver.Chrome()
browser.get('https://www.taobao.com/')# 等待页面加载
wait = WebDriverWait(browser, 10)# 定位搜索输入框
# 使用ID定位更准确
input = wait.until(EC.presence_of_element_located((By.ID, 'q')))# 定位搜索按钮
# 使用更精确的CSS选择器
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'button.btn-search.tb-bg')))print("搜索框:", input)
print("搜索按钮:", button)
print("搜索框类型:", input.get_attribute('type'))
print("搜索按钮文本:", button.text)
在这段代码里,首先引入了`WebDriverWait`对象,并且把最长等待时间设定成了10秒。接着调用`until()`方法,同时传入等待条件`expected_conditions`。就拿`presence_of_element_located`这个条件来说,它代表节点已经完成加载,其参数是节点的定位元组,这里指的是ID为`q`的搜索框节点。也就是说,在10秒的时间内,要是ID为`q`的节点成功加载出来,程序就会返回这个节点;要是超过10秒还没加载好,就会抛出异常。
对于按钮节点,等待条件被设置为`element_to_be_clickable`(节点可点击),也就是去查找CSS选择器为`.btn - search`的按钮。若在10秒内这个按钮变得可以点击(意味着已经成功加载),程序就会返回该按钮节点;若超过10秒按钮还是不可点击(即未加载完成),同样会抛出异常。
当网络状况良好时,运行这段代码能够成功加载并输出这两个节点。具体的控制台输出如下:
控制台输出如下:
搜索框: <selenium.webdriver.remote.webelement.WebElement (session="958f257d68f99e09f2e00835aa7eea4d", element="f.EF316286D5F2BC448BCF689697FBF6B0.d.ECC671338A30A18D014C352C3583444B.e.2")>
搜索按钮: <selenium.webdriver.remote.webelement.WebElement (session="958f257d68f99e09f2e00835aa7eea4d", element="f.EF316286D5F2BC448BCF689697FBF6B0.d.ECC671338A30A18D014C352C3583444B.e.12")>
搜索框类型: text
搜索按钮文本: 搜索
一旦网络状态不好,在那预先设定的10秒时间内,节点无法顺利完成加载,这种情况下程序就会抛出`TimeoutException`异常。
此外,等待条件的类型其实是多种多样的,除了前面提到的那些,还可以用来判断网页的标题内容,或者查看某个节点内部是不是包含特定的文字信息等等。所有的等待条件以及它们对应的含义,都详细地罗列在了下表当中。
等待条件及其含义
若你想了解更多关于等待条件参数的详细信息以及它们的具体用法,可查阅官方文档,链接为:(http://selenium - python.readthedocs.io/api.html#module - selenium.webdriver.support.expected_conditions)。在该文档中你能获取更全面和深入的讲解。
四、前进后退
在日常使用浏览器的过程中,前进和后退功能是我们经常会用到的。而Selenium也提供了对这些操作的支持。在Selenium里,通过调用`back()`方法就能实现浏览器页面的后退操作,使用`forward()`方法则可以实现页面的前进操作。下面为你展示具体的示例代码:
import time
from selenium import webdriverbrowser = webdriver.Chrome()
browser.get('https://www.baidu.com/')
browser.get('https://www.taobao.com/')
browser.get('https://www.python.org/')
browser.back()
time.sleep(1)
browser.forward()
browser.close()
在上述代码里,程序首先会依次访问百度、淘宝以及Python官网这三个网页。接着调用`back()`方法,此时浏览器会向后退回到淘宝页面。在等待1秒钟之后,再调用`forward()`方法,浏览器便会前进到Python官网页面,最后关闭浏览器。借助这样的操作方式,我们能够在Selenium所驱动的浏览器中模拟用户在浏览网页时前进和后退的操作。
五、Cookies
借助Selenium,我们可以非常方便地对Cookies进行各类操作,像获取Cookies信息、添加新的Cookies以及删除指定的Cookies等。下面是具体的示例代码,它将展示如何使用Selenium来完成这些操作:
from selenium import webdriverbrowser = webdriver.Chrome()
browser.get('https://www.zhihu.com/explore')
print(browser.get_cookies())
browser.add_cookie({'name': 'name', 'domain': 'www.zhihu.com', 'value': 'germey'})
print(browser.get_cookies())
browser.delete_all_cookies()
print(browser.get_cookies())
在代码执行时,第一步是访问知乎页面。当页面加载完毕后,浏览器会自动生成Cookies。此时调用`get_cookies()`方法,就能获取到页面所有的Cookies信息并打印输出。
随后,使用`add_cookie()`方法来添加一个新的Cookie,该方法需要传入一个字典,字典里要包含`name`(名称)、`domain`(域)和`value`(值)等必要信息。再次调用`get_cookies()`方法时,输出结果中会显示新增了刚刚添加的那个Cookie。
最后,执行`delete_all_cookies()`方法,这个操作会把所有的Cookies都删除。当再次获取Cookies时,得到的结果就会为空。
下面是控制台可能出现的输出情况:
没错,利用上述提到的`get_cookies()`、`add_cookie()`和`delete_all_cookies()`等方法,可轻松对浏览器里的Cookies进行管理和操作。无论是在测试环境中模拟用户登录状态,还是在数据采集时处理特定的用户标识信息,这些操作都能灵活满足不同场景下的数据处理需求,提升自动化测试与数据处理的效率和准确性。
六、选项卡管理
在日常浏览网页过程中,我们经常会同时打开多个选项卡来提高浏览效率。在Selenium里,也具备对选项卡进行操作的能力。下面是一个示例,为你展示如何在Selenium中操作选项卡:
import time
from selenium import webdriverbrowser = webdriver.Chrome()
browser.get('https://www.baidu.com')# 打开新窗口
browser.execute_script('window.open()')
print(browser.window_handles)# 切换到新窗口
# 旧版API
# browser.switch_to_window(browser.window_handles[1])
# 新版API
browser.switch_to.window(browser.window_handles[1])
browser.get('https://www.taobao.com')
time.sleep(1)# 切换回第一个窗口
# 旧版API
# browser.switch_to_window(browser.window_handles[0])
# 新版API
browser.switch_to.window(browser.window_handles[0])
browser.get('https://python.org')
控制台输出如下:
在上述代码中,整体操作模拟了用户在浏览器中对多个选项卡进行管理的场景。具体而言,一开始打开百度页面,之后借助`execute_script()`方法执行`window.open()`这条JavaScript语句,从而新开一个选项卡。`window_handles`属性发挥了重要作用,调用它可以获取当前所有已打开选项卡的信息,它会返回一个包含各选项卡代号的列表。
要想实现选项卡之间的切换,新版switch_to.window方法就派上用场了,只需把目标选项卡的代号作为参数传入即可。在示例里,先将第二个选项卡的代号传入该方法,成功切换到第二个选项卡后,在这个选项卡中打开了淘宝页面。等待1秒之后,又把第一个选项卡的代号传入switch_to.window 方法,从而切换回第一个选项卡,并在该选项卡中打开了Python官网页面。
通过这样的操作方式,能够在Selenium中灵活地对多个选项卡进行管理,完美模拟了用户在实际浏览网页时于不同页面间进行切换的操作。
七、异常处理
在运用Selenium开展自动化测试或者网页操作时,各种异常情况难以避免,像超时异常、节点未找到异常等错误都可能出现。一旦碰到这类错误,程序往往会停止运行。为了防止程序因为异常而中断,我们可以借助`try - except`语句来捕获并处理各种异常。
下面先通过一个节点未找到的异常示例,来看看如何运用`try - except`语句进行异常处理:
from selenium import webdriver
from selenium.webdriver.common.by import Bybrowser = webdriver.Chrome()
browser.get('https://www.baidu.com')# 旧版API
# browser.find_element_by_id('hello')
# 新版API
browser.find_element(By.ID, 'hello')
上述代码在打开百度页面之后,试图去查找一个ID为`hello`的节点,然而这个节点实际上并不存在于页面中,所以会触发异常。当代码运行起来,控制台就会输出相应的错误信息,下面是具体的输出情况:
从输出情况能够看出,代码抛出了`NoSuchElementException`异常,一般而言,这意味着在页面中没有找到指定的节点。为了避免程序因为这样的异常而中断运行,我们可以采用`try - except`语句来捕获并处理异常。下面是具体的示例代码:
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementExceptionbrowser = webdriver.Chrome()
try:browser.get('https://www.baidu.com')
except TimeoutException:print('Time Out')
try:browser.find_element(By.ID, 'hello')
except NoSuchElementException:print('No Element')
finally:browser.close()
在这段代码里,`try - except`语句发挥了重要作用,它能够捕获不同操作时可能出现的异常。针对`get()`方法,代码捕获`TimeoutException`异常,一旦出现超时情况,就会输出`Time Out`。而对于`find_element_by_id()`方法,捕获的是`NoSuchElementException`异常,要是没有找到指定的节点,便会输出`No Element`。
另外,代码里的`finally`块保证了无论是否有异常发生,浏览器最终都会被关闭。下面是控制台可能出现的输出内容:
No Element
要是还想了解更多Selenium里的异常类,能去参考官方文档,链接是:(http://selenium - python.readthedocs.io/api.html#module - selenium.common.exceptions) 。
上面把Selenium的各项功能都介绍了一遍,到这儿,咱们对它的常用操作方法也都清楚了。有了Selenium,处理JavaScript动态渲染的页面就简单多了。在网页数据抓取、自动化测试这些工作中,Selenium可是个既强大又好用的工具。不管是做简单的页面操作,还是获取复杂的动态数据,Selenium都能派上大用场,让我们高效完成任务。往后,大家可以根据自己实际的需求,进一步去研究和使用Selenium的更多高级功能,来解决不同场景下网页数据处理和测试的问题 。