爬虫学习笔记(十六)—— Selenium
Posted 别呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了爬虫学习笔记(十六)—— Selenium相关的知识,希望对你有一定的参考价值。
Selenium是一个主要用于Web应用程序自动化测试的工具集合,在行业内已经得到广泛的应用。
文章目录
一、简介
1.1、作用
- 自动化测试
通过它,我们可以写出自动化程序,模拟浏览器里操作web界面。 比如点击界面按钮,在文本框中输入文字等操作。 - 获取信息
而且还能从web界面获取信息。 比如招聘网站职位信息,财经网站股票价格信息 等等,然后用程序进行分析处理。
1.2、运行环境
Selenium测试直接运行在浏览器中,就好像一个真正的用户在操作一样, 支持大部分主流的浏览器,包括IE,Firefox,Safari,Chrome,Opera等。
我们可以利用它来模拟用户点击访问网站,绕过一些复杂的认证场景。
通过selnium+驱动浏览器这种组合可以直接渲染解析js,绕过大部分的参数构造和反爬。
1.3、注意事项
新版本的Selenium已经不在支持phantomjs,原作者也已经放弃维护该项目了。
还有在做爬虫的时候尽量不要用这种方法,Selenium+浏览器的组合速度慢,应付不了数据量比较大的爬取以及并发爬取。并且很吃电脑资源。
二、基本使用
2.1、原理
1. WebDriver API(基于Java、Python、C#等语言)
对于Python语言来说,就是下载下来的selenium库。
2. 浏览器的驱动(browser driver)
每个浏览器都有自己的驱动,均以exe文件形式存在
https://chromedriver.storage.googleapis.com/index.html
比如谷歌的chromedriver.exe、火狐的geckodriver.exe、IE的IEDriverServer.exe
3. 浏览器
浏览器当然就是我们很熟悉的常用的各种浏览器。
那在WebDriver脚本运行的时候,它们之间是如何通信的呢?为什么同一个browser driver即可以处理java语言的脚本,也可以处理python语言的脚本呢?
让我们来看一下,一条Selenium脚本执行时后端都发生了哪些事情:
- 对于每一条Selenium脚本,一个http请求会被创建并且发送给浏览器的驱动
- 浏览器驱动中包含了一个HTTP Server,用来接收这些http请求
- HTTP Server接收到请求后根据请求来具体操控对应的浏览器
- 浏览器执行具体的测试步骤
- 浏览器将步骤执行结果返回给HTTP Server
- HTTP Server又将结果返回给Selenium的脚本,如果是错误的http代码我们就会在控制台看到对应的报错信息。
2.2、安装
2.2.1、selenium安装
终端输入命令:
pip install selenium
2.2.2、浏览器驱动安装
chrome驱动下载地址:https://chromedriver.storage.googleapis.com/index.html
注意:每个驱动该对应每个浏览器;有时候浏览器会自动升级,导致浏览器不可用;
2.3、元素选取
2.3.1、find_element(s)by_…方法
在一个页面中有很多不同的策略可以定位一个元素。我们可以选择最合适的方法去查找元素。Selenium提供了下列的方法:
单个元素查找方法 | 作用 |
---|---|
find_element_by_xpath() | 通过Xpath查找 |
find_element_by_class_name() | 通过class属性查找 |
find_element_by_id() | 通过id属性查找 |
find_element_by_name() | 通过name属性进行查找 |
find_element_by_css_selector() | 通过css选择器查找 语法规则 |
find_element_by_link_text() | 通过链接文本查找 |
find_element_by_partial_link_text() | 通过链接文本的部分匹配查找 |
find_element_by_tag_name() | 通过标签名查找 |
注: 其中的element加上一个s,则是对应的多个元素的查找方法.
示例:
注意:webdriver.Chrome()
的参数是你驱动的位置,当然如果有放到python文件的Scripts下或者有添加到环境变量中就可以不用写
from selenium import webdriver
wb = webdriver.Chrome(r'D:\\SoftWare\\Python\\Python36\\Scripts\\chromedriver.exe')
wb.get('https://www.51zxw.net/List.aspx?cid=451')
#点击“首页”
#通过Xpath查找
home_page =wb.find_element_by_xpath('//div[@class="headLinks ml-10"]/a')
# 通过css选择器查找
# home_page =wb.find_element_by_css_selector('.headLinks a')
home_page.click()
结果展示:
2.3.2、By对象查找
By对象导入: from selenium.webdriver.common.by import By
除了以上的多种查找方式,还有两种私有方法集成了上面的所有的查找方法,让我们更方便的使用:
方法 | 作用 |
---|---|
find_element(By.XPATH, ‘//button/span’) | 通过Xpath查找一个 |
find_elements(By.XPATH, ‘//button/span’) | 通过Xpath查找多个 |
其中的第一个参数可以选择使用查找的方法,By.xxx
使用xxx方式解析,解析方法如下:
- ID:通过id属性查找
- XPATH :通过Xpath查找
- LINK_TEXT:通过链接文本查找
- PARTIAL_LINK_TEXT :通过链接文本的部分匹配查找
- NAME:通过name属性进行查找
- TAG_NAME:通过标签名查找
- CLASS_NAME:通过class属性查找
- CSS_SELECTOR :通过css选择器查找
示例:
from selenium import webdriver
from selenium.webdriver.common.by import By
wb = webdriver.Chrome() #驱动已经添加到环境变量,所以可以不用写参数
wb.get('https://www.51zxw.net/List.aspx?cid=451')
#通过Xpath查找
home_page = wb.find_element(By.XPATH,'//div[@class="headLinks ml-10"]/a')
home_page.click()
结果展示:
2.3.3、文本输入/提交
当我们需要通过selenium完成一个在网站中进行搜索的功能,前面我们已经知道了如何选取定位一个元素了,假如是定位到了一个输入框的元素,那么我们就要进行查询数据的输入和提交了。
方法 | 作用 |
---|---|
send_keys() | 文本输入 |
click() | 文本提交 |
示例:
from selenium import webdriver
from selenium.webdriver.common.by import By
wb = webdriver.Chrome()
wb.get('https://www.51zxw.net/List.aspx?cid=451')
#通过id属性定位到搜索框
home_page = wb.find_element_by_id("keyWordsT")
#搜索框输入 Python
home_page.send_keys('Python')
#定位到搜索按钮
submitbtn = wb.find_element_by_xpath('//button[@type="submit"]')
#点击搜索按钮
submitbtn.click()
结果展示:
三、动作切换
3.1、窗口切换
在开始讲解之前我们先来看一个示例:
from selenium import webdriver
from selenium.webdriver.common.by import By
wb = webdriver.Chrome()
wb.get('https://www.51zxw.net/List.aspx?cid=451')
print(wb.title)
openjs = 'window.open("https://www.csdn.net/")'
wb.execute_script(openjs)
print(wb.title)
结果:
程序开发-我要自学网
程序开发-我要自学网
通过结果我们可以看到,虽然浏览器给我们打开了第二个窗口并且停留在第二个窗口,但是其本质还是停留在第一个打开的窗口,因此我们需要进行窗口切换。
用selenium操作浏览器如果需要在打开新的页面,这个时候会有这个问题,因为我们用selenium操作的是第一个打开的窗口,所以新打开的页面我们是无法去操作的,所以我们要用到切换窗口:即handle切换的方法
方法 | 作用 |
---|---|
widgetjs = ‘window.open(“https://www.baidu.com”);’ chrome.execute_script(widgetjs ) | 打开新标签 |
window_handles | 获取所有页面窗口的句柄 |
current_window_handle | 获取当前页面窗口的句柄 |
switch_to.window(window_name) | 定位页面转到指定的window_name页面 |
注意:
- window_handles 的顺序并不是浏览器上标签的顺序,尽量避免多标签操作
示例:
from selenium import webdriver
wb = webdriver.Chrome()
wb.get('https://www.51zxw.net/List.aspx?cid=451')
print(wb.title) #当前打印窗口的标题
openjs = 'window.open("https://www.csdn.net/")'
wb.execute_script(openjs)
print(wb.title) #当前打印窗口的标题
print('所有页面窗口的句柄: ',wb.window_handles) #打印所有页面窗口的句柄
print('当前页面窗口的句柄: ',wb.current_window_handle) #获取当前页面窗口的句柄
wb.switch_to.window(wb.window_handles[1]) #通过上面打印比较 定位页面转到第二个窗口页面
print(wb.title) #获取当前页面窗口的句柄
结果:
程序开发-我要自学网
程序开发-我要自学网 #我们还没切换句柄,所以是还是第一个窗口
所有页面窗口的句柄: ['CDwindow-4ACAD14BE66BD92795930B70D8CA1FA7', 'CDwindow-C1D8821CFEBFE3A0C0AF456231EF2CB3']
当前页面窗口的句柄: CDwindow-4ACAD14BE66BD92795930B70D8CA1FA7
CSDN - 专业开发者社区
由于很难知道其他页面对应的handle是什么,如果只有少数的标签也许还能应付,但是当打开了相当多标签时就很难对他们进行处理了,所以要尽量避免多标签的操作。
3.2、页面(frame)切换
在实际的爬虫中,明明定位的路径没问题,这个时候我们可以考虑一下是否是该页面存在frame的问题导有时候我们会遇到找不到元素的问题致的定位不到元素。
知识点补充:什么是frame呢?
frame是一个框架标签,通过使用框架可以在一个浏览器窗口中显示不止一个页面,也就是说,在一个窗口中展示多个页面,每个页面称之为一个框架,并且每个框架独立于其他的框架。这个frame标签一共有三种,分别是frameset、frame、iframe,frameset跟其他普通标签没有区别,不会影响到正常的定位,而frame与iframe对selenium定位而言是一样的,selenium有一组方法对frame进行操作。reference是传入的参数,用来定位frame,可以传入id、name、index以及selenium的WebElement对象
方法 | 作用 |
---|---|
switch_to.frame(frame_reference) | 切到指定frame,可用id或name(str)、index(int)、元素(WebElement)定位 |
switch_to.parent_frame() | 切到父级frame,如果已是主文档,则无效果, 相当于后退回去 |
switch_to.default_content() | 切换到主页面,DOM树最开始的frame |
简单示例:
import time
from selenium import webdriver
wb = webdriver.Chrome()
wb.get('https://study.163.com/')
# 关闭页面弹出的小窗口
ok_btn = wb.find_element_by_xpath('//span[@class="ux-btn th-bk-main ux-btn- ux-btn- ux-modal-btn um-modal-btn_ok th-bk-main"]')
ok_btn.click()
# 登录窗口
login_btn = wb.find_element_by_xpath('//div[@class="go-login f-ib th-fs0fc6"]')
login_btn.click()
time.sleep(2) #先等2秒,代码执行过快,登录输入框加载需要时间,否则会出错
# 切换frame
fr = wb.find_element_by_xpath('//iframe[@frameborder="0"]')
wb.switch_to.frame(fr)
# 账号输入框输入
name_input = wb.find_element_by_id('phoneipt')
name_input.send_keys('123456789')
# 密码输入
pwd_input = wb.find_element_by_xpath('//input[@class="j-inputtext dlemail"]')
pwd_input.send_keys('10987654321')
结果展示:
3.2、页面弹窗
有的时候还会遇到弹窗的问题, 主要有两种一种是浏览器弹窗(alert/prompt),另一种是自定义弹窗。自定义弹窗,就是一个自定义的div层,是隐藏页面中的,当触发了这个弹窗后,他就显示出来,这种方式我们通过正常的定位方式是可以定位到的。
alert弹窗,就要用下面的方法处理:
方法 | 作用 |
---|---|
switch_to.alert | 定位到alert弹窗,返回一个弹窗的对象 |
dismiss() | 对弹窗对象的取消操作(相当于点击弹窗上的取消按钮) |
accept() | 对弹窗对象的确定操作(相当于点击弹窗上的确定按钮) |
send_keys(key) | 对弹窗对象内的输入框输入数据(针对于prompt弹窗) |
text | 获取弹窗内的文本 |
示例:
测试html代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>selenium</title>
</head>
<body>
<input id="alert" value="alert" type="button" onclick="alert('this is alert!!!')">
<input id="confirm" value="confirm" type="button" onclick="confirm('this is confirm!!!')">
<input id="prompt" value="prompt" type="button" onclick="var name = prompt('请输入用户名:','请输入');document.write(name)">
</body>
</html>
效果:
python代码:
import time
from selenium import webdriver
wb = webdriver.Chrome()
wb.get(r'D:\\html test\\一些示例\\ex.html')
# alert 弹窗
wb.find_element_by_id("alert").click()
alert_tag = wb.switch_to.alert
print(alert_tag.text)
time.sleep(2) #稍微延迟下,看得比较直观
alert_tag.accept()
# prompt 弹窗
#
# wb.find_element_by_id("prompt").click()
# prompt_tag = wb.switch_to.alert
# print(prompt_tag.text)
# prompt_tag.send_keys('hello world!!!')
# time.sleep(2) #稍微延迟下,看得比较直观
# prompt_tag.accept()
# confirm 弹窗
#
# wb.find_element_by_id("confirm").click()
# confirm_tag = wb.switch_to.alert
# print(confirm_tag.text)
# time.sleep(2) #稍微延迟下,看得比较直观
# confirm_tag.accept()
结果展示(这里我就只演示一个):
打印结果:
this is alert!!!
四、等待
4.1、简介
在selenium操作浏览器的过程中,每一次请求url,selenium都会等待页面加载完成以后,才会将操作权限在交给我们的程序。
但是,由于ajax和各种JS代码的异步加载问题,当一个页面被加载到浏览器时,该页面内的元素可以在不同的时间点被加载,这就使得元素的定位变得十分困难,当元素不再页面中时,使用selenium去查找的时候会抛出ElementNotVisibleException
异常。
为了解决这个问题,selenium提供了两种等待页面加载的方式,显示等待
和隐式等待
,让我们可以等待元素加载完成后在进行操作。
4.2、显式等待
显式等待: 显式等待指定某个条件,然后设置最长等待时间,程序每隔XX时间看一眼,如果条件成立,则执行下一步,否则继续等待,直到超过设置的最长时间,然后抛出超时异常(TimeoutException
)。
显示等待主要使用了WebDriverWait类
与expected_conditions模块
。
一般写法:
WebDriverWait(driver, timeout, poll_frequency, igonred_exceptions).until(method, message)
参数:
- Driver:传入WebDriver实例。
- timeout: 超时时间,等待的最长时间(同时要考虑隐性等待时间)
- poll_frequency: 调用until中的方法的间隔时间,默认是0.5秒
- ignored_exceptions: 忽略的异常,如果在调用until的过程中抛出这个元组中的异常,则不中断代码,继续等待。
- Method:可执行方法
- Message:超时时返回的信息
4.3、expected_conditions条件
expected_conditions是selenium的一个子模块,其中包含一系列可用于判断的条件,配合该类的方法,就能够根据条件而进行灵活地等待了。
ActionChains提供的方法 | 作用 |
---|---|
title_is title_contains | 这两个条件类验证title,验证传入的参数title是否等于或包含于driver |
presence_of_element_located presence_of_all_elements_located | 这两个条件验证元素是否出现,传入的参数都是元组类型的locator,如(By.ID, ‘kw’)顾名思义,一个只要一个符合条件的元素加载出来就通过;另一个必须所有符合条件的元素都加载出来才行 |
visibility_of_element_located invisibility_of_element_located visibility_of | 这三个条件验证元素是否可见,前两个传入参数是元组类型的locator,第三个传入WebElement |
text_to_be_present_in_element text_to_be_present_in_element_value | 判断某段文本是否出现在某元素中,一个判断元素的text,一个判断元素的value |
frame_to_be_available_and_switch_to_it | 判断frame是否可切入,可传入locator元组或者直接传入定位方式:id、name、index或WebElement |
alert_is_present | 判断是否有alert出现 |
element_to_be_clickable | 判断元素是否可点击,传入locator |
4.4、隐性等待
隐性等待implicitly_wait(xx)
:设置了一个最长等待时间,如果在规定时间内网页加载完成,则执行下一步,否则一直等到时间截止,然后执行下一步。
弊端就是程序会一直等待整个页面加载完成,就算你需要的元素加载出来了还是需要等待。,也就是一般情况下你看到浏览器标签栏那个小圈不再转,才会执行下一步。
隐性等待对整个driver的周期都起作用,所以只要设置一次即可;
隐性等待和显性等待可以同时用,但要注意:等待的最长时间取两者之中的大者;
默认等待时间为0,可以通过下面的方式设置:
from selenium import webdriver
driver = webdriver.Chrome()
······
driver.implicitly_wait(10) #隐式等待,最长10s
······
示例:
4.5、强制等待
强制等待就是不论如何,在此处都需要阻塞等待一段时间,使用方式如下:
import time
time.sleep(3) #等待3秒
4.6、代码示例
from selenium import webdriver
import time
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
wb = webdriver.Chrome()
wb.get('https://study.163.com/')
# 关闭页面弹出的小窗口
ok_btn = wb.find_element_by_xpath('//span[@class="ux-btn th-bk-main ux-btn- ux-btn- ux-modal-btn um-modal-btn_ok th-bk-main"]')
ok_btn.click()
# 登录窗口
login_btn = wb.find_element_by_xpath('//div[@class="go-login f-ib th-fs0fc6"]')
login_btn.click()
#设置显示等待
locater = (By.XPATH,'//iframe[@frameborder="0"]')
WebDriverWait(driver=wb,timeout=3,poll_frequency=0.4).until(EC.presence_of_element_located(locater),message='找不到元素!')
#隐式等待 最长3s
# wb.implicitly_wait(3)
# 强制等待 3s
# time.sleep(3)
# 切换frame 加载需要时间,所以要设等待
fr = wb.find_element_by_xpath('//iframe[@frameborder="0"]')
wb.switch_to.frame(fr)
# 账号输入框输入
name_input = wb.find_element_by_id('phoneipt')
name_input.send_keys('123456789')
# 密码输入
pwd_input = wb.find_element_by_xpath('//input[@class="j-inputtext dlemail"]')
pwd_input.send_keys('10987654321')
五、动作链
在selenium当中除了简单的点击动作外,还有一些稍微复杂的动作,就需要用到ActionChains(动作链)这个子模块来满足我们的需求。
ActionChains可以完成复杂一点的页面交互行为,例如元素的拖拽,鼠标移动,悬停行为,内容菜单交互。
它的执行原理就是当调用ActionChains方法的时候不会立即执行,而是将所有的操作暂时储存在一个队列中,当调用perform()方法的时候,会按照队列中放入的先后顺序执行前面的操作。
ActionChains包:from selenium.webdriver.common.action_chains import ActionChains
ActionChains提供的方法 | 作用 |
---|---|
click(on_element=None) | 鼠标左键单击 |
double_click(on_element=None) | 双击鼠标左键 |
context_click(on_element=None) | 点击鼠标右键 |
click_and_hold(on_element=None) | 点击鼠标左键,按住不放 |
release(on_element=None) | 在某个元素位置松开鼠标左键 |
drag_and_drop(source, target) | 拖拽到某个元素然后松开 |
drag_and_drop_by_offset(source, xoffset, yoffset) | 拖拽到某个坐标然后松开 |
move_to_element(to_element) | 鼠标移动到某个元素 |
move_by_offset(xoffset, yoffset) | 移动鼠标到指定的x,y位置 |
move_to_element_with_offset(to_element, xoffset, yoffset) | 将鼠标移动到距某个元素多少距离的位置 |
perform() | 执行链中的所有动作 |
示例:
import time
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
driver = webdriver.Chrome()
driver.get('http://www.treejs.cn/v3/demo/cn/exedit/drag.html')
time.sleep(2)
element1 = driver.find_element_by_id('treeDemo_2_span')
target = driver.find_element_by_id('treeDemo_3_span')
ActionChains(driver).drag_and_drop(element1, target).perform()
element2 = driver.find_element_by_id('treeDemo_12_span')
ActionChains(driver).drag_and_drop(element2,target).perform()
结果展示:
六、补充知识点
6.1、常用方法
方法 | 说明 |
---|---|
Chrome.refresh() | 刷新页面 |
Chrome.close() | 关闭当前标签 |
Chrome.quit() | 关闭所有标签 |
Chrome.page_source | 网页源代码 |
Chrome.cookies | 本页保存的cookie |
Chrome.maximize_window() | 最大化窗口 |
6.2、无界面设置
from selenium.webdriver.chrome.options import Options
from selenium import webdriver
chrome_options=Options()
chrome_options.add_argument("--headless")
drive = webdriver.Chrome(options=chrome_options)
drive.get('https://www.51zxw.net/List.aspx?cid=451')
print(drive.page_source) #获取网页源码
以上是关于爬虫学习笔记(十六)—— Selenium的主要内容,如果未能解决你的问题,请参考以下文章
[Python爬虫] 之二十六:Selenium +phantomjs 利用 pyquery抓取智能电视网站图片信息