04 - 页面自动化实战
04 - 页面自动化实战
掌握元素定位、等待策略、页面交互、表单操作等浏览器自动化的核心技能。
4.1 元素定位
元素定位是浏览器自动化的基石——找到正确的元素,才能与之交互。
定位器 (Locator) 总览
| 定位器 | By 方法 | 语法示例 | 说明 |
|---|---|---|---|
| ID | By.ID | "username" | 最稳定,推荐首选 |
| Name | By.NAME | "email" | 表单元素常用 |
| Class Name | By.CLASS_NAME | "btn-primary" | 匹配单个 class |
| Tag Name | By.TAG_NAME | "input" | 宽泛匹配 |
| Link Text | By.LINK_TEXT | "点击这里" | 精确匹配链接文本 |
| Partial Link Text | By.PARTIAL_LINK_TEXT | "点击" | 部分匹配链接文本 |
| CSS Selector | By.CSS_SELECTOR | "#login .btn" | 灵活强大 |
| XPath | By.XPATH | "//div[@id='app']//button" | 最灵活但较脆弱 |
CSS Selector 速查
/* ID 选择器 */
#login-form
/* 类选择器 */
.btn-primary
/* 属性选择器 */
input[type="email"]
a[href^="https"]
input[data-testid="submit"]
/* 后代选择器 */
#app .container input
/* 子元素选择器 */
#sidebar > ul > li
/* 伪类选择器 */
input:first-child
li:nth-child(3)
button:not([disabled])
/* 组合选择器 */
#app input[type="text"], #app input[type="email"]
XPath 速查
# 绝对路径 (不推荐)
/html/body/div[1]/div[2]/form/input
# 相对路径 (推荐)
//input[@id='username']
//button[@type='submit']
//div[contains(@class, 'alert')]
# 文本匹配
//a[text()='登录']
//button[contains(text(), '提交')]
# 轴 (Axis)
//div[@id='form']//following-sibling::div
//input[@name='email']/parent::div
//li[@class='item']/preceding-sibling::li
# 通配符
//div[@*='value']
//*[contains(@class, 'error')]
# 组合条件
//input[@type='text' and @name='email']
//button[@type='submit' or @type='button']
Python — 元素定位示例
from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com/login")
# 单个元素
username = driver.find_element(By.ID, "username")
email = driver.find_element(By.CSS_SELECTOR, "input[type='email']")
submit = driver.find_element(By.XPATH, "//button[@type='submit']")
menu = driver.find_element(By.CSS_SELECTOR, "nav .menu-item:first-child")
# 多个元素
items = driver.find_element(By.CSS_SELECTOR, ".list-item")
all_links = driver.find_elements(By.TAG_NAME, "a")
error_msgs = driver.find_elements(By.CSS_SELECTOR, ".error-message")
4.2 等待策略
页面元素的出现有先后顺序,等待策略决定了脚本的稳定性。
三种等待方式
| 类型 | 说明 | 优点 | 缺点 |
|---|---|---|---|
| 强制等待 | time.sleep(5) | 简单 | 浪费时间、不可靠 |
| 隐式等待 | driver.implicitly_wait(10) | 全局生效 | 粒度粗、影响性能查询 |
| 显式等待 | WebDriverWait(...).until(...) | 精准、灵活 | 代码量稍多 |
强制等待 (不推荐)
import time
time.sleep(5) # 固定等待 5 秒 —— 浪费且不可靠
隐式等待
driver.implicitly_wait(10) # 查找元素时最多等待 10 秒
# 注意: 隐式等待对 find_element 生效,但对 JavaScript 执行无效
# 不要混合使用隐式等待和显式等待,会导致不可预期的超时
显式等待 (推荐)
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.get("https://example.com")
# 等待元素可见
element = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "content"))
)
# 等待元素可点击
button = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, ".submit-btn"))
)
button.click()
# 等待文本出现
WebDriverWait(driver, 10).until(
EC.text_to_be_present_in_element((By.ID, "status"), "完成")
)
# 等待 URL 变化
WebDriverWait(driver, 10).until(
EC.url_contains("/dashboard")
)
# 等待元素消失 (loading 动画)
WebDriverWait(driver, 15).until(
EC.invisibility_of_element_located((By.CSS_SELECTOR, ".loading-spinner"))
)
常用 Expected Conditions
| 条件 | 说明 |
|---|---|
presence_of_element_located | 元素出现在 DOM 中(不要求可见) |
visibility_of_element_located | 元素可见(有宽高且不隐藏) |
element_to_be_clickable | 元素可见且可点击(未被遮挡、未 disabled) |
invisibility_of_element_located | 元素不可见或不在 DOM 中 |
text_to_be_present_in_element | 元素包含指定文本 |
title_contains | 页面标题包含指定字符串 |
url_contains | URL 包含指定字符串 |
alert_is_present | 出现 JavaScript 弹窗 |
number_of_windows_to_be | 窗口数量达到指定值 |
frame_to_be_available_and_switch_to_it | iframe 可用并自动切换 |
自定义等待条件
from selenium.webdriver.support.ui import WebDriverWait
# 自定义条件函数
def wait_for_ajax(driver):
"""等待所有 AJAX 请求完成"""
return driver.execute_script(
"return (typeof jQuery === 'undefined') || jQuery.active === 0"
)
def wait_for_page_ready(driver):
"""等待页面完全就绪"""
return driver.execute_script("return document.readyState === 'complete'")
def element_has_non_empty_text(locator):
"""等待元素有非空文本"""
def _predicate(driver):
element = driver.find_element(*locator)
return element if element.text.strip() else False
return _predicate
# 使用自定义条件
WebDriverWait(driver, 20).until(wait_for_ajax)
WebDriverWait(driver, 10).until(
element_has_non_empty_text((By.ID, "result"))
)
4.3 页面交互
4.3.1 鼠标操作
from selenium.webdriver.common.action_chains import ActionChains
actions = ActionChains(driver)
# 单击
element = driver.find_element(By.ID, "button")
actions.click(element).perform()
# 双击
actions.double_click(element).perform()
# 右击
actions.context_click(element).perform()
# 悬停
menu = driver.find_element(By.CSS_SELECTOR, ".dropdown-toggle")
actions.move_to_element(menu).perform()
# 拖拽
source = driver.find_element(By.ID, "draggable")
target = driver.find_element(By.ID, "droppable")
actions.drag_and_drop(source, target).perform()
# 按住、移动、释放 (精细拖拽)
actions.click_and_hold(source) \
.move_to_element(target) \
.release() \
.perform()
# 链式操作
actions.move_to_element(menu) \
.pause(0.5) \
.click(submenu_item) \
.perform()
4.3.2 键盘操作
from selenium.webdriver.common.keys import Keys
# 在输入框中输入
search = driver.find_element(By.NAME, "q")
search.send_keys("Hello World")
# 清空输入
search.clear()
# 组合键
search.send_keys(Keys.CONTROL + "a") # 全选
search.send_keys(Keys.CONTROL + "c") # 复制
search.send_keys(Keys.CONTROL + "v") # 粘贴
# 特殊键
search.send_keys(Keys.ENTER) # 回车
search.send_keys(Keys.TAB) # Tab
search.send_keys(Keys.ESCAPE) # ESC
search.send_keys(Keys.BACKSPACE) # 退格
# 模拟按键 (不针对特定元素)
from selenium.webdriver.common.action_chains import ActionChains
ActionChains(driver) \
.key_down(Keys.CONTROL) \
.send_keys("a") \
.key_up(Keys.CONTROL) \
.perform()
4.3.3 JavaScript 执行
# 同步执行 JS
title = driver.execute_script("return document.title;")
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
driver.execute_script("arguments[0].click();", element)
# 传入多个参数
driver.execute_script(
"arguments[0].style.border = '2px solid red';",
element
)
# 异步执行 JS
result = driver.execute_async_script("""
var callback = arguments[arguments.length - 1];
setTimeout(function() {
callback('异步结果');
}, 2000);
""")
# 修改元素属性 (绕过前端限制)
driver.execute_script("""
var el = document.getElementById('submit-btn');
el.removeAttribute('disabled');
el.style.display = 'block';
""")
4.4 表单操作
4.4.1 文本输入
# 基本输入
driver.find_element(By.ID, "username").send_keys("admin")
# 带延迟输入 (模拟真实用户)
import time
for char in "[email protected]":
driver.find_element(By.ID, "email").send_keys(char)
time.sleep(0.05) # 50ms 间隔
# 使用 clipboard 输入 (绕过某些前端限制)
import pyperclip
pyperclip.copy("要粘贴的内容")
driver.find_element(By.ID, "field").send_keys(Keys.CONTROL + "v")
4.4.2 下拉选择
from selenium.webdriver.support.ui import Select
# 定位 <select> 元素
select_element = Select(driver.find_element(By.ID, "country"))
# 按值选择
select_element.select_by_value("CN")
# 按文本选择
select_element.select_by_visible_text("中国")
# 按索引选择
select_element.select_by_index(0)
# 获取所有选项
for option in select_element.options:
print(f"{option.text}: {option.get_attribute('value')}")
# 取消选择 (多选框)
select_element.deselect_all()
4.4.3 复选框与单选框
# 复选框
checkbox = driver.find_element(By.ID, "agree")
if not checkbox.is_selected():
checkbox.click()
# 单选框
radio = driver.find_element(By.CSS_SELECTOR, "input[value='male']")
radio.click()
# 验证选中状态
assert radio.is_selected()
4.4.4 文件上传
# 方法 1: send_keys 到 <input type="file">
upload = driver.find_element(By.CSS_SELECTOR, "input[type='file']")
upload.send_keys("/path/to/document.pdf")
# 方法 2: 多文件上传
upload.send_keys("/path/to/file1.pdf\n/path/to/file2.pdf")
# 方法 3: 使用 AutoIt / pyautogui 处理系统对话框 (不推荐)
# 方法 4: 拦截文件选择器 (Puppeteer/Playwright 更方便)
4.5 页面导航
# 打开新 URL
driver.get("https://example.com")
# 刷新
driver.refresh()
# 前进后退
driver.back()
driver.forward()
# 获取当前 URL
current_url = driver.current_url
# 获取页面标题
title = driver.title
# 获取页面源码
source = driver.page_source
窗口与标签页管理
# 获取当前窗口句柄
main_window = driver.current_window_handle
# 获取所有窗口句柄
all_windows = driver.window_handles
# 打开新标签页
driver.execute_script("window.open('https://example.com', '_blank');")
# 切换到新标签页
driver.switch_to.window(driver.window_handles[-1])
# 切换回主窗口
driver.switch_to.window(main_window)
# 设置窗口大小
driver.set_window_size(1920, 1080)
# 最大化 / 最小化
driver.maximize_window()
driver.minimize_window()
# 全屏
driver.fullscreen_window()
# 获取窗口位置和大小
rect = driver.get_window_rect()
# {'x': 0, 'y': 0, 'width': 1920, 'height': 1080}
4.6 iframe 处理
# 切换到 iframe
driver.switch_to.frame("iframe-name") # 按 name
driver.switch_to.frame("iframe-id") # 按 id
driver.switch_to.frame(0) # 按索引
iframe = driver.find_element(By.TAG_NAME, "iframe")
driver.switch_to.frame(iframe) # 按元素
# 在 iframe 内操作
driver.find_element(By.ID, "inner-element").click()
# 切回主文档
driver.switch_to.default_content()
# 切换到父 iframe (嵌套 iframe)
driver.switch_to.parent_frame()
4.7 弹窗处理
# JavaScript alert / confirm / prompt
alert = driver.switch_to.alert
print(alert.text) # 获取弹窗文本
alert.accept() # 点击确定
alert.dismiss() # 点击取消
alert.send_keys("输入内容") # prompt 输入
# 等待弹窗出现
WebDriverWait(driver, 10).until(EC.alert_is_present())
4.8 Cookie 操作
# 获取所有 cookies
cookies = driver.get_cookies()
for cookie in cookies:
print(f"{cookie['name']} = {cookie['value']}")
# 获取指定 cookie
cookie = driver.get_cookie("session_id")
# 添加 cookie
driver.add_cookie({
"name": "token",
"value": "abc123",
"domain": ".example.com",
"path": "/",
"secure": True,
"httpOnly": True
})
# 删除指定 cookie
driver.delete_cookie("token")
# 删除所有 cookies
driver.delete_all_cookies()
4.9 截图与文件操作
# 全页面截图
driver.save_screenshot("/tmp/screenshot.png")
# 元素截图
element = driver.find_element(By.ID, "target")
element.screenshot("/tmp/element.png")
# 获取截图二进制数据 (用于上传/报告)
png_data = driver.get_screenshot_as_png()
base64_data = driver.get_screenshot_as_base64()
# 获取元素 HTML
html = element.get_attribute("outerHTML")
4.10 完整示例 — 自动登录
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 EC
from selenium.webdriver.chrome.options import Options
def auto_login(url, username, password):
options = Options()
options.add_argument("--disable-notifications")
driver = webdriver.Chrome(options=options)
try:
driver.get(url)
wait = WebDriverWait(driver, 15)
# 等待登录表单加载
username_field = wait.until(
EC.presence_of_element_located((By.ID, "username"))
)
password_field = driver.find_element(By.ID, "password")
submit_btn = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))
)
# 输入凭据
username_field.clear()
username_field.send_keys(username)
password_field.clear()
password_field.send_keys(password)
# 提交
submit_btn.click()
# 等待登录成功 (URL 变化或 dashboard 元素出现)
wait.until(EC.url_contains("/dashboard"))
print(f"✅ 登录成功! 当前页面: {driver.title}")
return True
except Exception as e:
print(f"❌ 登录失败: {e}")
driver.save_screenshot("/tmp/login_error.png")
return False
finally:
driver.quit()
if __name__ == "__main__":
auto_login("https://example.com/login", "admin", "secret123")
4.11 要点回顾
| 要点 | 说明 |
|---|---|
| 优先使用 ID/CSS Selector | ID 最稳定,CSS Selector 灵活高效 |
| 显式等待是必须的 | 不要使用 time.sleep(),用 WebDriverWait |
| 等待条件要精确 | 用 element_to_be_clickable 而不是 presence_of_element_located |
| ActionChains 处理复杂交互 | 拖拽、悬停、组合键等高级操作 |
| iframe 需显式切换 | 操作 iframe 内容前必须 switch_to.frame() |
| 异常处理要完善 | 捕获异常并截图保存现场 |
4.12 注意事项
⚠️ 不要混合隐式和显式等待: 两者混用会导致不可预期的超时行为,建议全局只使用显式等待。
⚠️ StaleElementReferenceException: 页面刷新或 DOM 变化后,之前定位的元素引用会失效,需要重新定位。
⚠️ 元素被遮挡:
click()时如果元素被其他元素遮挡,会抛出ElementClickInterceptedException,需先滚动到可见位置或关闭遮挡层。⚠️ XPath 性能: XPath 在现代浏览器中性能已不是问题,但
//div等宽泛匹配仍可能较慢,尽量缩小搜索范围。