在解析网络抓取的 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>在这个简单网页的基本示例中,我们可以看到文档已经类似于数据树。让我们更进一步说明这一点: 在这里,我们可以更轻松地理解它:它是一棵节点树,每个节点还可以附加属性,如关键字属性(如和)和自然属性(如文本
class
)href
。 现在我们熟悉了 HTML,让我们熟悉 CSS 选择器语法,并使用它来解析一些数据!
HTML 语法概述
Css 选择器通常被称为选择器,单个选择器指示到特定 HTML 节点的路径。<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>bxpath
ERROR: Expression is not a valid instance of the grammarCSS
<b>@jingzhengli_dev</b>如您所见,CSS 选择器只是一系列由空格或“>”字符连接的各种表达式。让我们看看网络抓取中最常用的表达式:
表达 | 描述 |
---|---|
node |
选择与节点名称匹配的任何后代(子代、孙代等) |
>node |
选择与节点名称匹配的直接子节点 |
~node |
选择与节点名称匹配的兄弟 |
+node |
仅选择与节点名称匹配的相邻兄弟节点 |
.class |
类约束 – 仅选择包含此类的节点 |
#id |
ID 约束 – 仅选择包含此 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 导航
本文中我们仅以我们网站jingzhengli.com为例,实际运用中你可替换为你需要的任意网站CSS 选择器最重要的特性是通过名称和后代链接来选择节点。例如,使用
>
字符我们可以链接多个节点选择器:
<div> <p class="socials"> Follow us on <a href="https://twitter.com/@jingzhengli_dev">Twitter!</a> </p> </div>
div>p>axpath
ERROR: Expression is not a valid instance of the grammarCSS
<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>axpath
ERROR: Expression is not a valid instance of the grammarCSS
<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 grammarCSS
<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')>bxpath
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-class和pseudo-element可用性因客户端而异。例如,:contains
伪类在大多数以网络抓取为重点的客户端中可用,例如parsel和 javascript 原生的jquery或sizzle
<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执行它们// 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