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()
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')
# 现在已经是登录状态了
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()
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_element 和 find_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操作和页面等待机制,这四块内容就讲完了。说白了,它们都是为了让你的自动化脚本更稳定、更智能。下一章我们会聊到「元素定位与交互操作」,到时候你会看到这些等待机制的实际应用场景。