返回博客
指南
Suciu DanLast updated on Apr 29, 20263 min read

XPath Web Scraping:附 Python 示例的实践指南

XPath Web Scraping:附 Python 示例的实践指南
简而言之:XPath 是一种用于通过路径、属性或文本内容遍历 HTML/XML 树的查询语言。本指南将介绍 XPath 的语法、轴和函数,并展示使用 lxml 和 Selenium 实现的 Python 爬虫示例。您还将获得一份综合速查表,以及针对最常见 XPath 错误的故障排除部分。

XPath(XML路径语言)是一种查询语言,它使用路径表达式从XML和HTML文档中选择节点。如果觉得CSS选择器对您的抓取任务来说过于局限,那么XPath网页抓取便是顺理成章的下一步。

CSS 选择器只能沿 DOM 结构向前或向下移动,而 XPath 则可向任意方向遍历:向上访问父节点、横向访问兄弟节点,或深入嵌套的子孙节点。它还能根据元素的可见文本进行匹配,这是 CSS 完全不具备的能力。这些特性使得 XPath 在处理结构复杂或混乱的网页时尤为有用。

在本教程中,您将学习核心的 XPath 语法(路径、谓词、轴、函数),了解如何在浏览器中测试表达式,并使用 lxml 和 Selenium 构建真正的 Python 爬虫。我们还将探讨生产环境中导致 XPath 选择器失效的常见陷阱,以及如何避免这些问题。

什么是 XPath 以及为何将其用于网页抓取?

XPath 将 HTML 或 XML 文档视为节点树,并提供了一种简洁的语法,用于精确定位您所需的节点。您可以将其视为文件路径:正如 /home/user/docs/file.txt 浏览目录一样, /html/body/div/p XPath 便会遍历 DOM 树以定位到特定的段落。

XPath 在爬取领域脱颖而出的三大原因:

  1. 双向遍历。XPath不仅能向下遍历,还能向上遍历至父元素或祖先元素。而 CSS 选择器只能向前遍历。
  2. 基于文本的选择。您可以通过可见文本(例如 <a> 标签显示“下一页”),这是 CSS 无法做到的。
  3. 内置函数。 contains(), starts-with()normalize-space() 等函数,让您无需正则表达式即可处理杂乱的标记。

编写扎实的 XPath 表达式是一项实用的技能,在处理非简单页面时能迅速见效。XPath 支持多种语言(Python、JavaScript、C#、PHP),因此无论您的技术栈如何,语法知识都能直接迁移。

DOM 与 XPath 表达式的关联

文档对象模型(DOM)将每个 HTML 元素、属性和文本节点组织成一棵树,其中每个节点都有父节点、子节点和兄弟节点。请看以下标记:

<ul id="menu">
  <li class="active">Home</li>
  <li>About</li>
</ul>

此处 <ul> 是父节点。 <li> 元素是其子元素,彼此之间也是兄弟节点。XPath //ul[@id='menu']/li[1] 表示:查找任何 <ul>id="menu",然后获取其第一个 <li> 子节点。每个XPath查询本质上都是一套穿行于这种父-子-兄弟结构中的路径,这也是为何在编写任何选择器之前,理解DOM至关重要。

XPath 语法基础

XPath路径有两种类型。绝对路径从根节点(/html/body/div/p) 开始,虽然精确但脆弱:结构发生一点变化就会导致它们失效。相对路径// ,并在整个树中进行搜索,这使得它们在 XPath 网络爬虫工作流中具有更强的容错性。

运算符

含义

示例

/

直接子节点

/html/body/div

//

任何后代

//div[@class='price']

..

父节点

//span/..

@

属性访问

//a[@href]

使用谓词选择节点

谓词是括号包围的过滤器,用于缩小节点集范围:

//ul/li[1]                  ← first <li>
//ul/li[last()]              ← last <li>
//input[@type='email']       ← by attribute
//a[text()='Next Page']      ← by exact text
//span[contains(text(),'$')] ← by partial text

正是谓词使 XPath 表达式在数据提取方面真正强大。如果没有它们,您就需要编写后处理代码来从结果中过滤掉不需要的匹配项。

XPath 轴:全方位导航

轴定义了相对于当前节点的遍历方向,这也是 XPath 网络爬虫选择器能够访问 CSS 无法触及的元素的核心原因。

方向

抓取用例

child::

直接子节点

选择 <li> 选定 <ul>

descendant::

所有嵌套节点

抓取容器内的所有链接

parent::

上移一级

从价格 <span>,到达其对应的产品 <div>

ancestor::

任意祖先节点

从一个单元格出发,查找 <table> 它所属的

following-sibling::

下一个兄弟节点

经过一个 <dt> 标签后,获取 <dd>

preceding-sibling::

前一个同级元素

从已知元素开始,向后查找标题

抓取定义列表的实际示例:

undefined//dt[text()='Price']/following-sibling::dd[1]

这将获取第一个 <dd> 之后 <dt> 后第一个包含“Price”的 following-sibling 轴使此操作变得轻而易举,而使用CSS则需要额外的JavaScript或包装逻辑。

用于抓取的强大 XPath 函数

现实中的 HTML 往往杂乱无章。XPath 函数能帮助您编写选择器,使其能够应对动态类名、隐藏的空格以及不断变化的属性值。

contains(@class, 'product')       ← matches "product-card", "product_item"
starts-with(@id, 'listing-')      ← matches "listing-001", "listing-002"
text()                             ← targets visible text content
normalize-space()='In Stock'       ← collapses whitespace before comparing
not(contains(@class, 'ad'))        ← excludes ad containers

contains() 在框架会在类名后附加生成的后缀的页面上,该函数特别有用。 normalize-space() 解决了这样一个令人沮丧的问题:XPath 选择器看似正确,却因文本周围存在隐藏的制表符或换行符而失败。这些函数共同作用,使您的 XPath 表达式在标记一致性无法得到保证的真实网站上具有很强的适应性。

XPath 快速参考指南

将此综合参考资料添加到书签,以便在编写 XPath 网页抓取选择器时快速查阅。

类别

语法

功能说明

路径

//tag

任何位置的所有匹配标签

路径

/parent/child

直接子节点

路径

..

父节点

谓词

[n]

第 n 个元素(从 1 开始计数)

谓词

[@attr='val']

按属性过滤

ancestor::tag

任何祖先

following-sibling::tag

下一个同级节点

函数

contains()

部分字符串匹配

函数

normalize-space()

去除空格

函数

text()

选择文本内容

若需更深入了解运算符和复合表达式,我们的 XPath 速查表提供了扩展参考。

在浏览器开发者工具中测试 XPath

在编写代码之前,请先在浏览器中验证您的 XPath 表达式。打开开发者工具(F12),转到“元素”面板,然后按 Ctrl+F(Mac 上为 Cmd+F)。搜索栏支持 XPath 语法,并会实时高亮显示匹配的节点。

您也可以右键单击任何元素,选择“复制”>“复制 XPath”。请注意:浏览器生成的绝对路径(如 /html/body/div[3]/section[1]/ul/li[2]/a ,一旦结构发生变化就会失效。请务必将自动生成的路径重写为基于稳定属性(ID、 data-* 语义类名)作为锚点的相对表达式,而非基于位置的索引。

使用 Python 进行 XPath 网页抓取:分步指南

让我们通过 Python 实践 XPath。Python 中用于 XPath 网页抓取的两个最常用库是 lxml(静态 HTML)和 Selenium(JavaScript 渲染的页面)。下面我们将使用这两个库针对同一个网站进行操作,以便您直接比较 API 的易用性。

使用 lxml 进行 XPath 操作

lxml 是一个基于 libxml2 构建的、由 C 语言支持的高速库。如果页面不需要 JavaScript,lxml 是最佳选择。

import requests
from lxml import html

response = requests.get("https://books.toscrape.com/")
tree = html.fromstring(response.content)
titles = tree.xpath('//article[@class="product_pod"]/h3/a/@title')
for title in titles:
    print(title)

使用 requests,使用 html.fromstring(),使用 .xpath()。结果将是一个普通的 Python 列表。若需更全面的 Python 爬取方案,我们的《Python 网络爬取指南》深入讲解了请求处理与数据存储。

在 Selenium 中使用 XPath

当页面通过 JavaScript 呈现内容时,您需要一个浏览器引擎。Selenium 驱动无头浏览器,并通过 find_elements提供。据称从 Selenium 4 开始,WebDriver 已随包捆绑(在依赖此功能前请核对最新的 Selenium 发布说明)。

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://books.toscrape.com/")
elements = driver.find_elements(By.XPATH, '//article[contains(@class,"product_pod")]/h3/a')
for el in elements:
    print(el.get_attribute("title"))
driver.quit()

该 XPath 与 lxml 版本几乎完全一致。区别在于 API: .xpath()find_elements(By.XPATH, ...)。关于无头浏览器工作流的更多内容,请参阅我们的 Selenium 和 Python 爬取教程,其中详细介绍了自动化模式。建议对静态页面使用 lxml,仅在确实需要执行 JavaScript 时才使用无头浏览器。

XPath 与 CSS 选择器:何时使用哪种

标准

XPath

CSS 选择器

方向

向上、向下、横向

仅向前

文本匹配

原生 (text(), contains())

不支持

易读性

冗长但明确

简洁

性能

实际表现大致相当

在基准测试中略快

学习曲线

更陡峭

更简单

当目标仅通过类名或ID即可定位时,请使用CSS。对于需要父节点遍历、文本匹配或多轴导航的抓取任务,请使用XPath。许多经验丰富的抓取开发者会灵活混合使用这两种方法。如需更深入的对比,我们的“XPath与CSS选择器对比”页面涵盖了更多边界情况和选择策略。

就性能而言,XPath 有时被认为较慢,但在网络延迟主导抓取工作负载的情况下,这种差距通常可以忽略不计。在实际项目中,选择器的速度极少成为瓶颈。

常见的 XPath 错误及解决方法

不稳定的自动生成路径。从浏览器复制的 XPath 通常使用绝对索引。请将其重写为基于稳定属性的锚点,例如 @id@data-testid.

命名空间冲突。若 XPath 在 XHTML 页面上返回空结果,很可能是命名空间前缀导致了干扰。使用 local-name() 来绕过它们: //*[local-name()='div'].

空格不匹配。 text()='In Stock' 当源代码包含隐藏的换行符或制表符时,该检查会失败。请将检查包裹在 normalize-space() 中,以先压缩空格。

动态类名。React 等框架会生成哈希类名(例如 card__a3xK2)。使用 contains(@class, 'card') 仅匹配稳定的前缀。

在扩展到数千个页面之前,先用小样本测试您的 XPath 选择器。在单个页面上调试失效的选择器很简单;而在批量运行后调试则会浪费时间和带宽。

关键要点

  • XPath 可在 HTML 中任意方向导航,这使其在处理复杂的抓取任务时比 CSS 选择器更具灵活性。
  • 始终优先使用相对路径(//) 代替绝对路径,以确保选择器在 DOM 变更时仍能正常工作。
  • 使用 contains(), normalize-space()text() 等函数来处理现实世界中杂乱的标记。
  • 对于静态 HTML,lxml 是 Python 中速度最快的选项。将 Selenium 保留给需要 JavaScript 渲染的页面。
  • 请先在浏览器开发者工具中测试每个 XPath 表达式,并重写自动生成的路径以使用稳定的锚点。

常见问题

在网页抓取中,XPath 比 CSS 选择器更好吗?

两者均无绝对优劣之分。当需要父节点遍历、基于文本的匹配或复杂筛选时,XPath 表现更佳。对于简单的类或 ID 选择,CSS 选择器编写速度更快,且在合成基准测试中通常表现略优。大多数生产环境中的爬虫都会同时使用这两种方法,并根据具体选择任务选择合适的方案。

XPath 能否抓取 JavaScript 渲染的页面?

XPath 是一种查询语言,而非渲染引擎。它操作的是接收到的任何 DOM。要抓取 JavaScript 渲染的内容,您首先需要一个能够执行页面 JavaScript 的工具(如无头浏览器),然后对生成的 DOM 应用 XPath 表达式。静态解析器只能看到任何客户端渲染之前的原始服务器响应。

哪种 Python 库最适合 XPath 网页抓取?

对于静态页面,lxml 是标准选择:速度快、文档完善,且由经过验证的 C 语言库提供支持。对于动态页面,Selenium 通过其 find_elements API 提供 XPath 支持。Scrapy 也通过其 Selector 类提供原生 XPath 支持,使其成为完整爬取管道的可靠选择。

为什么从 Chrome 开发者工具复制的 XPath 在代码中有时会失效?

Chrome 会根据实时 DOM 生成包含位置索引的绝对路径,其中可能包含由 JavaScript 或浏览器扩展注入的元素。当解析器处理原始 HTML(在 JS 执行之前)时,结构会有所不同,因此位置索引会指向错误的元素。请改用稳定的属性代替位置来重写表达式。

结论

XPath 网页抓取能提供更精确的定位,这是简单选择器方法无法比拟的。掌握本文介绍的语法、轴、函数和 Python 模式后,您将能够从基本的属性选择,到处理结构复杂、嵌套深且不规则的页面,游刃有余。

提升的关键在于实践:从简单目标入手,在开发者工具中测试表达式,并逐步增加复杂度。保持选择器的相对性,借助函数应对标记变化,并在扩展前务必先对小批量数据进行验证。

当反机器人防护、验证码或代理管理所耗费的时间超过实际的 XPath 逻辑时,WebScrapingAPI 将为您处理请求层,通过单一接口管理代理轮换和反封锁措施,让您能够专注于编写简洁的选择器并进行数据处理。

关于作者
Suciu Dan, 联合创始人 @ WebScrapingAPI
Suciu Dan联合创始人

Suciu Dan 是 WebScrapingAPI 的联合创始人,他撰写了关于 Python 网页抓取、Ruby 网页抓取以及代理基础设施的实用指南,这些指南专为开发者而设计。

开始构建

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

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