返回博客
网络爬虫技术
Ștefan Răcilă2022年12月15日阅读时间:7分钟

CSS 选择器速查表——网页抓取技巧与窍门

CSS 选择器速查表——网页抓取技巧与窍门

DOM 简介

在解析 HTML 文件的过程中,浏览器会在内存中创建一个类似树状结构的数据表示。这种表示被称为 DOM(文档对象模型)。每个 HTML 标签在 DOM 中都对应一个节点。节点具有名称、内容、子节点、样式、事件等属性。关于浏览器渲染原理的更多信息,请参阅这篇文章《浏览器渲染原理——幕后揭秘》

当我们说要访问网页数据时,实际上是指遍历 DOM 以定位特定节点集,并提取其中的内容。本文将介绍如何利用 CSS 选择器快速访问这些节点的各种技巧。

什么是 CSS 选择器?

为什么它们被称为 CSS(层叠样式表)选择器? 

CSS 用于定义页面上节点的样式。通过 CSS,您可以编写规则来规定节点的样式外观及其与其他节点的交互方式。一条规则由选择器和一组用于覆盖的样式组成。

因此,这些选择器之所以与 CSS 相关,是因为这是它们最常见的用途,但我们并不局限于仅在 CSS 中使用它们。使用 CSS 时,您希望选择一个节点并更改其样式属性。仔细想想,我们想要做的事情其实是一样的:选择一个节点并对它进行操作,比如读取其内容或触发某个事件。 

CSS 选择器是如何工作的?

如果能将选择过程可视化,将对你大有帮助。假设你想从某个网站抓取所有段落。你需要获取所有名称为 `p` 的节点。你可以手动完成这一操作,只需遍历 DOM 中的每个节点,并仅选择满足`node.tagName === 'P'`(标签名大写)条件的节点。

以下是一个可供参考的简短代码片段:

function scrapeByTagName(node, tagName) {
    if (node === null)
        return;
 
    node.childNodes.forEach(node => {
        //console.log(node.tagName)

        if (node.tagName?.toLowerCase() === tagName.toLowerCase()) {
            console.log(node)
            return
        }

        scrapeByTagName(node, tagName)
    });
}

我制作了一个示例网页,外观如下:

Example webpage card showing headings and paragraphs with IDs and CSS classes like .text and .bold

以下是该页面的 HTML 代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <link rel="stylesheet" href="styles.css">
    <script src="script.js"></script>
</head>

<body>
    <div id="wrapper">
        <h1 custom-attr="some data">Some Title</h1>
        <h2 custom-attr="some other data">Some Subtitle</h2>
        <div id="container">
            <p custom-attr>paragraph
                <span> subparagraph</span>
            </p>
            <p id="text">paragraph with id #text</p>
            <p class="bold">paragraph with class .bold</p>
            <p class="text">paragraph with class .text</p>
            <p class="text bold">paragraph with class .text.bold</p>
            <p class="text italic">paragraph with class .text.italic</p>
        </div>
    </div>
</body>

</html>

在浏览器控制台运行该函数后,我得到了以下响应:

Code snippet showing HTML <p> elements with id and class attributes under a function call scrapeByTagName(document, 'p')

如你所见,该函数记录了所有 p 标签。

要查看浏览器控制台,你需要打开开发者工具并切换到“控制台”选项卡,或者按 Esc 键。你可以通过右键点击某个元素并从菜单中选择“检查”,或者使用快捷键 Ctrl + Shift + I 来打开开发者工具。

如何使用 CSS 选择器?

我们将使用两种方法:querySelector 和 querySelectorAll。这些方法出现在所有类型为 Element 的对象上。我们要抓取的节点类型为 HTMLElement,它继承自 Element 类型。

querySelector 将返回第一个匹配选择器的节点。querySelectorAll 将返回一个包含所有匹配选择器的节点的列表。要复现之前展示的示例,我们只需调用 querySelectorAll 并遍历返回的列表。

document.querySelectorAll('p').forEach(node => console.log(node))
JavaScript console snippet using document.querySelectorAll('p') to log paragraph elements with various classes and IDs

你可以看到我使用了 document.querySelectorAll,这是因为在 window 上下文中,document 被定义为网页的根节点,即 html 标签的对应对象。你可以在任何节点上使用 querySelector 方法,而不仅仅是根节点。

若要实际进行数据抓取,你需要使用能够打开浏览器窗口并访问特定 URL 的库。只有这样,你的代码才能在该窗口的上下文中执行。若想深入了解具体实现方法,我推荐阅读这篇文章:《JavaScript 和 Node.js 网页抓取终极指南》

在 WebScrapingAPI,我们使用 Puppeteer。Puppeteer 是一个允许我们控制无头 Chromium 浏览器实例的库。您可以使用我们的 API 从网站中提取数据,而无需构建自定义爬虫。我们实际上提供了一个名为 extract_rules 的参数,它使用 CSS 选择器从给定的 URL 中提取数据。

CSS 选择器速查表

* 选择器

该选择器指定树中的所有元素。虽然用途不多,但了解它很有帮助。

.class 选择器

您可以通过 .class 选择器获取具有特定类的节点。该选择器主要在处理项目列表时使用。由于列表中的项目外观通常相似,它们可能具有相同的类。让我们搜索 .text 类。

Code snippet using `document.querySelectorAll('.text')` to iterate over elements, with sample paragraph tags

也许你想选择具有 .bold 类的节点。

JavaScript console example selecting elements with class .bold and logging two paragraph nodes

看起来还有另一个元素拥有 .bold 类。你可以通过将多个类名连接起来,使类选择器更加具体。

JavaScript console example selecting .text.bold and logging one paragraph element with classes text and bold

请注意,类名之间不能有空格。

document.querySelectorAll('.text .bold').forEach(node => console.log(node))

此查询在上述 HTML 中未返回任何结果,因为它寻找的是具有 .text 类且其子元素(不一定是直接子元素)具有 .bold 类的元素。若找到,该查询将返回该子元素。 

#id 选择器

如果某个元素没有类,或者该类在文档中使用过于频繁怎么办?您可以使用 ID 属性来实现更深层次的特异性。使用 ID 选择器的缺点是,在大多数情况下,ID 在 HTML 页面中是唯一的,因此您无法通过它获取节点列表。

Code snippet using `document.querySelector('#text')` returning a paragraph element with id text

节点名称选择器

每个节点都有一个名称。该名称即 HTML 中成对标签的精确名称。您可以在选择器中使用该名称,以获取所有具有特定名称的节点。

JavaScript console output listing two <div> elements with ids wrapper and container

[属性] 选择器 

您可能会遇到需要选择所有具有特定属性的节点的情况。

JavaScript console example selecting elements with a custom-attr attribute, showing heading and paragraph nodes

您还可以指定属性值。

JavaScript console example selecting [custom-attr=

甚至可以限定属性值应包含的内容。您可以在等号前使用波浪号 ~,以此定义属性值应包含一组特定词汇。

JavaScript console example selecting [custom-attr~=

如果您决定构建一个爬虫,属性选择器将是使用最频繁的。它非常强大,其应用场景远比我在这里展示的要多。您可以在 W3 属性选择器中找到有关如何使用属性选择器的更多信息。

组合多个选择器

获取所有具有 id 的 p 节点。

JavaScript console example selecting p[id], returning a paragraph element with id text

选择所有作为 p 节点子节点的 span 节点。

JavaScript console example selecting p span, returning a span element inside a paragraph

获取所有作为 body 节点直接子节点的 div 节点。

JavaScript console example selecting body > div, returning a wrapper div element

获取所有具有 .text 类的 p 节点

JavaScript console example selecting p.text, returning paragraph elements with class text in normal, bold, and italic variants

这些选择器的组合方式无穷无尽。试着复制上面的 HTML 代码,并向其中添加更多节点。然后尝试不同的选择器组合。如果你想更全面地了解 CSS 选择器,Mozilla 提供了一篇精彩的文章,详细解释了 CSS 选择器在网页开发中的工作原理

总结

若想学习新知识,我建议你先了解其运作原理。没错,这虽是可选步骤,但它能为你提供他人所不具备的洞察。 

在软件开发领域,这些知识将帮助你针对问题或错误找到正确的解决方案。你甚至可以亲力亲为,创建自定义的解决方案。

若想真正理解 CSS 选择器,就必须理解 DOM。它本质上只是一棵树(即一个带名称和属性的节点的连通无环无向图),仅此而已。当你编写选择器时,其实只是编写了一段字符串,该字符串会被解析并用于查询 DOM。

关于作者
Ștefan Răcilă, 全栈开发工程师 @ WebScrapingAPI
Ștefan Răcilă全栈开发工程师

Stefan Racila 是 WebScrapingAPI 的 DevOps 及全栈工程师,负责开发产品功能并维护确保平台稳定运行的基础设施。

开始构建

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

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