in

如何使用XPath解析HTML元素

如何使用XPath解析HTML元素

在解析网络抓取的 HTML 内容时,有多种技术可以选择我们想要的数据。 对于简单的文本解析,可以使用正则表达式,但HTML被设计成机器可读的文本结构。我们可以利用这一事实并使用特殊路径语言(如 CSS 选择器和 XPath)以更高效和可靠的方式提取数据! 您可能熟悉样式表文档 ( .css) 中的 CSS 选择器,但是,XPath 不止于此,它以其自己独特的语法实现了完整的文档导航。

使用 CSS 选择器解析 HTML

在本文中,我们将深入研究这种独特的路径语言,以及如何使用它从现代复杂的 HTML 文档中提取所需的详细信息。我们将从快速介绍和表达式备忘单开始,并使用交互式 XPath 测试器探索概念。 最后,我们将介绍各种编程语言中的 XPath 实现,以及涉及 Web 抓取中的 XPath 的一些常见习语和技巧。让我们开始吧!

什么是 Xpath?

XPath 代表“XML 路径语言”,本质上意味着它是一种查询语言,用于描述 XML/HTML 类型文档从 A 点到 B 点的路径。 您可能知道的其他路径语言是 CSS 选择器,它通常描述应用样式的路径,或者特定于工具的语言,如jq,它描述 JSON 类型文档的路径。 对于 HTML 解析,Xpath 比 CSS 选择器有一些优势:

  • Xpath 可以在各个方向遍历 HTML 树并且是位置感知的。
  • Xpath 可以在返回结果之前对其进行转换。
  • Xpath 很容易扩展附加功能。

在我们深入研究 Xpath 之前,让我们快速概述一下 HTML 本身,以及它如何使 xpath 语言能够使用正确的指令找到任何东西。

HTML 概述

HTML(超文本标记语言)旨在易于机器阅读和解析。换句话说,HTML 遵循节点及其属性的树状结构,我们可以轻松地以编程方式对其进行导航。 让我们从一个小示例页面开始并说明其结构:

<head>
  <title>
  </title>
</head>
<body>
  <h1>Introduction</h1>
  <div>
    <p>some description text: /p>
    <a class="link" href="https://example.com">example link</a>
  </div>
</body>

在这个简单网页的基本示例中,我们可以看到文档已经类似于数据树。让我们更进一步说明这一点: 在这里,我们可以更轻松地理解它:它是一棵节点树,每个节点还可以附加属性,如关键字属性(如和)和class自然href属性(如文本)。 现在我们已经熟悉了 HTML,让我们来熟悉一下 Xpath 本身吧!

Xpath 语法概述

本文中我们仅以我们网站jingzhengli.com为例,实际运用中你可替换为你需要的任意网站

Xpath 选择器通常称为“xpath”,单个 xpath 表示从根到所需端点的目的地。这是一种相当独特的路径语言,所以让我们从快速浏览基本语法开始。 网络抓取中的平均 xpath 选择器通常看起来像这样:

xpath协议说明
常用 xpath 选择器结构的图示

在此示例中,XPath 将选择具有类“按钮”的节点href的属性,该类也在节点的正下方:<a><div>

<div>
  <a class="button" href="https://www.jingzhengli.com/">jingzhengli</a>
</div>
//div/a[contains(@class, "button")]/@href
https://www.jingzhengli.com/
CSS
ERROR: Syntax error, unrecognized expression: //div/a[contains(@class, "button")]/@href
表达 描述
/node 选择与节点名称匹配的直接子节点。
//node 选择任何后代——孩子、孙子、曾孙等。
* 可以使用通配符代替节点名称
@ 选择节点的属性,例如a/@href将选择节点href的属性值a
text() 选择节点的文本值
[] 选择器约束 – 可用于过滤掉不符合某些条件的节点
parent or .. 选择当前节点的父节点,例如/div/a/..将选择div元素
self or . 选择当前节点(这在 xpath 函数中作为参数很有用,我们稍后会介绍更多)
following-sibling::node 选择所有以下类型的兄弟姐妹,例如following-sibling::div将选择div当前节点下的所有兄弟姐妹
preceding-sibling::node 选择类型的所有前面的兄弟姐妹,例如preceding-sibling::div将选择div当前节点下面的所有兄弟姐妹
function() 调用已注册的 xpath 函数,例如/a/text()将返回a节点的文本值

这份 XPath 备忘单可能需要大量的知识——所以,让我们通过一些现实生活中的例子来巩固这些知识。

基本导航

在编写 xpaths 时,我们应该注意的第一件事是所有 xpaths 都必须有一个根(又名点 A)和最终目标(又名点 B)。知道这个和 xpath 轴语法,我们可以开始描述我们的选择器路径:

<div>
  <p class="socials">
    Follow us on
    <a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
  </p>
</div>
div/p/a

xpath:

<a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>

CSS: div/p/a

ERROR: Syntax error, unrecognized expression: div/p/a

在这里,我们的简单 xpath 简单地描述了从根到节点的路径a。 我们只使用/直接子语法,但是对于大文档,直接 xpath 通常不可靠,因为对树结构或顺序的任何更改都会破坏我们的路径。 最好围绕一些智能上下文设计我们的 xpath。例如,在这里我们可以看到这个<a>节点在<p class="socials">node 下——我们可以强烈推断这两个节点很可能会放在一起:

<div>
  <p class="socials">
    Follow us on
    <a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
  </p>
</div>
//p[@class='socials']/a

xpath

<a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>

CSS

ERROR: Syntax error, unrecognized expression: //p[@class='socials']/a

有了这个 xpath,我们摆脱了很多结构依赖,有利于上下文。通常,现代网站的上下文比结构稳定得多,在上下文和结构之间找到正确的平衡是创建可靠 xpath 的关键! 此外,我们可以将约束选项 ( []) 与值测试函数结合起来,例如contains()使我们的 xpath 更加可靠:

<div>
  <p class="socials">
    Follow us on
    <a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
  </p>
</div>
//p[@class='socials']/a[contains(@href, 'twitter.com')]
<a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
CSS
ERROR: Syntax error, unrecognized expression: //p[@class='socials']/a[contains(@href, 'twitter.com')]
使用 XPathcontains()文本函数,我们可以过滤掉任何不包含文本的结果。 Xpath 函数非常强大,不仅可以检查真实性,还可以在运行时修改内容:
<div>
  <p class="socials">
    Follow us on
    <a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
    or connect with us on
    <a href="https://www.linkedin.com/company/jingzhengli/">Linkedin</a>
  </p>
</div>
//p[@class="socials"]/a/concat(text(), ": ",  @href)
Twitter!: https://twitter.com/@jingzhengli_dev
Linkedin: https://www.linkedin.com/company/jingzhengli/
CSS
ERROR: Syntax error, unrecognized expression: //p[@class="socials"]/a/concat(text(), ": ",  @href)
在这里,我们添加了concat()函数,它将所有提供的参数连接成一个值,然后才执行我们的匹配检查。 有时树的复杂性超过了基于上下文的选择器,我们必须实施一些复杂的结构检查。为此,xpath 具有强大的树导航功能,允许选择任何级别的祖先和兄弟姐妹:
<div>
  <span>For price contact </span>
  <a>Sales department </a>
  <div>
    <span>total: </span>
  </div>
  <span>166.00$</span>
  <span>*taxes apply</span>
</div>
//span[contains(text(), 'items:')]/following-sibling::span[position()>1 and position()<last()]/text() | //span[contains(text(), 'addons')]/following-sibling::span/text()
xpath
1. 166.00$
2. 25.00$
3. 0.5$
CSS
ERROR: Syntax error, unrecognized expression: //span[contains(text(), 'items:')]/following-sibling::span[position()>1 and position()<last()]/text() | //span[contains(text(), 'addons')]/following-sibling::span/text()
在此示例中,我们使用position()函数仅选择特定范围内的兄弟姐妹。|我们还使用运算符(or可以使用 for 或运算符)结合到 xpaths以完全检索所有定价信息。 如您所见,如果我们对路径逻辑有创意,xpaths 可以非常强大并解析几乎任何 html 结构!

扩展功能

大多数客户端中的 Xpath 可以扩展额外的功能,有些客户端甚至带有预注册的非标准功能。 例如,在 Python 的lxml(以及它基于parsel 的包)中,我们可以像这样轻松地注册新函数:
from lxml import etree

def myfunc(context, *args):
    return True 

xpath_namespace = etree.FunctionNamespace(None)
xpath_namespace['myfunc'] = myfunc
其他语言客户端遵循类似的过程。

Xpath 客户端

几乎每种编程语言都包含某种用于 XML 文件解析的 xpath 客户端。由于 HTML 只是 XML 的一个子集,我们几乎可以在所有现代语言中安全地使用 xpath!

Python 中的 Xpath

在 Python 中,有多个包实现了 xpath 功能,但是它们中的大多数都基于lxml包,它是libxml2libxslt C 语言库的 pythonic 绑定。这意味着 Python 中的 Xpath 选择器速度非常快,因为它在后台使用了强大的 C 组件。 虽然lxml是一个很好的包装器,但它缺乏网络抓取中使用的许多现代 API 可用性功能。为此,基于 lxml 的包parsel (由scrapy使用)和pyquery提供了更丰富的功能集。 使用parsel的示例用法:
from parsel import Selector

html = """
<html>
  <head>
    <title>Page Title</title>
  </head>
  <body>
    <h1 class="title">Page header by <a href="#">company</a></h1>
    <p>This is the first paragraph</p>
    <p>This is the second paragraph</p>
  </body>
</html>
"""
sel = Selector(html)
sel.xpath("//p").getall()
# [
# "<p>This is the first paragraph</p>",
# "<p>This is the second paragraph</p>",
# ]
其他工具推荐:
  • cssselect – 将 css 选择器转换为 xpath 选择器
  • parsel-cli – 用于测试 css/xpath 选择器的实时 REPL

PHP 中的 Xpath

在 PHP 中,最流行的 xpath 处理器是 Symphony 的DomCrawler
use Symfony\Component\DomCrawler\Crawler;

$html = <<<'HTML'
<html>
  <head>
    <title>Page Title</title>
  </head>
  <body>
    <h1 class="title">Page header by <a href="#">company</a></h1>
    <p>This is the first paragraph</p>
    <p>This is the second paragraph</p>
  </body>
</html>
HTML;

$crawler = new Crawler($html);
$crawler->filterXPath('//p');
其他工具推荐:
  • css-selector – 将 css 选择器转换为 xpath 选择器

Javascript 中的 Xpath

Javascript 原生支持 xpath,您可以在MDN 的 Introduction to Using Xpath in Javascript中阅读更多相关信息 其他工具推荐:
  • jQuery – 用于 xpath 查询的额外语法糖和助手。
  • cash – 轻量级的现代 jQuery 替代品。

其他语言的 Xpath

大多数其他语言都有某种 XPath 客户端,因为 XML 解析是一项重要的数据交换功能。意思是,我们可以用我们选择的语言解析网络抓取的内容! 例如,C# 本身也支持 XPath,您可以在官方文档中阅读更多相关信息,Objecive C 和其他低级语言也是如此。 虽然某些语言可能没有第一方 XPath 客户端,但很可能有一个社区包。例如,Go 语言在 xml、html 甚至 json 中都有 xpath 的社区包。

浏览器自动化中的 Xpath

浏览器自动化工具支持 XPath,无需任何额外的 Javascript 包。 要在Selenium中访问 XPath,我们可以使用by.XPath选择器。例如,对于 Python 中的 Selenium:
from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.get("https://httpbin.org/html")

element = driver.find_element(By.XPATH, '//p')
要在Playwright中访问 XPath,我们可以使用定位器功能,该功能将 CSS 选择器或 XPath 作为参数。例如在 Playwright 和 Python 中:
from playwright.sync_api import sync_playwright

with sync_playwright() as pw:
    browser = pw.chromium.launch(headless=False)
    context = browser.new_context(viewport={"width": 1920, "height": 1080})
    page = context.new_page()

    page.goto("http://httpbin.org/html")
    paragraphs = page.locator("//p")  # this can also take CSS selectors
要在Puppeteer中访问 XPath ,我们可以使用$$$方法。例如在 Javascript 中的 Puppeteer 中:
const puppeteer = require('puppeteer')
const browser = await puppeteer.launch();
let page = await browser.newPage();
await page.goto('http://httpbin.org/html');
await page.$("//p");

常问问题

为了结束这个介绍,让我们看一下有关使用 XPath 选择器进行 HTML 解析的一些常见问题:

XPATH 比 CSS 选择器快吗?

许多 CSS 选择器库将 CSS 选择器转换为 XPATH,因为它更快、更强大。话虽如此,这取决于每个单独的库和选择器本身的复杂性。一些使用广泛选择路径的 XPATH 选择器//在计算上可能非常昂贵。

我的 xpath 选择了比它应该多的数据

XPATH 广泛的选择器路径//是全局的而不是相对的。要使它们相对,我们必须添加相对性标记.->.//

如何通过多个名称匹配节点?

要通过多个名称匹配节点,我们可以使用通配符选择器和名称检查条件://*[contains("p h1 head", name())]– 将选择 h1、p 和头节点。

如何选择两个节点之间的选择元素?

如果我们知道两个节点,如文本标题,我们可以巧妙地使用符号来选择文本preceding-sibling//h2[@id="faq"]//following-sibling::p[(preceding-sibling::h2[1])[@id="faq"]]– 这个 xpath 选择 h2 标签下的所有段落节点faq,而不是其他h2节点下的元素。
<div>
  <span>items: </span>
  <span>(taxes not included)</span>
  <span>166.00$</span>
  <span>25.00$</span>
  <span>*taxes apply</span>
  <div>
    <span>addons:</span>
    <span>0.5$</span>
  </div>
</div>
//span[contains(text(), 'items:')]/following-sibling::span[position()>1 and position()<last()]/text() | //span[contains(text(), 'addons')]/following-sibling::span/text()
xpath
1. 166.00$
2. 25.00$
3. 0.5$
CSS
ERROR: Syntax error, unrecognized expression: //span[contains(text(), 'items:')]/following-sibling::span[position()>1 and position()<last()]/text() | //span[contains(text(), 'addons')]/following-sibling::span/text()

概括

在本文中,我们介绍了自己的 xpath 查询语言。我们发现 HTML 文档是具有节点和属性的数据树,可以被机器有效地解析。 我们浏览了最常用的 XPath 语法和函数,并使用我们的交互式 XPath 测试器探索了常见的 HTML 解析场景和最佳实践。 Xpath 是一种非常强大和灵活的路径语言,许多低级和高级语言都支持它:Python、PHP、Javascript 等。因此,无论您使用什么堆栈进行网络抓取,XPath 都可以- go HTML 解析工具! 如需更多 XPath 帮助,我们建议访问Stackoverflow 的 #xpath 标签

Written by 河小马

河小马是一位杰出的数字营销行业领袖,广告中国论坛的重要成员,其专业技能涵盖了PPC广告、域名停放、网站开发、联盟营销以及跨境电商咨询等多个领域。作为一位资深程序开发者,他不仅具备强大的技术能力,而且在出海网络营销方面拥有超过13年的经验。