强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

04 - 页面自动化实战

04 - 页面自动化实战

掌握元素定位、等待策略、页面交互、表单操作等浏览器自动化的核心技能。


4.1 元素定位

元素定位是浏览器自动化的基石——找到正确的元素,才能与之交互。

定位器 (Locator) 总览

定位器By 方法语法示例说明
IDBy.ID"username"最稳定,推荐首选
NameBy.NAME"email"表单元素常用
Class NameBy.CLASS_NAME"btn-primary"匹配单个 class
Tag NameBy.TAG_NAME"input"宽泛匹配
Link TextBy.LINK_TEXT"点击这里"精确匹配链接文本
Partial Link TextBy.PARTIAL_LINK_TEXT"点击"部分匹配链接文本
CSS SelectorBy.CSS_SELECTOR"#login .btn"灵活强大
XPathBy.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_containsURL 包含指定字符串
alert_is_present出现 JavaScript 弹窗
number_of_windows_to_be窗口数量达到指定值
frame_to_be_available_and_switch_to_itiframe 可用并自动切换

自定义等待条件

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())

# 获取所有 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 SelectorID 最稳定,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 等宽泛匹配仍可能较慢,尽量缩小搜索范围。


4.13 扩展阅读

资源链接
Selenium 定位策略https://www.selenium.dev/documentation/webdriver/elements/locators/
Selenium 等待https://www.selenium.dev/documentation/webdriver/waits/
Selenium 交互https://www.selenium.dev/documentation/webdriver/elements/interactions/
XPath 语法参考https://developer.mozilla.org/en-US/docs/Web/XPath
CSS Selectors 参考https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors