3、浏览器操控:窗口管理、前进后退、Cookie操作与页面等待机制

窗口管理、前进后退、Cookie操作和页面等待,这四块内容说白了就是浏览器自动化的「基本功」。我刚开始写自动化脚本时,总觉得能打开网页、能点按钮就够了。结果呢?窗口一多就乱套,页面加载慢了就报错,Cookie没处理好就得反复登录。嗯,今天咱们就把这些坑一个个填平。

3.1 窗口管理:别让标签页失控

你想想看,一个自动化流程里,可能同时打开好几个页面。比如我在做电商比价工具时,需要同时打开京东、淘宝、拼多多三个页面。如果不会管理窗口,那画面简直不敢看。

Selenium 里窗口是用「句柄」来区分的。每个标签页或窗口都有一个唯一的 ID。我个人习惯用 window_handles 来获取所有窗口列表,然后用 switch_to.window() 来切换。

from selenium import webdriver
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome()
driver.get('https://www.baidu.com')

# 打开一个新标签页
driver.execute_script('window.open("https://www.taobao.com")')
time.sleep(1)

# 获取所有窗口句柄
handles = driver.window_handles
print(f'当前有 {len(handles)} 个窗口')

# 切换到第二个窗口(淘宝)
driver.switch_to.window(handles[1])
print(f'当前页面标题: {driver.title}')

# 再切回第一个窗口(百度)
driver.switch_to.window(handles[0])
print(f'当前页面标题: {driver.title}')

# 关闭当前窗口
driver.close()

# 记得切回剩下的窗口
driver.switch_to.window(handles[1])
driver.quit()
⚠️ 注意: 关闭窗口后,一定要手动切换到其他窗口。否则你会遇到「NoSuchWindowException」—— 我曾经因为这个bug排查了整整一下午。

3.2 前进与后退:模拟浏览器的「返回键」

前进后退这个功能,说实话平时用得不多。但有一种场景特别需要:表单提交后的页面跳转验证。比如你提交了一个注册表单,页面跳转到「注册成功」,你想验证一下能不能回到上一页重新填写。

Selenium 提供了 back()forward() 方法,说白了就是模拟浏览器左上角的箭头按钮。

driver.get('https://www.baidu.com')
print(f'第一步: {driver.title}')

driver.get('https://www.taobao.com')
print(f'第二步: {driver.title}')

# 后退到百度
driver.back()
print(f'后退后: {driver.title}')

# 再前进到淘宝
driver.forward()
print(f'前进后: {driver.title}')
💡 小技巧: 前进后退依赖浏览器的历史记录。如果你用 driver.get() 跳转,历史记录会保留。但如果你用 execute_script('window.location.href = ...') 跳转,历史记录可能不完整。我建议统一用 driver.get()

3.3 Cookie操作:保持登录状态的秘密

Cookie 这个东西,说白了就是网站用来记住你的「小纸条」。每次请求都会带上它,告诉服务器「我是谁」。在自动化测试中,Cookie 最大的用处就是 —— 免登录。

我记得有一次做爬虫项目,需要抓取某个需要登录的网站数据。每次启动脚本都要手动登录,烦得要死。后来我学会了 Cookie 持久化,把登录后的 Cookie 存到文件里,下次直接加载,省事多了。

3.3.1 获取与打印 Cookie

driver.get('https://www.baidu.com')

# 获取所有 Cookie
cookies = driver.get_cookies()
for cookie in cookies:
    print(f'名称: {cookie["name"]}, 值: {cookie["value"]}')

# 获取单个 Cookie
baidu_cookie = driver.get_cookie('BAIDUID')
print(f'BAIDUID: {baidu_cookie}')

3.3.2 添加与删除 Cookie

# 添加自定义 Cookie
driver.add_cookie({
    'name': 'my_custom_cookie',
    'value': 'hello_selenium',
    'domain': '.baidu.com'
})

# 删除单个 Cookie
driver.delete_cookie('my_custom_cookie')

# 删除所有 Cookie
driver.delete_all_cookies()

3.3.3 Cookie 持久化实战

这是我个人最常用的模式。先手动登录一次,把 Cookie 存起来。以后启动脚本直接加载,不用再登录。

import pickle
import os

COOKIE_FILE = 'cookies.pkl'

def save_cookies(driver):
    """保存 Cookie 到文件"""
    with open(COOKIE_FILE, 'wb') as f:
        pickle.dump(driver.get_cookies(), f)
    print('Cookie 已保存')

def load_cookies(driver, url):
    """从文件加载 Cookie"""
    if not os.path.exists(COOKIE_FILE):
        print('没有找到 Cookie 文件,请先手动登录')
        return False
    
    # 先访问目标域名(必须同域才能添加 Cookie)
    driver.get(url)
    
    with open(COOKIE_FILE, 'rb') as f:
        cookies = pickle.load(f)
    
    for cookie in cookies:
        # 有些 Cookie 有 'sameSite' 字段,需要移除
        cookie.pop('sameSite', None)
        driver.add_cookie(cookie)
    
    # 刷新页面,Cookie 生效
    driver.refresh()
    print('Cookie 已加载')
    return True

# 使用示例
driver = webdriver.Chrome()

# 第一次:手动登录后保存 Cookie
driver.get('https://example.com/login')
# ... 手动输入账号密码 ...
input('请手动登录,然后按回车继续...')
save_cookies(driver)

# 第二次:直接加载 Cookie
load_cookies(driver, 'https://example.com/login')
# 现在已经是登录状态了
⚠️ 注意: Cookie 有有效期和域名限制。过期的 Cookie 加载了也没用。另外,有些网站会校验 User-Agent 或 IP,换了环境 Cookie 可能失效。

3.4 页面等待机制:别让脚本跑得太快

这是自动化脚本最容易翻车的地方。你想想看,脚本执行速度比页面加载速度快得多。你刚打开一个页面,脚本就去点按钮,结果按钮还没渲染出来 —— 报错!

Selenium 提供了三种等待方式。我个人强烈推荐第三种,因为它最智能。

3.4.1 强制等待(time.sleep)

最粗暴的方式,就是让脚本「睡一会儿」。简单直接,但效率低。

import time

driver.get('https://example.com/slow_page')
time.sleep(5)  # 不管页面加载完没,都等5秒
driver.find_element(By.ID, 'submit').click()
❌ 不推荐: 如果页面2秒就加载完了,你白白等了3秒。如果页面需要10秒,你只等了5秒,照样报错。说白了就是「赌运气」。

3.4.2 隐式等待(implicitly_wait)

设置一个全局等待时间。每次查找元素时,如果没找到,会轮询等待,直到超时。

driver.implicitly_wait(10)  # 全局等待10秒
driver.get('https://example.com/slow_page')

# 如果元素在3秒后出现,3秒就返回
# 如果10秒后还没出现,抛出 NoSuchElementException
element = driver.find_element(By.ID, 'submit')
element.click()

嗯,这里要注意:隐式等待只对 find_elementfind_elements 有效。对 get() 加载页面本身无效。而且它不能判断元素是否可点击、是否可见。

3.4.3 显式等待(WebDriverWait)—— 我的最爱

显式等待可以针对特定条件进行等待。比如等待按钮变成可点击状态,等待某个文本出现。这才是真正的「智能等待」。

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver.get('https://example.com/slow_page')

# 等待最多10秒,直到按钮可点击
wait = WebDriverWait(driver, 10)
submit_btn = wait.until(
    EC.element_to_be_clickable((By.ID, 'submit'))
)
submit_btn.click()

# 等待某个元素出现
element = wait.until(
    EC.presence_of_element_located((By.CLASS_NAME, 'result'))
)
print(element.text)

# 等待页面标题变化
wait.until(EC.title_contains('成功'))
print('页面标题已更新')

3.4.4 常用等待条件一览

条件 说明 使用场景
presence_of_element_located 元素出现在DOM中 页面刚加载,元素已存在但可能不可见
visibility_of_element_located 元素可见(宽高>0) 弹窗、下拉菜单等需要可见才能操作
element_to_be_clickable 元素可见且可点击 按钮、链接等交互元素
text_to_be_present_in_element 元素包含指定文本 等待加载完成提示
title_contains 页面标题包含指定文本 页面跳转完成判断
alert_is_present 弹窗出现 处理 alert 弹窗
💡 最佳实践: 我一般这样搭配使用:
1. 设置一个较短的隐式等待(3-5秒),作为兜底。
2. 对关键操作使用显式等待,设置合理的超时时间(10-15秒)。
3. 尽量避免强制等待,除非你明确知道某个操作需要固定时间(比如动画效果)。

3.5 避坑指南:我踩过的那些坑

最后分享几个我亲身经历过的坑,希望能帮你少走弯路。

  • 窗口句柄顺序问题: 我曾经以为 window_handles[0] 一定是第一个打开的窗口。后来发现,如果页面通过 JavaScript 打开新窗口,句柄顺序可能和打开顺序不一致。解决办法:用 set() 记录已知句柄,新出现的句柄就是新窗口。
  • Cookie 的 sameSite 属性: 加载 Cookie 时经常报错,说「InvalidCookieDomainException」。排查了半天,发现是 Cookie 里的 sameSite 字段在作怪。解决办法:加载前用 cookie.pop('sameSite', None) 移除它。
  • 显式等待超时: 有时候元素明明存在,但显式等待就是超时。原因可能是元素被 iframe 包裹了。记得先 switch_to.frame() 切换到对应的 iframe。
  • 页面加载超时: 有些页面加载特别慢,或者有资源加载失败。可以设置 driver.set_page_load_timeout(30),超时后捕获异常继续执行,而不是卡死。

好了,窗口管理、前进后退、Cookie操作和页面等待机制,这四块内容就讲完了。说白了,它们都是为了让你的自动化脚本更稳定、更智能。下一章我们会聊到「元素定位与交互操作」,到时候你会看到这些等待机制的实际应用场景。