AUTOMATION · SELENIUM · 浏览器自动化与测试

Selenium:让浏览器替你干活

重复点按钮、填表单、截图验证,都该交给脚本。这页用 Selenium 4.x 现行写法,从装包到跑通一个「打开页面 → 搜索 → 断言 → 截图」的完整脚本——Selenium Manager 已经替你管驱动,新手最大的坑没有了。

zsh — ~/autolab
$ pip install selenium
Successfully installed selenium-4.52.0
$ python search_test.py
启动 Chrome(驱动由 Selenium Manager 自动下载)→ 打开搜索页
输入关键词 → 等待结果加载…
断言通过:结果数 12 > 0,截图已保存 result.png
本页目录

01

Selenium 是什么:浏览器自动化的事实标准

Selenium 是驱动真实浏览器的自动化框架,也是 W3C WebDriver 协议的参考实现。它启动一个货真价实的 Chrome/Firefox/Edge——能执行 JavaScript、渲染 CSS、弹 Cookie——然后替你点击、输入、滚动、截图。两个典型用途:UI 自动化测试(回归测试的主力)和网页抓取(对付需要执行 JS 才能出内容的页面)。

它比 requests 慢得多,但看到的就是用户看到的。什么时候用它:目标页面靠 JS 渲染、需要登录交互、或者你写的本来就是测试。什么时候不用:静态页面用 requests + 解析库就够,快一个数量级。

两句话记住它的位置:测试眼里它是回归测试的执行器——每次发版前把关键流程点一遍;数据眼里它是「能交互的抓取器」——滚动加载、点击展开、登录后才可见的内容它都能拿。代价是快不起来,所以能不用则不用。

定位
W3C WebDriver 协议参考实现,浏览器自动化事实标准
当前版本
4.52(2026-11 查 PyPI,主线 4.x)
Python 要求
≥ 3.10
协议
Apache 2.0
驱动管理
Selenium Manager 内置(4.6+),不再手配 chromedriver
支持浏览器
Chrome / Firefox / Edge / Safari
语言绑定
Python / Java / C# / Ruby / JavaScript
老教程写法
find_element_by_* 已在 4.3 移除,勿用
为什么 4.6 是分水岭:以前新手要手动下载 chromedriver,还必须和浏览器版本严格对齐——版本差一位就报错,这是 Selenium 学不下去的头号原因。4.6 起 Selenium Manager 内置在绑定里:你写 webdriver.Chrome(),它自动检测/下载匹配的驱动(必要时连浏览器都能帮你装)。2022 年之后的教程如果还教你「先去官网下 chromedriver」,直接跳过那段。

02

安装:一个包 + 一个本机浏览器

Selenium 本体很小,真正的「环境」是你的浏览器。本机装有 Chrome、Firefox、Edge 任意一个即可,驱动交给 Selenium Manager。

Terminal
# 装包,一行搞定;建议先建虚拟环境
pip install selenium

# 不需要装 webdriver-manager!4.6+ 的 Selenium Manager 已内置接管
网上大量教程让你 pip install webdriver-manager——那是 Selenium Manager 出现前的方案。现在仅在离线环境、特殊浏览器版本等边缘场景才需要它。
浏览器要留着自己升级:Selenium Manager 按你当前的浏览器版本找驱动,浏览器本身太旧反而容易出兼容问题。保持 Chrome/Edge 自动更新即可,不要手动去网上下载来历不明的 chromedriver——老习惯,现在有害无益。

03

快速上手:十行代码自动搜索一次

Python · first_run.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

driver = webdriver.Chrome()                # Selenium Manager 自动管驱动
driver.get("https://www.bing.com")

box = driver.find_element(By.NAME, "q")   # 定位搜索框(name 属性是 q)
box.send_keys("Selenium WebDriver", Keys.RETURN)   # 输入并回车

print(driver.title)                        # 现在已是结果页的标题
driver.save_screenshot("result.png")      # 截一张图存档
driver.quit()                              # 关浏览器、结束会话
  • 01

    启动会话

    webdriver.Chrome() 返回 driver 对象,它是你接下来所有操作的入口。Firefox/Edge 换成 webdriver.Firefox() / webdriver.Edge(),用法完全一样。

  • 02

    打开页面

    driver.get(url) 会等页面基本加载完成才返回(与 requests 不同,它真的渲染 JS)。

  • 03

    定位 + 操作

    所有交互都是两步:find_element 找到元素,再 click() / send_keys()。Keys.RETURN 模拟回车,Keys.CONTROL + "a" 这类组合键也在 Keys 里。

  • 04

    断言 + 收尾

    测试的本质是断言:title 对不对、元素在不在、文本有没有。最后一定 quit()——不 quit 会留下一堆残留的 chrome 进程。

第一次运行会明显停顿几秒——那是 Selenium Manager 在下载驱动,之后有缓存就快了。想看它到底用了哪个驱动,把 driver 启动包在 try/except 里打印异常即可,报错信息会写明原因。

04

核心概念:定位、等待、页面操作与无头模式

WebDriver 与 Selenium Manager:手配驱动已成历史

3.x 时代的入门流程是「查浏览器版本 → 官网下载对应 chromedriver → 放进 PATH → 版本不匹配再重来」。Selenium 4.6 把这套流程收进 Selenium Manager:一个用 Rust 写的内置工具,绑定启动时自动检测浏览器、下载/缓存匹配的驱动。你要做的只有 webdriver.Chrome()。只有在驱动下载不通(内网)或要固定驱动版本时才需要人工介入。

八大定位器:find_element 与 find_elements

定位是 Selenium 的基本功。统一入口是 find_element(By.X, "值")——注意 4.3 起老写法 find_element_by_id() 已彻底删除。单数版返回第一个匹配(找不到抛 NoSuchElementException),复数版 find_elements 返回列表(找不到给空列表,不报错)。优先级经验:CSS_SELECTOR 为主,XPATH 兜底——CSS 快且直观,XPATH 能向上找父节点、按文本定位。

Python · 定位示例
driver.find_element(By.ID, "username")
driver.find_element(By.CSS_SELECTOR, "#login input[name='password']")
driver.find_element(By.XPATH, "//button[contains(text(), '登录')]")

items = driver.find_elements(By.CSS_SELECTOR, ".search-result li")
print(len(items))        # find_elements 找不到返回 [],适合计数与遍历

等待机制:不懂这个必踩坑

页面是异步的:你 get 之后,内容可能还没渲染出来,find_element 立刻就找不到——九成的「No such element」都是这个。time.sleep 硬等是下策:等短了还是炸,等长了白浪费。正确姿势是显式等待:给一个最长期限,条件一满足立刻往下走。

Python · 显式等待(标准写法)
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 最多等 10 秒,元素「可见且可点」就立刻返回
btn = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, "#submit"))
)
btn.click()

# 隐式等待是另一条路:全局设置一次,所有 find_element 自动轮询
driver.implicitly_wait(5)
# 注意:隐式与显式不要混用,会造成难以理解的叠加等待
怎么选:全站统一隐式等待(5 秒左右)打底,关键步骤用显式等待精确控制——等的是「某个条件成立」而不是「碰运气的时间」。EC 里最常用的几个见第 05 节速查表。

页面操作、无头模式与 Options

常用操作三板斧:点击 click()、输入 send_keys()、清空 clear();下拉框要包一层 Select;多窗口和 iframe 用 switch_to 切换;特殊操作(滚动、改属性)直接执行 JS。跑在服务器上没有显示器?headless 无头模式让浏览器在内存里渲染,过程一样、不弹窗口。

Python · Options 与无头模式
options = webdriver.ChromeOptions()
options.add_argument("--headless=new")        # 新版无头模式
options.add_argument("--window-size=1920,1080")  # 无头下也要给尺寸
options.add_argument("--disable-gpu")

driver = webdriver.Chrome(options=options)

# 切换窗口 / iframe / 执行 JS 的常用姿势
driver.switch_to.window(driver.window_handles[-1])   # 切到新开的窗口
driver.switch_to.frame("login-iframe")              # 进 iframe
driver.switch_to.default_content()                   # 出来,回主文档
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")

05

速查表:定位、等待与常用操作

下面这块表格能搜。忘了某个定位器或条件怎么写,直接搜 点击、等待、iframe 之类的词。

定位器说明
By.ID按 id 属性定位,页内唯一,优先用
By.NAME按 name 属性,常见于表单控件
By.CSS_SELECTORCSS 选择器,如 "#main .item a";首选方案
By.XPATHXML 路径,如 //input[@name='q'];能向上找、按文本找
By.CLASS_NAME单个 class 名(不能写多个空格分隔)
By.TAG_NAME按标签名,如 button、tr
By.LINK_TEXT链接的完整文字精确匹配
By.PARTIAL_LINK_TEXT链接文字的部分匹配
find_element(By.X, "v")返回第一个匹配;找不到抛 NoSuchElementException
find_elements(By.X, "v")返回列表;找不到是空列表,不报错
el.find_element(By.X, "v")在元素内部继续找,缩小范围
locate_with(By.TAG_NAME, "input").below(el)Selenium 4 相对定位器:按方位找(above/below/near)
顺序建议先 ID/NAME,再 CSS_SELECTOR,动态结构才上 XPATH

06

实战:搜索自动化完整脚本

把概念串成一个能跑的测试脚本:打开搜索引擎 → 等输入框可点 → 输入关键词回车 → 显式等结果列表 → 断言结果非空 → 截图存档。约 35 行,本机装有 Chrome 即可跑:

Python · search_test.py
# 运行前:pip install selenium(本机装有 Chrome,驱动自动管理)
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

KEYWORD = "Selenium WebDriver"

def main():
    options = webdriver.ChromeOptions()
    options.add_argument("--window-size=1280,900")

    driver = webdriver.Chrome(options=options)
    try:
        driver.get("https://www.bing.com")

        # 1. 等搜索框「可点」再输入,不用 time.sleep 硬等
        box = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.NAME, "q"))
        )
        box.send_keys(KEYWORD + "\n")          # 回车即搜索

        # 2. 显式等待结果列表出现(页面异步渲染,必须等)
        results = WebDriverWait(driver, 10).until(
            EC.presence_of_all_elements_located(
                (By.CSS_SELECTOR, "#b_results > li")
            )
        )

        # 3. 断言:这就是自动化测试与裸跑的区别
        assert len(results) > 0, "结果列表为空,搜索可能失败"
        first = results[0].text.replace("\n", " ")
        print(f"共 {len(results)} 条结果,第一条:{first[:60]}")

        # 4. 截图存档,方便回看与写报告
        driver.save_screenshot("result.png")
        print("截图已保存 result.png")
    finally:
        driver.quit()                        # 无论成败都关浏览器

if __name__ == "__main__":
    main()

运行效果

Terminal
$ python search_test.py
共 12 条结果,第一条:Selenium automates browsers...
截图已保存 result.png
改造成你自己的用例:把 get 的网址、定位器、断言换成你的业务页面即可。选择器会随页面改版失效——报 NoSuchElementException 时先人工打开页面,右键检查元素,更新选择器。脚本一旦多起来,就按下面两步升级工程结构。

升级一:用 pytest 组织用例(点名)

真实项目不会一个脚本跑到底,而是用 pytest:fixture 负责开关浏览器(每个用例一份全新会话),用例就是一个个断言函数,失败自动记录。上面的脚本拆进去大概是这个样子:

Python · test_search.py(pip install pytest 后运行 pytest)
import pytest
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

@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    yield driver          # 用例在这里运行
    driver.quit()         # 无论成败都会执行

def test_bing_search(driver):
    driver.get("https://www.bing.com")
    box = WebDriverWait(driver, 10).until(
        EC.element_to_be_clickable((By.NAME, "q"))
    )
    box.send_keys("Selenium WebDriver\n")
    results = WebDriverWait(driver, 10).until(
        EC.presence_of_all_elements_located((By.CSS_SELECTOR, "#b_results > li"))
    )
    assert len(results) > 0

升级二:Page Object 模式简介

用例一多,「选择器散落在每个测试里」就是灾难——页面一改版要改几十处。Page Object 的解法:每个页面封装成一个类,定位器和操作只写在类里,用例只调用方法。改版时只改 Page 类一处:

Python · pages.py 与用例的分工
# pages.py —— 页面长什么样,只有这个文件知道
from selenium.webdriver.common.by import By

class SearchPage:
    BOX = (By.NAME, "q")
    RESULTS = (By.CSS_SELECTOR, "#b_results > li")

    def __init__(self, driver):
        self.driver = driver

    def search(self, keyword):
        self.driver.find_element(*self.BOX).send_keys(keyword + "\n")

    def result_count(self) -> int:
        return len(self.driver.find_elements(*self.RESULTS))

# test_search.py —— 用例里只出现业务语言,不出现选择器
def test_bing_search(driver):
    page = SearchPage(driver)
    driver.get("https://www.bing.com")
    page.search("Selenium WebDriver")
    assert page.result_count() > 0

07

避坑:四个最常见的坑

坑一:照老教程写 find_element_by_id,报 AttributeError

Selenium 4.3 起,find_element_by_* / find_elements_by_* 系列方法全部移除,2022 年前的教程和 Stack Overflow 高票答案里到处都是。统一改成新写法:

Python · 新旧对照
# 旧(4.3 起已删除,直接报错)
driver.find_element_by_id("username")

# 新(唯一写法)
from selenium.webdriver.common.by import By
driver.find_element(By.ID, "username")
自查方法:代码里出现 find_element_by 这个前缀,就是老写法。同理,看到「先下载 chromedriver 放 PATH」的教程段落,也可以跳过——那是 4.6 之前的世界。

坑二:NoSuchElementException——九成是没等待

页面 JS 渲染需要时间,get 返回 ≠ 内容就绪。解决顺序:先用显式等待包住关键定位;等待后仍失败,再人工检查选择器写没写对(id 是不是动态生成的);最后才怀疑页面结构变了。把 time.sleep(3) 全文搜一遍,每处都换成 WebDriverWait。

Python · 坏写法与好写法对照
# 坏:猜时间,猜错了就炸
time.sleep(3)
driver.find_element(By.ID, "result")

# 好:等条件成立,最快 0.1 秒、最慢 10 秒
el = WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.ID, "result"))
)

坑三:SessionNotCreatedException / 驱动相关报错

Selenium Manager 需要联网下载驱动:公司内网可能被拦——配代理或手动下载驱动放进 PATH。Docker 里跑还要注意:容器里没有浏览器,要装 chromium 类镜像包,并加 --headless=new --no-sandbox --disable-gpu 三件套。本机报错时,先升级浏览器到最新版再重试,多数情况自愈。

坑四:合规红线——自动化不等于可以乱来

用它做自己系统的测试天经地义;抓第三方站点前,看两样东西:目标站的 robots.txt 与服务条款是否允许。控制频率(加延时、别并发轰炸)、别碰验证码与登录态倒卖这类灰产场景、数据只做个人研究不商用转卖。脚本身份是浏览器,但责任主体是你。

08

进阶方向与资源

学完本页去哪:把第 06 节的脚本挂进 pytest(fixture 管理 driver,一个页面一个类);再学 Page Object 模式——把每个页面的定位器和操作封装成类,用例里只调方法不碰选择器,改版时只改一处。这两步走完,你就有了正经 UI 自动化工程的骨架。

09

收尾:照着勾一遍

0 / 5

已复制