in

使用CSS选择器解析HTML的教程

使用CSS选择器解析HTML的教程

在解析网络抓取的 HTML 内容时,有多种技术可以选择我们想要的数据。对于简单的文本解析——可以使用各种文本解析技术,如正则表达式。然而,HTML 被设计为机器可读的文本结构——我们可以利用这一事实并使用特殊路径语言(如 CSS 选择器)以更高效和可靠的方式提取数据! 在本文中,我们将深入研究这种独特的路径语言,以及我们如何使用它从现代复杂的 HTML 文档中提取所需的详细信息。

什么是 CSS 选择器?

如果曾经进行过 Web 开发,您可能熟悉将样式应用于 HTML 网站的 CSS 选择器——我们可以使用相同的工具来解析 HTML 数据! 级联样式表协议提供了一种独特的路径语言,用于选择要应用样式的 HTML 节点——这些被称为 CSS 选择器。虽然此路径语言旨在查找用于样式化的节点,但我们也可以使用它来查找用于在我们的网络抓取工具中进行解析和导航的节点。 CSS 选择器往往很简短,但对于大多数与网络抓取相关的解析来说足够强大。让我们快速浏览一下常见的优缺点,尤其是与 Xpath 选择器相比

优点:
  • 简短易读 – 这反过来意味着更易于维护。
  • 为网络而生。由于 CSS 选择器用于为内容应用样式,这意味着我们的网络抓取工具应该能够像我们的浏览器一样选择元素。这,也意味着这种路径语言拥有庞大的社区
  • Web 标准的一部分 – 这意味着它是内置的 Web 浏览器和许多其他 Web 工具,这使得 CSS 选择器非常易于访问!
缺点:
  • 只能选择节点,where's xpath 也可以选择节点属性。然而,许多 CSS 选择器客户端使用它们自己的额外语法来扩展功能。例如,用于选择节点属性或内部文本的额外伪类,如::attr::text或简单的客户端方法,如.text().attribute()正在作为额外功能添加。
  • 不是很可扩展。与 XPATH 不同,CSS 选择器客户端倾向于提供较少的扩展机会。
  • 不如 XPATH 强大。我们稍后会对此进行更多探讨,但一般来说,CSS 选择器可能无法像 XPATH 选择器那样自由地遍历 HTML 树。

HTML 概述

HTML(超文本标记语言)旨在易于机器阅读和解析。换句话说,HTML 遵循节点及其属性的树状结构,我们可以轻松地以编程方式对其进行导航。 让我们从一个小示例页面开始并说明其结构:
<head>
  <title>
  Document 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>
在这个简单网页的基本示例中,我们可以看到文档已经类似于数据树。让我们更进一步说明这一点:
html 树图
HTML 树由节点组成,节点可以包含类、ID 和文本本身等属性。
在这里,我们可以更轻松地理解它:它是一棵节点树,每个节点还可以附加属性,如关键字属性(如和)和自然属性(如文本classhref。 现在我们熟悉了 HTML,让我们熟悉 CSS 选择器语法,并使用它来解析一些数据!

HTML 语法概述

Css 选择器通常被称为选择器,单个选择器指示到特定 HTML 节点的路径。
为了测试我们的 CSS 选择器,我们将使用嵌入式选择器游乐场
网络抓取中的普通 CSS 选择器通常看起来像这样:
css 协议说明
最常见的 CSS 选择器特性是类和后代选择器
在此示例中,我们的选择器将选择ID 为“title”的节点<b>的所有子节点,该节点位于CLASS 为“content”的节点下:<a><div>
<div class="content">
  <a id="title" href="https://twitter.com/@jingzhengli_dev">follow us on twitter
    <b>@jingzhengli_dev</b></a>
</div>
div.content a#title>b
xpath
ERROR: Expression is not a valid instance of the grammar
CSS
<b>@jingzhengli_dev</b>
如您所见,CSS 选择器只是一系列由空格或“>”字符连接的各种表达式。让我们看看网络抓取中最常用的表达式:
表达描述
node选择与节点名称匹配的任何后代(子代、孙代等)
>node选择与节点名称匹配的直接子节点
~node选择与节点名称匹配的兄弟
+node仅选择与节点名称匹配的相邻兄弟节点
.class类约束 – 仅选择包含此类的节点
#idID 约束 – 仅选择包含此 ID 的节点
[attribute=]属性匹配约束,例如span[data=foo]将选择所有具有 data=”foo” 属性的 span 节点
[attribute*=]属性包含约束,例如span[data*=foo]将选择data属性中具有“foo”值的所有跨度节点,例如:<span data="foobar gaz">
[... i]属性约束匹配大小写不敏感指示符,例如span[data=foo i]将同时匹配<span data="Foo"><span data="FOO">等。
,允许对多个选择器进行分组以连接所有结果,例如h1, h2将同时选择 h1 和 h2 节点
这是大多数 CSS 选择器客户端中可用的核心语法,它应该为我们提供足够的灵活性来解析我们在现代网络上可能遇到的大多数 HTML 树。让我们看一些例子!

基本 CSS 导航

本文中我们仅以我们网站jingzhengli.com为例,实际运用中你可替换为你需要的任意网站
CSS 选择器最重要的特性是通过名称和后代链接来选择节点。例如,使用>字符我们可以链接多个节点选择器:
<div>
  <p class="socials">
    Follow us on
    <a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
  </p>
</div>
div>p>a
xpath
ERROR: Expression is not a valid instance of the grammar
CSS
<a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
这允许我们定义严格的选择路径。但是,使用直接子选择器 ( >) 会使我们的选择器对于现代网站中的高度动态 HTML 文件来说过于严格。换句话说,如果<a>节点被包裹在其他一些样式节点中怎么办?那会破坏我们的选择器。 相反,我们应该混合使用空格和>选择器来找到稳定性和准确性的最佳点:
<div>
  <p class="primary socials content">
    Follow us on
  <ul>
    <li>
      <a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
    </li>
  </ul>
  <a href="#">advertisement</a>
  </p>
</div>
p.socials li>a
xpath
ERROR: Expression is not a valid instance of the grammar
CSS
<a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
这里我们没有定义直接路径,而是将我们的选择器定位到<div>包含类的节点socials(使用.socials类约束),从那里我们可以假设无序列表中的任何链接都是社交链接。 更少地依赖 HTML 结构而更多地依赖上下文允许创建在 HTML 结构更改时中断的选择器。 理想情况下,在设计我们的选择器时,我们希望找到结构和上下文之间的最佳点,这将不会导致误报,并且不会因小的 HTML 树更改而中断:
<html>
<p class="primary socials content">
  Follow us on
<ul>
  <li>
    <a href="https://twitter.com/@jingzhengli_dev">Twitter!</a>
  </li>
  <li>
    <a href="https://linkedin.com/@jingzhengli_dev">Linkedin!</a>
  </li>
</ul>
<a href="#">advertisement</a>
</p>
​
</html>
p.socials li>a[href*="linkedin"]
xpath
ERROR: Expression is not a valid instance of the grammar
CSS
<a href="https://linkedin.com/@jingzhengli_dev">Linkedin!</a>
在这里,我们使用属性包含约束来限制仅提取包含在其 url 中的链接"linkedin"
在本节中,我们发现了网络抓取器中好的 CSS 选择器的组成部分:我们想要一些不会选择误报的健壮的东西,以及一些不太严格的东西,可能会错过一些结果。 更进一步,让我们看看一些更复杂的解析场景,以及我们如何单独使用 CSS 选择器来解决它们 不幸的是,现代网站可能具有非常复杂和动态的 HTML 树,难以可靠地导航。让我们来看看复杂结构的几个常见示例,以及我们如何使用 CSS 选择器解决它们。 首先,很高兴记住我们不必将所有内容都塞进单个 CSS 选择器,我们可以使用以下语法安全地连接多个选择器,
<div>
  // American English website might contain:
  <p>Our favorite product is
    <b>product 1</b>
  </p>
  // while British website might contain:
  <p>Our favourite product is
    <b>product 2</b>
  </p>
</div>
p:contains('favorite')>b, p:contains('favourite')>b
xpath
ERROR: A QName used in an expression contains a namespace prefix that cannot be expanded into a namespace URI by using the statically known namespaces.
CSS
<b>product 1</b>
<b>product 2</b>
在此示例中,我们使用两个选择器来表示单词“favorite”的两种不同拼写。我们还使用了一个特殊的伪类:contains,它允许我们检查节点的文本值是否包含一些字符串。
请注意,pseudo-classpseudo-element可用性因客户端而异。例如,:contains伪类在大多数以网络抓取为重点的客户端中可用,例如parsel和 javascript 原生的jquerysizzle
选择器连接的另一个很酷的特性是所有结果都按它们的外观排序,这意味着我们可以安全地连接选择器并保留内容结构:
<div>
  <p>For this recipe you'll need:</p>
  <a href="https://patreon.com">Support us on patreon</a>
  <b>600g of butter</b>
  <i>*margarine also works</i>
  <p>First, preheat the oven to 200C...</p>
  <p class="promo">For more recipes click the subscribe button</p>
</div>
b, i, p:not([class*=promo])
ERROR: A QName used in an expression contains a namespace prefix that cannot be expanded into a namespace URI by using the statically known namespaces.
CSS
<p>For this recipe you'll need:</p>
<b>600g of butter</b>
<i>*margarine also works</i>
<p>First, preheat the oven to 200C...</p>
在此示例中,我们提取食谱文本,同时避免促销和其他与食谱无关的文本。我们还使用:not伪类,它允许我们反转我们的选择器约束,这对于过滤掉不需要的节点非常有用。 最后,用于网络抓取的最后一个重要的 CSS 选择器功能是结果切片。通常我们只想选择特定索引的匹配节点:
<div>
  <p>advertisement paragraph</p>
  <p>first paragraph</p>
  <p>second paragraph</p>
  <p>advertisement paragraph</p>
</div>
p:nth-of-type(n + 2):nth-last-of-type(n + 2)
xpath
ERROR: A QName used in an expression contains a namespace prefix that cannot be expanded into a namespace URI by using the statically known namespaces.

CSS

<p>first paragraph</p>
<p>second paragraph</p>

在这个例子中,我们使用:nth-of-type:nth-last-of-type实现基本的结果切片,它允许我们从我们的选择中过滤掉第一个和最后一个节点!


虽然 Css 选择器与 Xpath 选择器(或其他选项)相比可能显得有点笨拙,但它具有惊人的强大功能,通过一些巧妙的工程可以帮助我们可靠地从 HTML 文档中提取数据。

CSS 选择器客户端

CSS 选择器主要用于前端 Web 开发,但很少有后端实现用作 HTML 解析的客户端。让我们来看看最流行的实现 CSS 选择器的库。

Python

要在 Python 中解析 HTML,我们有几种选择。然而,他们中的许多人不是执行自然的 CSS 选择器,而是使用cssselect将它们转换为 XPath,并通过lxml XPath 客户端运行 XPath 选择器。此类客户端的一个示例是parsel。 其他包以不同的能力实现 CSS 选择器:

  • selectolax – 是一个新的现代的、快速的 CSS 选择器客户端。
  • beautifulsoup – 支持 CSS 选择器以及基于 xpath 和 python 对象的导航的经典 python 客户端。

对于 Python,解析 HTML 最流行的选择是 beautifulsoup4 或 parsel。

PHP

像 python 一样的 PHP 也更喜欢 Xpath 选择器因此大多数 CSS 选择器客户端使用css-selector组件将 CSS 选择器转换为 xpath 选择器并通过内置的DOMXPath或社区最喜欢的DOMCrawler执行它们
或者,当通过浏览器模拟客户端(如Selenium php)使用浏览器模拟时,也可以访问浏览器的 CSS 选择器功能:
// we can use findElements method of Selenium web driver to find elements by CSS selectors
$webDriver->findElements(WebDriverBy::cssSelector("div.content a#title>b"));

Ruby

Ruby 有几个支持 CSS 的客户端,但是最受欢迎的软件包是nokogiri,它提供 CSS 和 xpath 选择器以及大量解析实用程序函数和扩展:
html_doc = Nokogiri::HTML('<html><body><div class="socials"><a href="https://jingzhengli.com/blog">Our blog</a></div></body></html>')
@doc.css("div.socials>a").attributes["href"]

其他语言

通常 xpath 选择器比 CSS 选择器更受青睐,这使得 CSS 在上述少数选择器之外更难访问。也就是说,由于 CSS 选择器与 XPATH 选择器非常相似,因此通常至少有一个社区维护的翻译层可用!

常问问题

CSS 选择器比 XPATH 好吗?

两种路径语言都有其优点和缺点。通常,CSS 选择器比 xpath 更简洁但功能更弱。网络抓取时最好将两者混合使用!

如何使用 CSS 选择器选择元素父级?

CSS 选择器不支持选择父节点。相反,..可以使用 XPath 选择器,例如root/child/..将选择root

CSS 选择器可以像 XPATH 一样扩展吗?

不,通常 CSS 选择器不支持原生扩展。然而,许多网络抓取库可以使用额外的功能进行修补,因为 CSS 选择器通常在运行前转换为 XPath 选择器。也就是说,如果 CSS 功能不足,最好回退到 XPath。

CSS 选择器总结

在这篇介绍性文章中,我们介绍了 CSS 选择器的语法,探索了基本导航以巩固我们的知识,最后通过查看更高级的用法来完全掌握这种小型路径语言的功能! 虽然 CSS 选择器在网络抓取时非常理想,但最好同时利用 CSS 和 XPATH 选择器。常见的习惯用法是对简单路径使用 CSS 选择器,因为它们简短且易于遵循,而对于更复杂的选择,则回退到更冗长和更强大的 xpath。
如需进一步的 CSS 选择器帮助,我们建议查看Stackoverflow 上的#CSS-selectors 标签,该标签非常活跃,并且有很多专业的教师!

Written by 河小马

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