网站首页 > 技术文章 正文
概述
selenium 是网页应用中最流行的自动化测试工具,可以用来做自动化测试或者浏览器爬虫等。官网地址为: https://www.selenium.dev/ 。相对于另外一款web自动化测试工具QTP来说有如下优点:
- 免费开源轻量级,不同语言只需要一个体积很小的依赖包
- 支持多系统环境,包括Windows,Mac,Linux
- 支持多种浏览器,包括Chrome,FireFox,IE,safari,opera等
- 支持多语言,包括Java,C,python,c#等主流语言
- 支持分布式测试用例执行
python+selenium环境安装
首先需要安装python(推荐3.7+)环境,然后直接用 pip install selenium 安装依赖包即可。
另外还需要下载浏览器相应的 webdriver 驱动程序, 注意下载的驱动版本一定要匹配浏览器版本 。
- Firefox浏览器驱动: geckodriver
- Chrome浏览器驱动: chromedriver
- IE浏览器驱动: IEDriverServer
- Edge浏览器驱动: MicrosoftWebDriver
- Opera浏览器驱动: operadriver
下载以后可以把驱动程序加到环境变量,这样使用时就不用手动指定驱动程序路径。
使用selenium启动浏览器
可以在python中使用下面的代码启动一个 Chrome 浏览器,然后控制这个浏览器的行为或者读取数据。
from selenium import webdriver
# 启动Chrome浏览器,要求chromedriver驱动程序已经配置到环境变量
# 将驱动程序和当前脚本放在同一个文件夹也可以
driver = webdriver.Chrome()
# 手动指定驱动程序路径
driver = webdriver.Chrome(r'D:/uusama/tools/chromedriver.exe')
driver = webdriver.Ie() # Internet Explorer浏览器
driver = webdriver.Edge() # Edge浏览器
driver = webdriver.Opera() # Opera浏览器
driver = webdriver.PhantomJS() # PhantomJS
driver.get('http://uusama.com') # 打开指定路径的页面
启动的时候还可以设置启动参数,比如下面的代码实现启动时添加代理,并且忽略 https 证书校验。
from selenium import webdriver
# 创建chrome启动选项对象
options = webdriver.ChromeOptions()
options.add_argument("--proxy-server=127.0.0.1:16666") # 设置代理
options.add_argument("---ignore-certificate-errors") # 设置忽略https证书校验
options.add_experimental_option("excludeSwitches", ["enable-logging"]) # 启用日志
# 设置浏览器下载文件时保存的默认路径
prefs = {"download.default_directory": get_download_dir()}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=options)
一些非常有用的启动选项,下面使用的 options = webdriver.ChromeOptions() :
- options.add_argument("--proxy-server=127.0.0.1:16666") : 设置代理,可以结合 mitmproxy 进行抓包等
- option.add_experimental_option('excludeSwitches', ['enable-automation']) : 设置绕过 selenium 检测
- options.add_argument("---ignore-certificate-errors") : 设置忽略https证书校验
- options.add_experimental_option("prefs", {"profile.managed_default_content_settings.images": 2}) : 设置不请求图片模式加快页面加载速度
- chrome_options.add_argument('--headless') : 设置无头浏览器
selenium页面加载等待和检测
使用selenium打开页面以后,还不能立刻操作,需要等到待处理页面元素加载完成,这时就需要检测和等待页面加载完成。
使用time.sleep()等待
最简单的方法就是打开页面以后,使用 time.sleep() 强制等待一定时间,该方法只能设置一个固定时间等待,如果页面提前加载完成,则会空等阻塞。
from time import sleep
from selenium import webdriver
driver = webdriver.Chrome()
driver.get('http://uusama.con')
time.sleep(10)
print('load finish')
使用implicitly_wait设置最长等待时间
另外还可以使用 implicitly_wait 设置最长等待时间,如果在给定时间内页面加载完成或者已经超时,才会执行下一步。该方法会等到所有资源全部加载完成,也就是浏览器标签栏的loading小圈不再转才会执行下一步。有可能页面元素已经加载完成,但是js或者图片等资源还未加载完成,此时还需要等待。
另需注意使用 implicitly_wait 只需设置一次,并且对整个 driver 生命周期都起作用,凡是遇到页面正在加载都会阻塞。
示例如下:
from selenium import webdriver
driver = webdriver.Chrome()
driver.implicitly_wait(30) # 设置最长等30秒
driver.get('http://uusama.com')
print(driver.current_url)
driver.get('http://baidu.com')
print(driver.current_url)
使用WebDriverWait设置等待条件
使用 WebDriverWait (selenium.webdriver.support.wait.WebDriverWait)能够更加精确灵活地设置等待时间, WebDriverWait 可在设定时间内每隔一段时间检测、是否满足某个条件,如果满足条件则进行下一步操作,如果超过设置时间还不满足,则抛出异常 TimeoutException ,其方法声明如下:
WebDriverWait(driver, timeout, poll_frequency=0.5, ignored_exceptions=None)
其中各参数含义如下
- driver :浏览器驱动
- timeout :最长超时时间,默认以秒为单位
- poll_frequency :检测的间隔(步长)时间,默认为 0.5秒
- ignored_exceptions :忽略的异常,即使在调用 until() 或 until_not() 的过程中抛出给定异常也不中断
WebDriverWait() 一般配合 until() 或 until_not() 方法使用,表示等待阻塞直到返回值为 True 或者 False ,需要注意这两个方法的参数都需是可调用对象,即方法名称,可以使用 expected_conditions 模块中的方法或者自己封装的方法。
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
driver = webdriver.Chrome()
driver.get("http://baidu.com")
# 判断id为`input`的元素是否被加到了dom树里,并不代表该元素一定可见,如果定位到就返回WebElement
element = WebDriverWait(driver, 5, 0.5).until(expected_conditions.presence_of_element_located((By.ID, "s_btn_wr")))
# implicitly_wait和WebDriverWait都设置时,取二者中最大的等待时间
driver.implicitly_wait(5)
# 判断某个元素是否被添加到了dom里并且可见,可见代表元素可显示且宽和高都大于0
WebDriverWait(driver,10).until(EC.visibility_of_element_located((By.ID, 'su')))
# 判断元素是否可见,如果可见就返回这个元素
WebDriverWait(driver,10).until(EC.visibility_of(driver.find_element(by=By.ID, value='kw')))
下面列出 expected_conditions 常用的一些方法:
title_is
title_contains
presence_of_element_located
visibility_of_element_located
visibility_of
presence_of_all_elements_located
text_to_be_present_in_element
text_to_be_present_in_element_value
frame_to_be_available_and_switch_to_it
invisibility_of_element_located
element_to_be_clickable
staleness_of
element_to_be_selected
element_selection_state_to_be
element_located_selection_state_to_be
检测document是否加载完成
另外还可以使用 driver.execute_script('return document.readyState;') == 'complete' 来检测 document 是否加载完成。
注意 document 加载完成,是不包括那种异步加载ajax请求动态渲染的dom的,这种需要使用上面的方法检测某个元素是否渲染完成。
selenium元素定位和读取
查找元素
selenium提供了一系列api方便获取chrome中的元素,这些API都返回 WebElement 对象或其列表,如:
- find_element_by_id(id) : 查找匹配id的第一个元素
- find_element_by_class_name() : 查找匹配 class 的第一个元素
- find_elements_by_xpath() : 查找匹配 xpath 的所有元素
- find_elements_by_css_selector() : 查找匹配css选择器的所有元素
其实可以看 WebDriver 类里面的实现源码,其核心实现都是调用两个基本函数:
find_element(self, by=By.ID, value=None)
find_elements(self, by=By.ID, value=None)
其中 by 参数可以是 ID , CSS_SELECTOR , CLASS_NAME , XPATH 等。下面举几个简单的例子:
- 通过xpath查询包含文本 登录 的第一个元素: find_element_by_xpath("//*[contains(text(),'登录')]")
- 查询包含类名 refresh 的所有元素: find_elements_by_class_name('refresh')
- 查询 table 表格的第二行: find_element_by_css_selector('table tbody > tr:nth(2)')
dom元素交互
上面介绍的元素查找方法基本返回 WebElement 对象或者该对象的列表,该对象常用的有如下api:
- element.text : 返回元素的文本内容(包括后台节点所有内容),注意如果元素 display=none 则返回为空字符串
- element.screenshot_as_png : 元素截图
- element.send_keys("input") : 元素输入框输入 input 字符串
- element.get_attribute('data-v') : 获取 data-v 名称属性值,除了自定义节点属性,还可以获取如 textContent 等属性
- element.is_displayed() : 元素是否用户可见
- element.clear() : 清除元素文本
- element.click() : 点击元素,如果元素不可点击会抛出 ElementNotInteractableException 异常
- element.submit() : 模拟表单提交
查找元素失败处理
如果找不到指定元素,则会抛出 NoSuchElementException 异常,而且需要注意,即使是 display=none 的元素也会获取到,凡是在 dom 节点中的元素都可以获取到。
而且实际使用的时候要注意一些js代码动态创建的元素,可能需要轮询获取或者监控。
一个检查是否存在指定元素的方法如下:
def check_element_exists(xpath):
try:
driver.find_element_by_xpath(xpath)
except NoSuchElementException:
return False
return True
selenium交互控制
ActionChains动作链
webdriver通过 ActionChains 对象来模拟用户操作,该对象表示一个动作链路队列,所有操作会依次进入队列并不会立即执行,需要调用 perform() 方法时才会执行。其常用方法如下:
click(on_element=None)
click_and_hold(on_element=None)
context_click(on_element=None)
double_click(on_element=None)
send_keys(*keys_to_send)
send_keys_to_element(element, *keys_to_send)
key_down(value, element=None)
key_up(value, element=None)
drag_and_drop(source, target)
drag_and_drop_by_offset(source, xoffset, yoffset)
move_by_offset(xoffset, yoffset)
move_to_element(to_element)
move_to_element_with_offset(to_element, xoffset, yoffset)
perform()
release(on_element=None)
模拟鼠标事件
下面代码模拟鼠标移动,点击,拖拽等操作,注意操作时需要等待一定时间,否则页面还来不及渲染。
from time import sleep
from selenium import webdriver
# 引入 ActionChains 类
from selenium.webdriver.common.action_chains import ActionChains
driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
action_chains = ActionChains(driver)
target = driver.find_element_by_link_text("搜索")
# 移动鼠标到指定元素然后点击
action_chains.move_to_element(target).click(target).perform()
time.sleep(2)
# 也可以直接调用元素的点击方法
target.click()
time.sleep(2)
# 鼠标移动到(10, 50)坐标处
action_chains.move_by_offset(10, 50).perform()
time.sleep(2)
# 鼠标移动到距离元素target(10, 50)处
action_chains.move_to_element_with_offset(target, 10, 50).perform()
time.sleep(2)
# 鼠标拖拽,将一个元素拖动到另一个元素
dragger = driver.find_element_by_id('dragger')
action.drag_and_drop(dragger, target).perform()
time.sleep(2)
# 也可以使用点击 -> 移动来实现拖拽
action.click_and_hold(dragger).release(target).perform()
time.sleep(2)
action.click_and_hold(dragger).move_to_element(target).release().perform()
模拟键盘输入事件
通过 send_keys 模拟键盘事件,常用有:
send_keys(Keys.BACK_SPACE)
send_keys(Keys.SPACE)
send_keys(Keys.TAB)
send_keys(Keys.ESCAPE)
send_keys(Keys.ENTER)
send_keys(Keys.F1)
send_keys(Keys.CONTROL,'a')
send_keys(Keys.CONTROL,'c')
send_keys(Keys.CONTROL,'x')
send_keys(Keys.CONTROL,'v')
示例:定位到输入框,然后输入内容
# 输入框输入内容
driver.find_element_by_id("kw").send_keys("seleniumm")
# 删除多输入的一个 m
driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)
警告框处理
用于处理调用 alert 弹出的对话框。
driver.switch_to_alert()
text
accept()
dismiss()
send_keys(keysToSend)
selenium浏览器控制
基本常用api
下面列出一些非常实用的浏览器控制api:
- driver.current_url : 获取当前活动窗口的url
- driver.switch_to_window("windowName") : 移动到指定的标签窗口
- driver.switch_to_frame("frameName") : 移动到指定名称的 iframe
- driver.switch_to_default_content() : 移动到默认文本内容区
- driver.maximize_window() : 将浏览器最大化显示
- driver.set_window_size(480, 800) : 设置浏览器宽480、高800显示
- driver.forword() , driver.back() : 浏览器前进和后退
- driver.refresh() : 刷新页面
- driver.close() : 关闭当前标签页
- driver.quiit() : 关闭整个浏览器
- driver.save_screenshot('screen.png') : 保存页面截图
- driver.maximize_window() : 将浏览器最大化显示
- browser.execute_script('return document.readyState;') : 执行js脚本
selenium读取和加载cookie
使用 get_cookies 和 add_cookie 可以实现将cookie缓存到本地,然后启动时加载,这样可以保留登录态。实现如下
import os
import json
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
# 读取所有cookie并保存到文件
cookies = driver.get_cookies()
cookie_save_path = 'cookie.json'
with open(cookie_save_path, 'w', encoding='utf-8') as file_handle:
json.dump(cookies, file_handle, ensure_ascii=False, indent=4)
# 从文件读取cookie并加载到浏览器
with open(cookie_save_path, 'r', encoding='utf-8') as file_handle:
cookies = json.load(file_handle)
for cookie in cookies:
driver.add_cookie(cookie)
selenium打开新的标签页窗口
使用 driver.get(url) 会默认在第一个标签窗口打开指定连接,点击页面中的 _blank 的链接时也会打开一个新的标签窗口。
还可以用下面的方式手动打开一个指定页面的标签窗口,需要注意打开新窗口或者关闭以后,还需要手动调用 switch_to.window 切换当前活动的标签窗口,否则会抛出 NoSuchWindowException 异常。
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://www.baidu.cn")
new_tab_url = 'http://uusama.com'
driver.execute_script(f'window.open("{new_tab_url}", "_blank");')
time.sleep(1)
# 注意:必须调用switch_to.window手动切换window,否则会找不到tab view
# 聚焦到新打开的tab页面,然后关闭
driver.switch_to.window(driver.window_handles[1])
time.sleep(2)
driver.close() # 关闭当前窗口
# 手动回到原来的tab页面
driver.switch_to.window(driver.window_handles[0])
time.sleep(1)
除了使用 execute_script 外,还可以使用模拟打开新tab页按键的方式新建一个标签页窗口:
driver.find_element_by_tag_name('body').send_keys(Keys.CONTROL + 't')
ActionChains(driver).key_down(Keys.CONTROL).send_keys('t').key_up(Keys.CONTROL).perform()
selenium一些问题记录
获取隐藏元素的文本内容
如果一个元素是隐藏的,即 display=none ,虽然可以通过 find_element 查找到该元素,但是用 element.text 属性是获取不到该元素的文本内容的,起值是空字符串,这时可以用下面的方式获取:
element = driver.find_element_by_id('uusama')
driver.execute_script("return arguments[0].textContent", element)
driver.execute_script("return arguments[0].innerHTML", element)
# 相应的也可以把隐藏的元素设置为非隐藏
driver.execute_script("arguments[0].style.display = 'block';", element)
浏览器崩溃WebDriverException异常处理
比如在 Chrome 中长时间运行一个页面会出现 Out Of Memory 内存不足的错误,此时 WebDriver 会抛出 WebDriverException 异常,基本所有api都会抛出这个异常,这个时候需要捕获并进行特殊处理。
我的处理方式是记录页面的一些基本信息,比如url,cookie等,然后定期写入到文件中,如果检测到该异常,则重启浏览器并且加载url和cookie等数据。
selenium抓取页面请求数据
网上有通过 driver.requests 或者通过解析日志来获取页面请求的方式,但是我感觉都不是很好使。最后使用 mitmproxy 代理进行抓包处理,然后启动 selenium 时填入代理来实现。
proxy.py 为在 mitmproxy 基础上封装的自定义代理请求处理,其代码如下:
import os
import gzip
from mitmproxy.options import Options
from mitmproxy.proxy.config import ProxyConfig
from mitmproxy.proxy.server import ProxyServer
from mitmproxy.tools.dump import DumpMaster
from mitmproxy.http import HTTPFlow
from mitmproxy.websocket import WebSocketFlow
class ProxyMaster(DumpMaster):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def run(self, func=None):
try:
DumpMaster.run(self, func)
except KeyboardInterrupt:
self.shutdown()
def process(url: str, request_body: str, response_content: str):
# 抓包请求处理,可以在这儿转存和解析数据
pass
class Addon(object):
def websocket_message(self, flow: WebSocketFlow):
# 监听websockt请求
pass
def response(self, flow: HTTPFlow):
# 避免一直保存flow流,导致内存占用飙升
# flow.request.headers["Connection"] = "close"
# 监听http请求响应,并获取请求体和响应内容
url = flow.request.url
request_body = flow.request
response_content = flow.response
# 如果返回值是压缩的内容需要进行解压缩
if response_content.data.content.startswith(b'\x1f\x8b\x08'):
response_content = gzip.decompress(response_content.data.content).decode('utf-8')
Addon.EXECUTOR.submit(process, url, request_body, response_content)
def run_proxy_server():
options = Options(listen_host='0.0.0.0', listen_port=16666)
config = ProxyConfig(options)
master = ProxyMaster(options, with_termlog=False, with_dumper=False)
master.server = ProxyServer(config)
master.addons.add(Addon())
master.run()
if __name__ == '__main__':
with open('proxy.pid', mode='w') as fin:
fin.write(os.getpid().__str__())
run_proxy_server()
在使用 mitmproxy 过程中,随着时间推移 proxy.py 会出现占用内存飙升的问题,在 github的issue区 有人也遇到过,有说是因为http连接 keep-alive=true 请求会一直保存不会释放,导致请求越多越占用内存,然后通过添加 flow.request.headers["Connection"] = "close" 来手动关闭连接,我加了以后有一定缓解,但还是不能从根本上解决。
最后通过写入 proxy.pid 记录代理程序进程,然后用另外一个程序定时重启 proxy.py 来解决内存泄漏的问题。
猜你喜欢
- 2025-07-06 Apifox--比 Postman 还好用的 API 测试工具
- 2025-07-06 前端测试利器:全面解析 Jest 的魅力与实战技巧
- 2025-07-06 AI辅助开发实战经验总结:Trae、Cursor 与 MCP 工具组合评测
- 2025-07-06 一文了解 Telerik Test Studio 测试神器
- 2025-07-06 OpenAI在GitHub上发布了用于自动化前端测试的 AI 代理的演示
- 2024-10-09 软件测试开发丨PageObject模式:为什么是Web自动化测试必备工具
- 2024-10-09 AutoRunner自动化测试工具 自动化测试app
- 2024-10-09 Katalon Studio 自动化测试工具介绍
- 2024-10-09 Afuzz:一款功能强大的自动化Web路径模糊测试工具
- 2024-10-09 高级测开工程师,呕心沥血整理出这份自动化测试核心知识PDF
你 发表评论:
欢迎- 593℃几个Oracle空值处理函数 oracle处理null值的函数
- 586℃Oracle分析函数之Lag和Lead()使用
- 574℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 571℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 567℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 559℃【数据统计分析】详解Oracle分组函数之CUBE
- 546℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 540℃Oracle有哪些常见的函数? oracle中常用的函数
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- 前端懒加载 (49)
- 前端获取当前时间 (50)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle的函数 (57)
- 前端调试 (52)
本文暂时没有评论,来添加一个吧(●'◡'●)