XPath 与 CSS 选择器:上下文、支持与维护

XPath 与 CSS 选择器:你应该使用哪一个?

Scrapeless Agent Browser 提供一个浏览器执行环境,在其中抽取逻辑可以在 JavaScript 运行后检查页面 DOM。

摘要

  • CSS 在常见的元素和属性关系上更简洁。 使用稳定属性和较窄的作用域,而不是复制完整的可视路径。
  • XPath 能表达显式的轴、谓词和文本操作。 当抽取依赖树关系或 XML 上下文时,它可能更清晰。
  • CSS :has() 可以通过相关内容选择元素。 “CSS 永远不能选择类似父级的容器”这一旧说法过于宽泛。
  • 选择器的可移植性取决于引擎。 浏览器 API 和解析库可能支持不同版本和扩展。

你应该从哪种选择器语言开始?

对于简单的元素、类和属性查询,从 CSS 开始;当 XPath 的轴或谓词能更清晰地表达关系时再使用 XPath。选择 XPath vs CSS 选择器的最佳方式,是在规则将要运行的引擎中,采用能选中正确节点的最简单规则。

两种语言都无法修复缺失的源内容。如果页面在 JavaScript 执行后才构建商品列表,那么即使语法正确,应用到原始响应上的选择器也可能返回空结果。在比较查询风格之前,应先确认所需的树结构已经存在。

该 CSS Selectors 规范 定义了元素匹配,而 XPath 1.0 定义了用于导航文档树的表达式语言。它们在许多抽取任务上有重叠,但暴露的操作与结果模型不同。

需求CSS 方法XPath 方法
按标签选择元素简短的类型选择器元素节点测试
稳定属性属性选择器属性谓词
后代关系后代组合符后代路径
直接子代子代组合符子代路径
祖先关系关系匹配可以覆盖部分需求显式祖先轴
基于文本的条件没有通用标准的 contains-text 选择器字符串函数和谓词
属性或文本值在元素匹配后读取值表达式可以选择属性或文本节点

上下文是每个选择器的一部分

选择器的上下文决定了它要在其上求值的树或子树。同一表达式在从完整文档、产品卡片或嵌入的 frame 进行求值时,可以返回不同结果。应将这一假设与抽取规则一同存储。

XPath 在其求值模型中将上下文节点显式化。从文档根开始的路径与在选定项内求值的相对路径行为不同。在卡片循环中,相对后代查询可以让每个价格保持附着在正确的产品上,而文档范围的查询可能会意外选到其他卡片的字段。

CSS 同样需要清晰的作用域。应用于整页的简短选择器,可能同时匹配导航或促销组件以及目标记录。先选择记录容器,可以让字段选择器更易读,并减少意外匹配。

The DOM 查询接口模型 定义了基于选择器的元素查询如何在文档结构上运行。评估实际的查询上下文,而不是假设在浏览器控制台中可用的字符串在解析器回调内部也具有相同含义。

文本匹配和结构化导航

当某个字段是通过附近的文本或仅用元素属性难以表达的关系来标识时,XPath 往往非常有用。它可以在祖先和兄弟节点之间导航,并应用字符串谓词。这些特性有助于处理标签-值布局和 XML 文档。

文本条件仍然需要谨慎处理。标签可以包含嵌套的 span、不间断空格、本地化措辞或视觉上隐藏的文本。当可见短语由多个子节点组合而成时,针对单一文本节点的精确比较可能会失败。需要定义预期值是直接的文本节点、组合后的字符串值,还是渲染后的可视标签。

CSS 没有为浏览器元素查询定义通用的“包含文本”伪类。一些库提供了看起来熟悉的扩展语法,但这些表达式并不是可移植的标准 CSS。应将库扩展视为引擎约定的一部分,而不是原样传递到另一个环境中。

XPath 版本也有所不同。使用较新语言版本中函数的表达式,可能无法在浏览器的 XPath 接口中运行,或无法在仅实现较早版本的解析器中运行。在仅仅因为某个函数出现在通用参考资料中就选择它之前,请先检查所支持的版本。

一个小型选择器示例

在你控制的示例标记中,假设每条记录是一个 article 带有 data-record 属性并包含一个锚点,带有 data-role="title"。CSS 表达式 article[data-record] a[data-role="title"] 和 XPath 表达式 //article[@data-record]//a[@data-role='title'] 描述相应的后代锚点。 从匹配的元素中读取链接值,或在 XPath 中显式选择其属性。

对于关系条件, article[data-record]:has(a[data-role="title"]) 在支持 :has() 的引擎中选择外层的文章。XPath 谓词 //article[@data-record][.//a[@data-role='title']] 为这棵简单的树表达了相同的条件。它们是示例性表达式,而不是某个已命名的在线网站的选择器。

CSS :has() 的改变

CSS :has() 允许一个元素根据由相对选择器描述的相关元素进行匹配。它可以表达有用的容器选择条件,因此笼统地说 CSS 只能向下查找,对现代选择器引擎来说是有误导性的。

例如,可以选择一个卡片,因为它包含指定的价格元素。匹配结果就是被命名为该选择器主体的卡片。XPath 可以通过谓词和轴来表达相关需求,但其确切的返回类型和上下文仍然取决于具体表达式。

不要仅从标准本身推断具有普遍支持。浏览器、服务器端的 CSS 到 XPath 转换器以及较旧的嵌入式引擎可能实现了不同的特性。如果一个选择器必须在这些环境之间共享,请选择它们共同支持的子集,或者记录分别等效的规则。

关系匹配也不能替代语义字段设计。一个包含任意类似价格子元素的容器仍然可能是错误的商品卡片。验证标识符及其与提取值之间的关系,而不是仅仅因为选择器返回了一个节点就接受它。

可移植性包括文档模型

选择器的可移植性既取决于语法支持,也取决于提供给引擎的树结构。浏览器的 HTML 解析、XML 解析器以及宽容的服务器端 HTML 解析器,可能会从相似的输入生成不同的结构。对于 XML 而言,命名空间又增加了一种导致差异的来源。

浏览器检查器可以显示由脚本插入的节点或由解析器纠正而生成的节点。响应解析器只能看到其输入以及它自身的解析规则。当结果出现差异时,比较一个小而相关的子树;用 XPath 替换 CSS 并不能解决源树差异的问题。

框架和影子树会引入额外的查询边界。对顶层文档进行的普通查询不应被假定为会遍历每个嵌入或封装的文档。请为每棵树使用其支持的访问模型,并尊重其安全边界。

Scrapeless 代理浏览器 当内容需要脚本时,可以提供浏览器执行层。你的抽取逻辑仍然负责决定要检查哪个文档和状态。 XPath 和 CSS 的比较 提供了更多示例,用于根据标准和当前引擎支持情况进行评估。

可维护性比笼统的速度宣称更重要

选择一种能够表达稳定关系且维护人员容易理解的选择器。冗长的绝对路径和高度依赖位置的规则对布局变更非常敏感。应优先使用有意义的属性、明确的记录上下文,以及对提取身份的校验。

CSS 和 XPath 都不是普遍更快的选择。引擎实现、编译方式、文档大小以及查询结构都会影响执行。若查询性能对你很重要,应在本地对具有代表性的工作负载进行测量,并将网络与渲染时间与选择器求值时间区分开来。

  1. 确认所选树中存在所需的节点。
  2. 将查询限定在正确的记录或文档范围内。
  3. 使用稳定的属性或清晰的关系。
  4. 检查预期匹配计数和必填字段值。
  5. 测试缺失字段、重复标签和布局变体。
  6. 记录引擎和语言假设以便维护。

对于重复的卡片,不仅要验证各个值本身,还要验证记录之间的配对关系。标题选择器和价格选择器各自都可能返回看似合理的结果,但它们在列表中的位置可能已经不再对应。应在每张卡片内部进行抽取,并为记录保留一个稳定的标识符,以便能够观测到这种错误。

当需要在浏览器中执行时,请通过 当前的 Scrapeless 方案。不要基于某种查询语言可以避免渲染原始响应中不存在的内容这一预期来进行选择。

通过反例来审查选择器更改

选择器审查应包含一个目标缺失或重复的页面变体。如果某个商品卡片没有促销价,规则就不应悄悄地从下一张卡片中选取价格。若同一标签同时出现在导航和正文中,语境应使结果仍附着在预期的那一部分上。

为每种有意义的布局变体保留一个小型、已授权的示例子树,并比较预期的记录标识以及其值。当数据集依赖将字符串与特定项目配对时,仅仅返回一个看起来合理的字符串并不足以证明查询正确。反例比在一个简单页面上的成功匹配更清楚地暴露这种配对错误。

结论

使用 CSS 进行清晰的元素匹配,使用 XPath 处理它在关系或值表达上更直接描述的情况。在比较结果前,先验证上下文、文档状态以及引擎支持情况。更稳健的选择是:当页面或抽取环境发生变化时,其含义仍然清晰的那个选择器。

针对所需 DOM 验证选择器

当你需要的节点只在执行或交互之后才出现时,请使用 Scrapeless Agent Browser。

立即注册即可获得 $5 的免费额度 — 无需信用卡.

领取你的 $5 额度 →

常见问题

问:CSS 能否根据后代元素来选择一个容器?

在引擎支持的情况下,CSS 可以使用 :has() 根据相关元素来选择容器。这覆盖了有用的“父级式”匹配需求,尽管它并不等同于所有 XPath 轴或表达式。

问:XPath 一定比 CSS 慢吗?

XPath 并不总是比 CSS 慢。性能取决于引擎和查询本身。有些服务端工具会在内部将 CSS 转换为 XPath,因此当评估时间很重要时,应在实际环境中对等价操作进行比较。

问:为什么一个选择器在检查器中可用,却在解析器中失效?

选择器在解析器中可能失败,是因为输入树与浏览器 DOM 不同,或因为两种引擎支持的语法不同。在重写规则之前,应检查由 JavaScript 构建的内容、解析差异、查询上下文,以及库特有的扩展。

问:项目是否应该只使用一种选择器语言?

项目不需要只使用一种选择器语言。对于常见情况使用一致的风格,对于少数特殊字段则选择更清晰且受支持的表达方式。记录上下文与返回值预期,以便混合规则依然便于维护。

参考资料