返回博客
网络抓取科学
斯特凡·拉西拉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)
    });
}

我制作了一个示例网页,看起来像这样:

示例网页卡片,展示带有 ID 和 CSS 类(如 .text 和 .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 标签。

要查看浏览器控制台,您需要打开开发者工具并转到“控制台”选项卡,或者按下 esEsc键。您可以通过右键单击某个元素并选择 检查视图,或使用快捷键 Control + Shift + i。

如何使用 CSS 选择器?

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

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

document.querySelectorAll('p').forEach(node => console.log(node))
一段 JavaScript 控制台代码,使用 document.querySelectorAll('p') 来记录具有不同类名和 ID 的段落元素

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

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

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

CSS 选择器速查表

* 选择器

此选择器指定树中的所有元素。虽然它用处不大,但了解一下还是有帮助的。

.class 选择器

你可以通过使用 .class 来获取具有特定类的节点。这种方法主要在处理项目列表时使用。由于列表中的项目外观通常相似,它们可能具有相同的类。让我们来查找 .text 类。

使用 `document.querySelectorAll('.text')` 遍历元素的代码片段,附带示例段落标签

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

JavaScript 控制台示例:选择具有 .bold 类的元素并输出两个段落节点

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

JavaScript 控制台示例:选择 .text.bold 并输出一个具有 text 和 bold 类的段落元素

请注意,课程之间没有空格。

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

该查询无法从上面的 HTML 中返回任何结果,因为它要查找的是具有 .text 类且包含具有 .bold 类的子元素(不一定是直接子元素)的元素。如果找到,该查询将返回该子元素。 

#id 选择器

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

使用 `document.querySelector('#text')` 的代码片段,返回一个 id 为 text 的段落元素

节点名称选择器

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

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

[属性] 选择器 

您可能会遇到这样的情况:您希望选中所有具有某个特定属性的节点。

JavaScript 控制台示例:通过 custom-attr 属性选择元素,并显示标题和段落节点

您还可以指定属性值。

JavaScript 控制台示例:选择 [custom-attr="some data"],返回单个 h1 元素

甚至包括属性值应包含的内容。你可以在等号前使用波浪号 ~ 来指定属性值应包含一组单词。

JavaScript 控制台示例:筛选 [custom-attr~="data"],匹配带有自定义属性的 h1 和 h2 元素

如果你决定开发一个网页抓取工具,属性选择器将是使用最频繁的。它功能非常强大,其应用场景远不止我在这里展示的这些。关于如何使用属性选择器的更多信息,请参阅此处:W3 属性选择器

组合多个选择器

获取所有具有该 ID 的 p 节点。

JavaScript 控制台示例:选择 p[id],返回一个 id 为 text 的段落元素

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

JavaScript 控制台示例:选择 p 标签内的 span 标签,并返回段落中的 span 元素

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

JavaScript 控制台示例:选择 body > div,返回一个包裹 div 元素

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

JavaScript 控制台示例:选择 p.text,返回具有 class="text" 且样式为常规、粗体和斜体的段落元素

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

摘要

如果你想学习新知识,我建议你先弄清楚它是如何运作的。没错,这虽然不是必须的步骤,但它能让你掌握一些别人所没有的信息。 

在软件开发领域,这些信息将帮助您找到解决您的问题或错误的正确答案。您也可以亲力亲为,甚至创建一个自定义解决方案。

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

关于作者
Ștefan Răcilă,全栈开发者 @ WebScrapingAPI
斯特凡·拉西拉全栈开发工程师

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

开始构建

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

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