系列 · 影刀 × Agent 开发实战 第 2 篇 / 共 2 篇 教程

自动化实战:CSS 定位怎么写才稳定?

从稳定属性、属性匹配、范围控制到影刀里的文字定位、唯一性验证和排错路线,整理网页自动化中更可维护的 CSS 定位方法。

作者:黄撑 更新于 2026-07-13
本文目录 9 节 · 点击展开

刚开始学自动化,CSS 定位很容易被讲成“背几个语法”。真实项目里更重要的问题是:这个选择器能不能长期命中正确元素,失效后能不能快速知道原因。

本文面向网页自动化场景,尤其是影刀编码版常见的 browser.find_by_css()browser.find_all_by_css()browser.find_by_xpath()。临时操作可以只求能用;交给 RPA 长期运行时,定位必须像代码一样可读、可验证、可维护。

CSS SELECTOR FIELD MANUAL网页自动化定位图解手册
一句话结论稳定定位不是把路径写长,
而是逐层减少不确定性。

先限定业务区域,再找稳定特征,用最少条件收窄目标,最后把匹配数量变成可检查的结果。

区域特征组合验证
PAGE整个页面
.order-dialog订单弹窗
tr[data-order-id]目标订单行button[data-action=“confirm”]
优先 稳定属性控制 业务范围检查 匹配数量兜底 页面状态

稳定定位到底在解决哪几层问题?

只会写选择器,解决的是“能否命中”;能把范围、唯一性和页面上下文一起考虑,才是在解决自动化稳定性。

01 · CAPABILITY STACK四层能力栈

每往下一层,定位从“碰巧找到”更接近“可长期维护”。

01
基础命中先找到候选元素
input[name=“keyword”]回答:页面上有没有它?
02
精准匹配处理动态属性
[class*=“comments—“]回答:哪个片段真正稳定?
03
范围控制先锁业务容器
.dialog .confirm回答:它属于哪个弹窗或记录?
04
唯一验证让错误显式发生
len(elements) == 1回答:是 0 个、1 个还是多个?
能力边界入门掌握基础命中;真实项目至少同时使用精准匹配、范围控制和唯一验证。

基础选择器怎么选,才不会一开始就走偏?

基础语法不需要死背。先判断元素有没有“表达功能的稳定特征”,再决定用 id、class 还是属性。

02 · SELECTOR INDEX六种入口,一条优先级

越靠近业务语义,通常越适合自动化;越依赖位置和样式,维护风险越高。

更稳定更依赖页面结构
属性input[name=“username”]name、稳定且表达业务或测试语义的 data-*aria-label 优先
id#submit-order仅在 id 稳定且唯一时使用
组合input.form-input[name=“keyword”]单条件不唯一时补必要条件
范围.dialog .confirm页面有同名元素时先缩小区域
class.button.primary确认是业务类名,不是随机样式名
标签button适合找候选,不建议单独点击
HTML 样本
<input
id="username"
class="login-input"
name="username"
placeholder="请输入用户名"
type="text"
/>
推荐input[name=“username”]

name 更像程序字段,能直接说明用途。

不优先input[placeholder=“请输入用户名”]

placeholder 是展示文案,更容易被产品调整。

基础判断优先找能表达功能的稳定属性,不复制浏览器生成的完整层级路径。

六种属性匹配分别该在什么时候用?

页面没有干净的 id 或专用测试属性时,关键不是立即改用长路径,而是识别属性值中哪一部分稳定。

03 · ATTRIBUTE GAUGE属性匹配刻度尺

从精确到宽松,匹配范围逐渐扩大;越宽松,越需要范围控制和数量验证。

=完全等于[data-action=“submit”]属性值完整且稳定
^=固定开头[id^=“order-”]前缀稳定、后半段变化
$=固定结尾[href$=“.xlsx”]文件名或状态后缀稳定
*=包含片段[class*=“comments—“]动态值中间有稳定语义
~=空格分词[class~=“primary”]属性由多个独立词组成
|=词或词前缀[lang|=“zh”]等于 zh 或以 zh- 开头
动态 class
<div class="comments--ChxC7GEN">评价区</div>

随机后缀会变化,稳定片段是 comments--

候选写法div[class*=“comments—“]

可用于收窄候选,但必须检查匹配数量。

[class~=“primary”]class=“button primary large”
[class~=“primary”]class=“primary”
[class~=“primary”]×class=“primary-button”
风险边界*= 不是更高级,只是更宽松。能精确匹配时不要先用模糊匹配。

批量选择器可以先拿到候选,再按文本、状态或其它属性筛选:

rows = browser.find_all_by_css('tr[data-order-id^="202607"]', timeout=10)

关系选择器和伪类,什么时候是在帮忙,什么时候在添风险?

关系选择器负责表达“元素在哪个容器、哪一层、哪个相邻位置”;伪类负责表达“元素是什么状态或位置”。它们解决的问题不同。

04 · DOM RELATION MAP先看关系,再评估结构依赖

业务容器通常比页面位置稳定;位置型伪类通常比属性状态更脆弱。

.dialog业务容器 A
.toolbar直接子元素 B
label相邻参照 A
input目标元素 B
button后续同级 B
A B任意后代.dialog button最常用的范围控制
A > B直接子元素.menu > li避免继续命中深层元素
A + B紧邻兄弟label + input依赖相邻结构,改版可能失效
A ~ B后续同级.title ~ button命中 A 后面的所有同级 B
低风险:checked:disabled状态明确,适合判断控件状态
中风险:not(…):first-child适合排除干扰或固定结构
高风险:nth-child(n):last-child数据和顺序变化时容易错位
需运行验证:has(…)表达力强,但要确认影刀实际运行环境支持
tr:has([data-status=“待处理”]) button.process

语义是“找到包含待处理状态的表格行,再取这一行的处理按钮”。如果能用订单 id 直接定位,就不必为了复杂而使用 :has()

使用原则范围关系优先服务业务语义;结构关系和位置伪类只在没有更稳定特征时使用。

写一个选择器时,应该按什么顺序下手?

不要从浏览器复制路径开始。把定位当成一次从业务区域到唯一元素的逐层诊断。

05 · LOCATOR PROCEDURE四步定位仪

每一步都有输入、动作和退出条件;任何一步信息不足,都先回页面确认。

01
确定区域

页面主体、弹窗、表格、商品卡片

退出条件:业务容器已明确
02
寻找稳定特征

id、name、具备业务或测试语义的 data-*、aria-label

退出条件:特征稳定且能表达功能
03
组合必要条件

只补足能消除歧义的条件

退出条件:选择器仍然短且可读
04
验证匹配数量

0 个、1 个、多个分别处理

退出条件:目标唯一且语义正确
优先级1稳定且表达业务或测试语义的 data-* / name / aria-label
优先级2稳定且有意义的 id
优先级3稳定且有意义的 class
优先级4标签 + 属性组合
最后考虑5nth-child
× 复制超长路径× 只靠随机 class× 模糊匹配不验数量

这张定位仪就是写代码时的检查顺序:先确认范围和稳定特征,再组合,最后把数量验证写进程序。

CSS、XPath 和 get_text() 应该怎么分工?

CSS 是主力定位方式,但不是唯一方式。只有显示文字稳定时,硬凑 CSS 往往会把问题复杂化。

06 · ROUTING CONSOLE定位方式路由台

先判断稳定信息来自属性、文字还是页面上下文,再选工具。

START目标元素有什么稳定信息?
稳定属性优先 CSSbutton[data-action=“submit”]短、清楚,适合范围组合
只有文字改用 XPath//button[normalize-space(.)=“提交订单”]标准 CSS 不能匹配内部文字
先限范围再看文字CSS + get_text().order-dialog button先拿候选,再在 Python 中筛选
上下文异常先处理页面状态iframe / popup / async加载、弹窗和 iframe 不是选择器语法问题
不能匹配内部文字button[text=“提交订单”]
非标准 / 无效 CSSbutton:contains(“提交订单”)
前者是合法属性选择器,但只会检查 text 属性;后者不是标准 CSS。
分工结论CSS 缩小结构范围,XPath 处理稳定文字,get_text() 在候选集合中完成业务筛选。

按文字直接定位:

button = browser.find_by_xpath(
    '//button[normalize-space(.)="提交订单"]',
    timeout=10,
)
button.click()

先限定弹窗,再按文字筛选:

buttons = browser.find_all_by_css(".order-dialog button", timeout=10)

for button in buttons:
    if button.get_text().strip() == "提交订单":
        button.click()
        break
else:
    raise RuntimeError('未找到文本为“提交订单”的按钮')

怎样验证唯一性,并判断“找不到”到底是哪一层出了问题?

先在开发者工具中确认 HTML 和匹配数量,再进入影刀运行环境。这样可以把“选择器语法”和“页面状态”分开排查。

07 · VALIDATION LAB唯一性测试台

一个选择器至少要通过“语法、数量、语义、运行环境”四道检查。

1检查元素F12 → Inspect观察标签、id、class、name、data-*、aria-label
2Console 验证document.querySelectorAll(…)先确认浏览器里能否匹配
3读取数量0 / 1 / 多个不要只看结果列表里“好像有目标”
4影刀复验find_all_by_css(…)验证加载、iframe 和实际运行环境
0未找到

检查语法、加载状态、弹窗和 iframe。

1理想结果

继续确认它的文本、属性和业务语义。

2+范围太大

增加业务容器或稳定条件,不直接点击。

可观察失败
selector = '.order-dialog button.submit:not([disabled])'
elements = browser.find_all_by_css(selector, timeout=10)

if len(elements) == 0:
  raise RuntimeError(f"未找到元素:{selector}")

if len(elements) > 1:
  raise RuntimeError(f"定位不唯一:{selector},数量={len(elements)}")

elements[0].click()

报错直接区分“没找到”和“找到太多”,后续维护不必重新猜。

验收标准不仅要“现在能找到”,还要让失效方式可观察、可复现、可定位。
08 · TROUBLESHOOTING STACK页面状态排障栈

Console 能找到而自动化找不到时,优先向页面上下文排查,不要立刻重写选择器。

现象 01Console 能找到,影刀找不到

页面、弹窗或列表还没渲染完成。

→ 等待目标状态出现
现象 02语法正确,结果始终为 0

元素可能位于 iframe 或另一个页面对象。

→ 切换正确上下文
现象 03点击后元素才出现

下拉框、弹窗和异步区域尚未创建。

→ 先触发,再查找
现象 04看得见,但代码无法命中

可能是 Shadow DOM、虚拟列表或特殊控件。

→ 探测真实页面结构
现象 05find_by_css() 报多个

候选范围大于业务目标范围。

→ 用 find_all_by_css() 数量诊断
现象 06昨天能跑,今天失效

id、class、顺序或层级发生动态变化。

→ 改用稳定属性与业务范围
环境边界:has() 在浏览器 Console 可用,不等于在影刀运行环境稳定

必须在实际浏览器内核与项目环境中运行验证。

→ 需运行验证
排错顺序语法 → 匹配数量 → 加载状态 → 页面上下文 → 特殊 DOM → 运行环境。

坏定位应该怎样一步步改造成可维护定位?

改造方向不是“换一个更复杂的语法”,而是减少结构依赖,增加业务语义,并让结果可验证。

09 · REFACTOR CLINIC定位重构手术台

从原始页面特征出发,逐步替换不稳定依赖,而不是记固定答案。

CASE 01
登录输入框稳定程序字段优先于展示文案
不优先input[placeholder=“请输入账号”]
推荐input[name=“username”]
CASE 02
弹窗确认按钮同名按钮先限定业务容器
范围过大.confirm
推荐.dialog .confirm
CASE 03
订单行操作按钮先定位业务记录,再操作行内按钮
不稳定tr:nth-child(2) button
推荐tr[data-order-id=“20260713001”] .process-button
CASE 04
动态 class 评价区提取稳定片段,并补数量验证
随机后缀.comments—ChxC7GEN
候选div[class*=“comments—“]
CASE 05 · 结构路径重构
浏览器复制路径body > div:nth-child(3) > div > div:nth-child(2) > button:nth-child(2)

依赖弹窗顺序、容器层级和按钮位置,任一处变化都可能点错。

1识别业务范围 .order-dialog2寻找稳定动作 data-action="confirm"3组合为短而可读的选择器
重构结果.order-dialog button[data-action=“confirm”]

范围和动作都带业务语义,页面增加容器时仍有机会保持稳定。

改造方向少依赖页面层级,多依赖业务范围、稳定属性和可验证结果。

动态 class 的数量验证:

areas = browser.find_all_by_css('div[class*="comments--"]', timeout=10)

if len(areas) != 1:
    raise RuntimeError(f"评价区定位不唯一,当前数量={len(areas)}")

如果弹窗没有 data-action,但文字稳定,可以退回 CSS 候选加文字筛选:

buttons = browser.find_all_by_css(".order-dialog button", timeout=10)

for button in buttons:
    if button.get_text().strip() == "确认":
        button.click()
        break
else:
    raise RuntimeError("未找到弹窗里的确认按钮")

学完后,怎样确认自己已经能独立写稳定定位?

先完成两道练习,再用能力验收条逐项检查。答案不是唯一目标,能解释为什么稳定才算掌握。

10 · PRACTICE & CHECK练习台与能力验收

先看业务区域和稳定属性,再动手写选择器。

练习 A · 基础命中
<div class="login-panel">
<input name="account" placeholder="请输入账号">
<input name="password" type="password">
<button class="login-button" data-action="login">登录</button>
</div>

分别定位账号、密码和登录按钮。

查看参考答案
input[name="account"]
input[name="password"]
button[data-action="login"]
练习 B · 范围控制
<div class="order-dialog">
<div class="row" data-order-id="20260713001">
  <span class="status pending">待处理</span>
  <button class="btn btn-primary">处理</button>
</div>
<div class="row" data-order-id="20260713002">
  <span class="status done">已完成</span>
  <button class="btn btn-primary">查看</button>
</div>
</div>

定位订单 20260713001 这一行里的按钮。

查看参考答案
.order-dialog .row[data-order-id="20260713001"] button.btn-primary

先限定弹窗,再定位订单行,最后取行内按钮。

01能在开发者工具里找到目标元素的 HTML
02能区分精确、包含、开头和结尾匹配
03能用业务范围避免点错同名按钮
04能在浏览器和影刀里验证选择器是否唯一
05知道什么时候换 XPath 或 get_text()
最终判断中等水平的 CSS 定位,不是会写更多语法,而是能判断稳定性、控制范围,并验证结果。