返回博客
用例
Mihai MaximLast updated on May 1, 20264 min read

XPath 与 CSS 选择器:选择正确的选择器

XPath 与 CSS 选择器:选择正确的选择器
简而言之:XPath 和 CSS 选择器都能定位 DOM 元素,但它们解决的问题不同。对于简单的选择,CSS 选择器速度更快且更易于阅读。当需要沿任意方向遍历 DOM、匹配文本内容或处理复杂的条件逻辑时,XPath 则更具优势。大多数生产项目都能通过策略性地结合使用这两种方法而受益。

每一个网页抓取脚本、浏览器自动化工作流和端到端测试都有一个共同的基本要求:在 DOM 中查找元素。在每个项目初期,都会面临 XPath 与 CSS 选择器孰优孰劣的问题,而选择错误的方法可能会导致执行速度变慢、定位器不稳定以及维护困难。

XPath(XML路径语言)是一种专为在XML和HTML文档中导航及选择节点而设计的查询语言。CSS选择器原本是为HTML样式设计的一类模式字符串,但已被广泛应用于测试和爬取框架中的元素选择。虽然两者都能定位到相同的元素,但它们的实现路径(以及其中的权衡取舍)却存在显著差异。

本指南将详细解析两种方法的语法、性能特征、框架支持情况以及边界情况下的行为表现,助您为项目做出自信且明智的选择。

XPath 与 CSS 选择器一览

XPath 和 CSS 选择器都能识别 HTML 或 XML 文档中的元素,但它们源自不同的领域。XPath 专为 XML 文档导航而设计,支持双向遍历,这意味着您可以像从父元素到子元素那样轻松地从子元素移动到父元素。CSS 选择器起源于样式表,且仅支持单向遍历:从父元素到子元素(或同级元素)。

简而言之:如果您的选择需求较为简单(ID、类、属性、组合符),CSS 选择器是更快且更易读的选择。当您需要向上遍历、匹配文本内容或应用复杂的条件过滤器时,XPath 是唯一能满足需求的选项。

维度

CSS 选择器

XPath

方向

仅限父子关系

双向(任意轴)

速度

通常更快(原生引擎)

在浏览器中较慢

文本匹配

不支持

text(), contains()

可读性

简洁、熟悉

冗长,学习曲线陡峭

文档类型

仅限 HTML

HTML 和 XML

XPath 的工作原理

XPath(全称 XML Path Language)是一种用于导航和查询 XML 文档(包括 HTML)的表达式语言。它将文档视为一个节点树,并允许您编写路径表达式来选择其中一个或多个节点。

绝对路径与相对路径。绝对 XPath 从文档根节点开始,并详细列出每个步骤: /html/body/div[1]/ul/li[3]。这种路径非常脆弱,因为任何结构变更都会导致其失效。相对 XPath 以 // ,并能匹配树中任意位置的节点: //li[@class='active']。在实际应用中,相对路径几乎总是更合适的选择。

轴方法是 XPath 真正大显身手的地方。诸如 parent::, ancestor::, following-sibling::preceding-sibling:: 等方法,可让你从上下文节点向任意方向移动。例如, //span[@id='price']/parent::div 可选定 div ,这是 CSS 选择器根本无法做到的。 span,这是 CSS 选择器根本无法做到的。

诸如 contains(), starts-with()text() 等关键函数可添加条件筛选。您可以定位一个元素,其可见文本包含某个子字符串(//a[contains(text(), 'Next Page')])的元素,而完全无需依赖属性。

一个重要注意事项:大多数浏览器环境目前仍仅支持 W3C 于 1999 年发布的 XPath 1.0。XPath 2.0 和 3.0 引入了正则表达式和更丰富的类型系统等强大功能,但在基于浏览器的自动化场景中,您很少会遇到它们。像 lxml(Python)这样的库确实提供了 XPath 2.0 支持,因此您能获得的版本取决于您的工具链。

CSS 选择器的工作原理

CSS 选择器是一种模式字符串,它根据 HTML 元素的标签名、ID、类、属性、位置或状态来定位目标元素。CSS 选择器最初是为在样式表中应用样式而设计的,如今已成为大多数现代自动化和爬取框架中默认的元素选择方法。

其基本原理对任何前端开发者来说都很熟悉。 #main 通过 ID 定位元素。 .card 匹配具有特定类的元素。 div > p 选择直接 p 的直接 div。属性选择器如 input[type="email"] 以及位置伪类 :nth-child(2) 等,可进一步缩小选择范围。

现代伪类正在缩小与XPath的差距。 :has() 选择器现已得到广泛支持,它允许你根据父元素的子元素来选择父元素: div:has(> img.hero) 可选中任何 div 直接包含 img 且具有类 hero:is():where() 伪类简化了分组操作,而 :not() 用于处理排除。这些新增功能意味着 CSS 选择器现在能够处理一些此前需要使用 XPath 的场景。

话虽如此,CSS 选择器无法直接选择文本节点,且仍仅限于正向(父子)遍历。它们也仅适用于 HTML 文档;若需查询原始 XML 或非 HTML 源,XPath 是唯一的选择。

语法并列对比

将 XPath 与 CSS 选择器并列对比,是快速掌握其差异的最佳方式。下表针对同一组假设页面元素,将常见的选择目标映射到这两种语法中。

选择目标

CSS 选择器

XPath

按 ID

#username

//*[@id='username']

按类

.card

//*[contains(@class,'card')]

按属性

a[href^="https"]

//a[starts-with(@href,'https')]

直接子元素

ul > li

//ul/li

第 n 个子元素

li:nth-child(3)

//li[3]

按文本内容

不可用

//a[text()='Login']

元素的父元素

div:has(> span.icon) (CSS4)

//span[@class='icon']/parent::div

后面的同级元素

h2 ~ p

//h2/following-sibling::p

祖先

不可行

//span/ancestor::form

有几点特别显眼。对于ID、类和属性选择,CSS明显更简洁、更易读。但一旦需要文本匹配或祖先遍历,XPath便是唯一的选择。CSS :has() 伪类在父级选择方面缩小了这一差距,但无法取代 XPath 完整的轴系统。

从可读性角度来看,对于任何写过样式表的人来说,CSS 选择器都显得很自然。XPath 的基于路径的语法虽然更冗长,但这种冗长性换来了复杂查询的精确性。如果你的团队中有熟悉 CSS 的前端开发人员,他们掌握 CSS 选择器的速度会比掌握 XPath 表达式快得多。

性能与速度

普遍认为在浏览器环境中 CSS 选择器比 XPath 更快,实践中这一观点通常成立。浏览器内置了高度优化的原生 CSS 选择器引擎,因为 CSS 匹配是渲染管道的核心部分。相比之下,XPath 评估位于该快速路径之外,通常会带来更多开销。

话虽如此,目前尚无被广泛引用的标准化公开基准测试来量化 XPath 与 CSS 选择器性能的精确差异。这种差距确实存在,但除非每页需要执行数万次选择器评估,否则通常可以忽略不计。对于大多数抓取和测试工作流而言,选择器速度极少成为瓶颈;网络延迟和页面渲染才是执行时间的主要影响因素。

在浏览器之外,情况则有所不同。像 lxml 这样的库会将 XPath 表达式编译为优化的 C 代码,这使得 XPath 求值在 Python 的服务器端抓取中变得极其快速。例如,Scrapy 用户可能会发现 XPath 和 CSS 选择器之间几乎没有速度差异,因为两者在底层都是通过 lxml 进行求值的。

高级过滤、遍历与可读性

XPath 的双向遍历是其最大的技术优势。使用诸如 parent::, ancestor::, following-sibling::preceding-sibling::等轴,您可以从任意起始节点沿任意方向遍历 DOM 树。当需要选择的元素本身不具备唯一属性,但与其具有该属性的兄弟节点或祖先节点之间存在可预测的关系时,这一特性便不可或缺。

CSS 选择器仅支持单向遍历。您可以从父节点遍历到子节点(>)或从前置同级元素跳转到后置同级元素(~, +),但无法向上遍历。 :has() 伪类在一定程度上弥补了这一缺憾:它允许你根据后代元素来条件性地选择父元素。不过, :has() 并不能提供完整的祖先遍历功能,且尽管其浏览器支持率正在提升,但在较旧的环境中仍未实现全面兼容。

文本节点选择是 XPath 另一项明显的优势。诸如 //td[contains(text(), 'Total')] 这类表达式允许你根据元素的可见内容定位它们,这对抓取那些元素未携带有效类名或ID的页面而言极为宝贵。CSS 没有等效功能。

在为团队评估 XPath 与 CSS 选择器时,学习曲线值得特别关注。CSS 选择器得益于广泛普及;大多数开发者在接触自动化之前,就已经在样式表中编写过它们。而 XPath 表达式,尤其是那些使用多轴或嵌套谓词的表达式,会带来更高的认知负荷。这种复杂性在需要时是值得的,但对于简单的选择而言,则是不必要的开销。

框架与库的兼容性

并非所有框架都对 XPath 和 CSS 选择器一视同仁。在确定选择器策略之前,请先确认您的工具链实际支持哪些功能。

框架 / 库

CSS 选择器

XPath

Selenium(所有语言)

Playwright

木偶师

是(通过 $x())

Scrapy (Python)

是(通过 parsel)

是(通过 parsel/lxml)

lxml (Python)

是(通过 cssselect)

是(原生)

BeautifulSoup(Python)

否(使用 lxml 后端)

Cheerio (Node.js)

有几点细节值得注意。Puppeteer 通过一个独立的 $x() 方法,而非主 $() 选择器 API,因此集成体验稍显不那么顺畅。BeautifulSoup 未内置 XPath 引擎;若需在 BeautifulSoup 中使用 XPath,则需将其与 lxml 解析器后端配合使用。Cheerio 按设计仅支持 CSS。

对于正在权衡 XPath 与 CSS 选择器的 Selenium 用户而言,这两种类型均可通过 By.CSS_SELECTORBy.XPATH,均被视为一等公民。Playwright同样支持这两种方式,若您希望在单个测试套件或数据解析管道中灵活混合使用选择器策略,它将是一个不错的选择。

边界情况:Shadow DOM、Iframes 和动态内容

现实中的网页很少像教程示例那样干净,当涉及 Shadow DOM、iframe 和动态注入的内容时,XPath 与 CSS 选择器的抉择就变得更加复杂。

Shadow DOM。默认情况下,CSS 选择器无法穿透封闭的 Shadow DOM 根节点。Playwright 提供了一个 css=pierce/ 前缀作为变通方案,但浏览器中的标准 CSS 引擎会在 Shadow DOM 边界处停止解析。XPath 在此也无能为力;它完全不具备 Shadow DOM 的原生概念。在这两种情况下,您通常需要使用框架特有的 API(如 Playwright 的 locator() 的穿透功能)才能访问 Shadow 元素。

iframe。无论是 XPath 还是 CSS 选择器,都无法自行跨越 iframe 边界。你必须先将驱动程序或上下文切换到 iframe 的文档(driver.switchTo().frame() 在 Selenium 中, frame.contentFrame() 在 Playwright 中),然后在该作用域内运行选择器。

动态内容。在导航时重写 DOM 的单页应用程序带来了新的挑战。针对 data-testidaria-label 等稳定属性的 CSS 选择器,通常比可能随构建版本变化的基于类的选择器更具鲁棒性。与文本内容相关的 XPath 表达式同样可靠,前提是可见文本保持一致。

编写健壮的选择器

无论您在 XPath 与 CSS 选择器之间如何抉择,编写能够经受住 DOM 变更的选择器都比您选择的语言更为重要。脆弱的定位器是导致测试不稳定和抓取工具失效的主要原因。

两种类型的选择器均应遵循以下最佳实践:

  • 优先使用稳定的属性。使用 data-testid, aria-label、或其他语义属性,而非自动生成的类名或位置索引。
  • 保持选择器简短。[data-testid="submit-btn"]div.form-wrapper > div:nth-child(3) > button.btn-primary。XPath 也是如此: //button[@data-testid='submit-btn'] 比五级绝对路径更胜一筹。
  • 避免使用绝对 XPath。/html/body/... 的选定器,一旦父元素发生变化就会失效。请始终使用以 //.

应避免的常见反模式:

  • 在 CSS 中将后代组合符链式连接超过三层
  • 使用 XPath position() 或基于索引的选择(div[4]
  • 无论使用哪种选择器类型,都依赖动态生成的类名(常见于 CSS-in-JS 框架)

在项目初期就制定选择器策略,选择稳定的锚点并记录规范,随着项目规模的扩大,这将节省大量调试时间。

何时使用 XPath、CSS 选择器或两者兼用

在 XPath 与 CSS 选择器的争论中,没有放之四海皆准的优胜者。正确的选择取决于您正在构建的内容。

当你的选择涉及 ID、类、属性或定位伪类时,请优先使用 CSS 选择器。它们在浏览器中运行更快、更易于阅读,且得到广泛支持。对于简单的网页抓取任务和大多数前端测试自动化,CSS 选择器能以更少的代码满足 80% 以上的定位需求。

当需要向上遍历(父级或祖先选择)、匹配文本内容,或应用串联多个谓词的复杂条件过滤器时,请选用 XPath。在处理非 HTML 的 XML 文档,或目标元素缺乏有用属性时,XPath 也是更优的选择。

若项目需求合理,请同时使用这两种方法。在 Selenium 或 Playwright 中,混合使用这两种方法 By.CSS_SELECTORBy.XPATH 调用。混合方法可让您使用 CSS 进行简单、快速的选择,同时针对少数需要 XPath 的特殊情况使用 XPath。

快速参考决策清单

使用此清单将您的项目限制与正确的选择器类型进行匹配:

  • 速度是首要考量且在浏览器中运行:使用 CSS 选择器。
  • 需要父元素或祖先元素遍历:使用 XPath。
  • 需要根据可见文本内容进行匹配:使用 XPath。
  • 您的框架仅支持一种类型(例如,Cheerio 仅支持 CSS):请使用现有功能。
  • 您正在抓取 XML 源或非 HTML 数据:请使用 XPath。
  • 你的团队主要由前端开发者组成:默认使用 CSS 选择器,以便更快上手。
  • DOM 频繁变化,且您需要具有弹性的定位器:使用针对 data-testidaria-label 属性(两者都能很好地处理这种情况)。

关键要点

  • 在浏览器环境中,CSS 选择器通常速度更快,且对于标准选择(ID、类、属性、定位)而言更易于阅读。
  • 当需要双向 DOM 遍历、文本内容匹配或祖先选择时,XPath 是唯一的选择。
  • 现代 CSS 伪类(如 :has() 正在缩小功能差距,但无法完全替代 XPath 的轴系统。
  • 框架支持情况各异:Cheerio 和 BeautifulSoup(不带 lxml)仅支持 CSS,而 Selenium 和 Playwright 则对这两种选择器类型一视同仁。
  • 在比较 XPath 与 CSS 选择器时,最关键的决策是编写针对稳定属性的弹性选择器,而不是针对脆弱的位置或生成类定位器。

常见问题

我可以在同一个 Selenium 测试中同时使用 XPath 和 CSS 选择器吗?

可以。Selenium 通过 By.CSS_SELECTORBy.XPATH,您可以在单个测试文件甚至单个测试方法中自由混合使用它们。在两者之间切换不会造成性能损失,因此请根据每个元素查找的具体情况选择最合适的类型。

像 :has() 这样的现代 CSS 选择器是否会取代 XPath 用于父元素选择?

部分可以。 :has() 伪类允许您根据子元素来选择父元素,这覆盖了最常见的父元素选择场景。然而,它并不支持跨多级完全的祖先遍历、前置兄弟元素逻辑,也不支持 XPath 轴所支持的条件链。可以认为 :has() 大致能覆盖此前需要使用 XPath 进行向上导航的 60% 的场景。

对于动态单页应用程序,哪种选择器类型更可靠?

两者本身并无优劣之分。可靠性取决于选择器的锚点,而非选择器语言本身。针对 data-testidaria-label 属性的选择器,无论采用 CSS 还是 XPath 编写,在框架重新渲染时都能保持稳定。应避免使用依赖自动生成的类名或深层位置索引的选择器。

Puppeteer 和 Playwright 是否支持 XPath?

是的,两者都支持 XPath。Puppeteer 通过 $x() 方法(或 page.evaluate 配合 document.evaluate)。Playwright 在其 locator()$() API中原生支持XPath。在这两种工具中,CSS选择器是默认且更常用的,但当您需要其遍历功能时,XPath同样可用。

结论

关于 XPath 与 CSS 选择器的优劣之争,其实没有唯一的答案,因为它们是互补的工具,解决的是既有重叠又各不相同的问题。出于速度、可读性和简洁性的考虑,CSS 选择器应作为您的首选。当您在仅支持前向遍历时遇到瓶颈、需要基于文本的匹配,或处理非 HTML 文档格式时,XPath 才是您的不二之选。

选择器语言的重要性远不如选择器质量。请将定位器锚定在稳定的语义属性上,并保持其简短。记录您的规范,以免下一位开发者(或未来的自己)不得不逆向推导某个特定 XPath 表达式存在的理由。

关于作者
Mihai Maxim, 全栈开发工程师 @ WebScrapingAPI
Mihai Maxim全栈开发工程师

米海·马克西姆(Mihai Maxim)是 WebScrapingAPI 的全栈开发工程师,他在产品各领域均有贡献,并协助为该平台构建可靠的工具和功能。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。