python selenium快速上手教程
2024年4月16日大约 9 分钟约 2713 字
相关资料
selenium官方中文文档
火狐插件 Selenium IDE
重写WebDriver
以下示例可能导入的模块
import time
from typing import Optional, Union
from itertools import cycle
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.remote.webdriver import WebElement
driver初始化优化
webdriver_chrome.py
class ChromeDriver(webdriver.Chrome):
"""自定义 Chrome Driver"""
def __init__(self, headless=False, proxy_ip=None):
"""这个原始webdriver的初始化
Args:
headless: 是否为无头模式
proxy_ip: 代理ip,username:password@12.38.182.18:8080
"""
chrome_options = ChromeOptions()
# 配置代理
if proxy_ip:
username, password, ip, port = re.split('[:@]', proxy_ip)
proxies_extension = proxies(username, password, ip, port)
chrome_options.add_extension(proxies_extension)
# chrome_options.add_argument(f'--proxy-server={ip}:{port}') # 无法设置代理IP账号密码功能
# 小红书会对user_agent进行检测,请使用真实user_agent
# chrome_options.add_argument('user-agent=' + user_agent)
chrome_options.add_argument("--disable-blink-features=AutomationControlled") # 关闭(用户其浏览器正由自动化测试控制),使浏览器看起来不像是被自动化控制的。
# chrome_options.add_argument("--incognito") # 无痕模式(无痕模式无法使用扩展)
chrome_options.add_experimental_option('prefs', {'credentials_enable_service': False, 'profile.password_manager_enabled': False}) # 关闭保存密码的弹框
# chrome_options.add_argument("blink-settings=imagesEnabled=false") # Blink渲染引擎级别的禁用图片加载,加快访问速度
chrome_options.add_argument('log-level=3') # 设置日志级别,禁用大部分日志输出,使输出更简洁
chrome_options.add_argument("--enable-precise-memory-info") # 网站可以通过 JS API 获取到更详细的内存使用数据
chrome_options.add_argument("--test-type") # 开启测试模式(减少弹框和警告,放宽安全特效,启用某些测试特性)
chrome_options.add_argument('--no-sandbox') # 禁用沙盒模式。沙盒模式是一种安全特性,但在某些环境(如Docker容器)中可能导致问题。解决DevToolsActivePort文件不存在的报错
chrome_options.add_argument("--disable-setuid-sandbox") # 禁用setuid沙盒,这是沙盒模式的一个组成部分。
chrome_options.add_experimental_option('excludeSwitches', ['enable-automation']) # 设置为开发者模式,防止网站检测到Selenium的使用。
chrome_options.add_experimental_option('useAutomationExtension', False) # 禁用自动化扩展,这有助于减少Selenium自动化的可见性。
chrome_options.add_argument("--no-default-browser-check") # 不进行浏览器默认检查,这有助于自动化测试的流畅进行。
chrome_options.add_argument("--disable-popup-blocking") # 关闭弹窗,关闭网站代码直接生成的弹窗行为
# chrome_options.add_argument("--disable-extensions") # 禁用所有Chrome扩展,这有助于保持测试环境的一致性。
chrome_options.add_argument("--ignore-certificate-errors") # 忽略SSL/TLS证书错误,对于测试自签名或无效证书的网站很有用。
chrome_options.add_argument("--no-first-run") # 跳过初次运行的初始化过程,直接进入浏览器主界面。
chrome_options.add_argument("--start-maximized") # 最大化启动,模拟常规用户行为
chrome_options.add_argument("--disable-xss-auditor") # 禁止xss防护
chrome_options.add_argument("--disable-web-security") # 禁用同源策略,允许不受限制地跨域访问,这在某些特定的测试场景中可能是必要的。
chrome_options.add_argument("--allow-running-insecure-content") # 允许运行不安全的内容,即使在使用HTTPS的页面上也可以加载不安全的HTTP资源。
# 无头模式是在docker部署中运行的
if headless or os.getenv('PRODUCTION') or True:
chrome_options.add_argument("--headless=new") # 设置无头模式(新无头模式可以加载插件)
# chrome_options.add_argument("--headless=chrome") # 设置无头模式(这个无头模式插件加载失败)
chrome_options.add_argument("--window-size=1920,1080") # 设置窗口大小,解决无头模式报错element not interactable
chrome_options.add_argument('--disable-gpu') # 无头模式下,需要禁用GPU硬件加速来避免BUG
chrome_options.add_argument("--disable-webgl") # 禁用WebGL,这可能有助于提高性能或规避特定的WebGL相关问题。
chrome_options.add_argument("--disable-dev-shm-usage") # 避免将数据写入/dev/shm,而写入到/tmp中,因为在docker环境中它的容量为64M,这可能会导致问题。
# selenium 4 在启动浏览器时会自动识别和下载对于的webdriver,导致第一次启动速度非常慢,所以需要手动指定版本
# driver下载后,保存文件路径到配置文件,再次使用时加载
config_path = os.path.join(os.path.expanduser('~'), '.world-seo.ini')
config = configparser.ConfigParser()
if not os.path.exists(config_path):
# 另外提供一个手动下载地址:https://registry.npmmirror.com/binary.html?path=chrome-for-testing/
driver_path = ChromeDriverManager(url='https://cdn.npmmirror.com/binaries/chromedriver', latest_release_url='https://cdn.npmmirror.com/binaries/chromedriver/LATEST_RELEASE').install()
config['DEFAULT'] = {'driver_path': driver_path}
with open(config_path, 'w') as file:
config.write(file)
else:
config.read(config_path)
driver_path = config['DEFAULT']['driver_path']
super().__init__(
options=chrome_options,
service=ChromeService(driver_path),
)
self.set_page_load_timeout(15) # 设置页面加载的超时时间
self.implicitly_wait(30)
# 隐式等待(全局有效),隐式等待是针对页面的,在使用find函数查找元素时,最多等待30s
webdriver_chrome_proxy_auto_extension.py
"""Selenium chrome 代理IP认证 自定义扩展。https://github.com/Smartproxy/Selenium-proxy-authentication"""
import zipfile
import tempfile
import os
def proxies(username, password, endpoint, port):
manifest_json = """
{
"version": "1.0.0",
"manifest_version": 2,
"name": "Proxies",
"permissions": [
"proxy",
"tabs",
"unlimitedStorage",
"storage",
"<all_urls>",
"webRequest",
"webRequestBlocking"
],
"background": {
"scripts": ["background.js"]
},
"minimum_chrome_version":"22.0.0"
}
"""
background_js = """
var config = {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: "http",
host: "%s",
port: parseInt(%s)
},
bypassList: ["localhost"]
}
};
chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
function callbackFn(details) {
return {
authCredentials: {
username: "%s",
password: "%s"
}
};
}
chrome.webRequest.onAuthRequired.addListener(
callbackFn,
{urls: ["<all_urls>"]},
['blocking']
);
""" % (endpoint, port, username, password)
# 创建一个临时目录
with tempfile.TemporaryDirectory(delete=False) as temp_dir:
# 定义文件路径,系统会自动分配一个唯一的文件名
extension_path = os.path.join(temp_dir, 'proxies_extension.zip')
# 创建ZIP文件并写入内容
with zipfile.ZipFile(extension_path, 'w') as zp:
zp.writestr("manifest.json", manifest_json)
zp.writestr("background.js", background_js)
# 返回文件路径
return extension_path
if __name__ == '__main__':
file_path = proxies('username', 'password', 'endpoint', 'port')
print(file_path)
通过提供的列表查找某个元素
class ChromeDriver(webdriver.Chrome):
def find_element(self, by=By.ID, value: Optional[str, list] = None, timeout=15, is_super=True) -> WebElement:
"""升级原版函数,可从提供的列表中查找出第一个发现的元素。"""
if is_super:
return super().find_element(by, value)
values = [value] if isinstance(value, str) else value
start_time = time.time()
for _value in cycle(values):
# 计算剩余时间,如果超时了则抛出异常
remaining_time = timeout - (time.time() - start_time) # 剩余秒数
if remaining_time <= 0:
raise TimeoutException(f"等待元素 {str(values)} 超时")
try:
# 等待元素在页面上变得可见
element = WebDriverWait(self, 1).until(EC.visibility_of_element_located((by, _value)))
return element
except TimeoutException as e:
continue
等待元素不可见
class ChromeDriver(webdriver.Chrome):
def wait_element_hidden(self, by=By.ID, value: Optional[str] = None, timeout=15) -> bool:
"""等待元素消失。"""
try:
# 使用显式等待检查元素是否消失(不可见或不在DOM中)
WebDriverWait(self, timeout).until(EC.invisibility_of_element_located((by, value)))
return True # 元素已消失
except TimeoutException as e:
return False # 元素未消失
等待URL跳转
class ChromeDriver(webdriver.Chrome):
def find_url(self, url_part, timeout=10) -> Union[str, None]:
"""查找地址栏是否包含某个URL片段。
Args:
timeout (int): 等待超时时间
url_part (str): url片段
Returns:
str: 包含该url片段的地址栏完整URL
None: 没有查找到指定URL
"""
try:
WebDriverWait(self, timeout).until(lambda driver: url_part in driver.current_url)
return self.current_url
except TimeoutException:
return None
等待cookies中key的存在
class ChromeDriver(webdriver.Chrome):
def get_cookie(self, name, timeout=1) -> Optional[Dict]:
"""升级源函数,添加等待功能。
Args:
name: cookies key
"""
start_time = time.time()
while time.time() - start_time <= timeout:
value = super().get_cookie(name)
if value:
return value
添加cookies
class ChromeDriver(webdriver.Chrome):
def add_cookies(self, cookie_list: Union[list, str]) -> None:
"""给页面添加cookies。"""
if isinstance(cookie_list, str):
cookie_list = json.loads(cookie_list)
# 为WebDriver会话添加每个cookie
for cookie in cookie_list:
# 确保cookie中不包含会导致问题的额外字段
clean_cookie = {k: v for k, v in cookie.items() if k in ['name', 'value', 'domain', 'path', 'expiry', 'secure', 'httpOnly']}
# 如果cookie中有'expiry'字段,确保它是整数类型
if 'expiry' in clean_cookie:
clean_cookie['expiry'] = int(clean_cookie['expiry'])
self.add_cookie(clean_cookie)
self.refresh() # 刷新页面用于应用新的cookies
在页面中打印
class ChromeDriver(webdriver.Chrome):
def print(self, message: str):
"""在浏览器页面上显示进度信息"""
js_code = f"""
var progressDiv = document.getElementById('customProgressDiv');
if (!progressDiv) {{
progressDiv = document.createElement('div');
progressDiv.id = 'customProgressDiv';
document.body.appendChild(progressDiv);
progressDiv.style.position = 'fixed';
progressDiv.style.bottom = '20px';
progressDiv.style.right = '20px';
progressDiv.style.backgroundColor = 'rgba(76, 175, 80, 0.9)';
progressDiv.style.color = 'white';
progressDiv.style.padding = '10px';
progressDiv.style.borderRadius = '5px';
progressDiv.style.zIndex = '10000';
}}
progressDiv.textContent = '{message}';
"""
self.execute_script(js_code)
保存页面到html
class ChromeDriver(webdriver.Chrome):
def save_html(self, file_path: str = 'test.html'):
"""保存网页源代码"""
html_source = self.page_source
with open(file_path, 'w', encoding='utf-8') as file:
file.write(html_source)
元素基本操作
# 元素的属性
element.id # 元素的id
element.location # 元素的位置
element.size # 元素的大小
element.tag_name # 当前元素的标签名
element.text # 当前元素的内容
element,is_displayed() # 当前元素是否可见
element.is_selected() # 当前元素是否选中, 文本输入框的内容
element.is_enabled() # 当前元素是否禁止, 比如经常会禁用一些元素的点击
element.get_attribute(name) # 获取当前元素执行属性的值
# 元素的操作
element.clear() # 清楚元素的内容, 假如这个元素是一个文本元素(input元素)
element.click() # 点击当前元素
element.send_keys(*value) # 向当前元素模拟键盘事件
element.submit() # 提交表单
element_select.select_by_value('50') # 下拉框选择操作
鼠标交互动作
鼠标操作的方法封装在 ActionChains 类提供
from selenium.webdriver.common.action_chains import ActionChains
# 对元素执行鼠标悬停操作
ActionChains(browser).move_to_element(element).perform()
# 双击
ActionChains(browser).double_click().perform()
# 右击
ActionChains(browser).context_click().perform()
# 拖拽
ActionChains(browser).drag_and_drop(element1, element2).perform()
将动作附加到动作链中串行执行
# 拖拽
from selenium.webdriver.common.action_chains import ActionChains
source = browser.find_element_by_css_selector('#draggable')
target = browser.find_element_by_css_selector('#droppable')
actions = ActionChains(browser) # 构造ActionChains对象
actions.drag_and_drop(source, target) # 拖拽
actions.perform() # 执行所有 ActionChains 中存储的行为,可以理解成是对整个操作的提交动作
浏览器的操作
# ----------------------获取属性------------------------
browser.title # 当前页面的 title
browser.name # 当前浏览器的名字
browser.page_source # 网页源码
browser.current_url # 获取当前加载页面的 URL
# ---------------------cookie操作----------------------
browser.get_cookies() # 得到所有的 cookie
browser.get_cookie("name") # 返回字典的key为“name”的cookie信息
# 为当前会话添加cookie,“cookie_dict”指字典对象,必须有name 和value 值
browser.add_cookie(cookie_dict)
driver.add_cookie({‘name’ : ‘foo’, ‘value’ : ‘bar’}) driver.add_cookie({‘name’ : ‘foo’, ‘value’ : ‘bar’, ‘path’ : ‘/’}) driver.add_cookie({‘name’ : ‘foo’, ‘value’ : ‘bar’, ‘path’ : ‘/’, ‘secure’:True})
browser.delete_all_cookies() # 删除当前会话的所有cookie
# 删除cookie信息。“name”是要删除的cookie的名称,“optionsString”是该cookie的选项,目前支持的选项包括“路径”,“域”
browser.delete_cookie(name,optionsString)
browser.delete_cookie(name) # 删除指定 cookie
# ---------------------页面跳转---------------------------
browser.get(url) # 在当前窗口加载 url
browser.refresh() # 刷新当前页面
browser.back() # 相当于浏览器的后退历史记录
browser.forward() # 相当于浏览器的前进历史记录
browser.close() # 关闭当前窗口, 如果当前窗口是最后一个窗口, 浏览器将关闭
browser.quit() # 关闭所有窗口并停止 ChromeDriver 的执行
# -----------------------页面切换----------------------------------
browser.current_window_handle # 当前窗口的 handle, 相当于一个指针一样的东西, 用来指向当前窗口
browser.window_handles # 当前浏览器中的已经打开的所有窗口, 是一个 list
browser.window_handles[0]
browser.switch_to.window(browser.window_handles[0]) # 切换 window_handle 指向的窗口
browser.switch_to.frame('iframeResult') # 切入到frame标签中
browser.switch_to.parent_frame() # 切出到frame标签
browser.switch_to.default_content() # 切出到最外层的frame标签
#---------------------------脚本执行---------------------------
browser.execute_script(script, *args) # 同步执行 js 脚本
browser.execute_script('alert("demo")') # 执行脚本举例
browser.execute_script("arguments[0].focus();", element) # 脚本传参
browser.execute_async_script(script, *args) # 异步执行 js 脚本
# 截取当前窗口,并指定截图图片的保存位置
browser.get_screenshot_as_file("D:\\baidu_img.jpg")
# 调整窗口大小
browser.set_window_size(375, 812)
# 全屏
browser.maximize_window()
脚本执行
# 停止页面加载
browser.execute_script('window.stop()')
# 拖动滚动条是元素居中
element = browser.find_element_by_id("demo")
browser.execute_script("arguments[0].scrollIntoView();", element)
警告框处理
警告框处理:
- 进入警告框(alert/confirm/prompt)
- 操作警告框
操作方法:
方法 | 说明 |
---|---|
text | 返回 alert/confirm/prompt 中的文字信息 |
accept() | 接受现有警告框 |
dismiss() | 解散现有警告框 |
send_keys(keysToSend) | 发送文本至警告框。keysToSend:将文本发送至警告框。 |
# 接受警告框
browser.switch_to.alert.accept()
错误处理
ElementClickInterceptedException
点击元素时报错,可能是在点击时被其他元素遮挡了,做休眠即可成功点击
time.sleep(2)
browser.find_element_by_css_selector(".c-blocka").click()