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)
});
}
我制作了一个示例网页,看起来像这样:

以下是相应的 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>
我在浏览器控制台中运行该函数后,得到了以下响应:

如您所见,该函数记录了所有 p 标签。
要查看浏览器控制台,您需要打开开发者工具并转到“控制台”选项卡,或者按下 esEsc键。您可以通过右键单击某个元素并选择 检查视图,或使用快捷键 Control + Shift + i。
如何使用 CSS 选择器?
我们将使用两种方法: querySele和 querySelectorAll。这些方法出现在所有类型为 Element 的对象上。我们试图抓取的节点类型为 HTMLElement,它继承自 Element 类型。
querySele将返回第一个匹配选择器的节点。 querySelectorAll 将返回一个包含所有匹配选择器的节点的列表。要重现之前展示的示例,我们只需调用 querySelectorAll,并遍历返回的列表即可。
document.querySelectorAll('p').forEach(node => console.log(node))

你可以看到我使用了 document.querySelectorAll,这是因为 document 在窗口上下文中被定义为网页的根节点,也就是 html 标签的对应对象。你可以对每个节点使用 querySelector 方法,而不仅仅是根节点。
要想真正进行网页抓取,你需要使用一个能够打开浏览器窗口并访问指定 URL 的库。只有这样,你的代码才能在该窗口的上下文中执行。若想进一步了解具体操作方法,我推荐这篇文章:《JavaScript 和 Node.js 网页抓取终极指南》。
在 WebScrapingAPI,我们使用Puppeteer。Puppeteer 是一个允许我们控制无头 Chromium 浏览器实例的库。您可以使用我们的 API 从网站中提取数据,而无需构建自定义爬虫。实际上,我们提供了一个名为extract_rules 的参数,它使用 CSS 选择器从指定的 URL 中提取数据。
CSS 选择器速查表
* 选择器
此选择器指定树中的所有元素。虽然它用处不大,但了解一下还是有帮助的。
.class 选择器
你可以通过使用 .class 来获取具有特定类的节点。这种方法主要在处理项目列表时使用。由于列表中的项目外观通常相似,它们可能具有相同的类。让我们来查找 .text 类。

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

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

请注意,课程之间没有空格。
document.querySelectorAll('.text .bold').forEach(node => console.log(node))
该查询无法从上面的 HTML 中返回任何结果,因为它要查找的是具有 .text 类且包含具有 .bold 类的子元素(不一定是直接子元素)的元素。如果找到,该查询将返回该子元素。
#id 选择器
如果某个元素没有类名,或者该类名在文档中被使用得太频繁,该怎么办?你可以使用 ID 属性来实现更高的特异性。使用 ID 选择器的缺点在于,在大多数情况下,ID 在 HTML 页面中是唯一的,因此无法通过它获取节点列表。

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

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

您还可以指定属性值。
![JavaScript 控制台示例:选择 [custom-attr="some data"],返回单个 h1 元素](/_next/image?url=https%3A%2F%2Fimages.prismic.io%2Fwebscrapingapi%2Fe6d9c82d-40b5-435b-a583-e355c31c3a54_image8.png%3Fauto%3Dformat%2Ccompress&w=3840&q=75)
甚至包括属性值应包含的内容。你可以在等号前使用波浪号 ~ 来指定属性值应包含一组单词。
![JavaScript 控制台示例:筛选 [custom-attr~="data"],匹配带有自定义属性的 h1 和 h2 元素](/_next/image?url=https%3A%2F%2Fimages.prismic.io%2Fwebscrapingapi%2Fd3160920-a556-43a7-a069-461561f0b188_image9.png%3Fauto%3Dformat%252Ccompress%26rect%3D0%252C0%252C1100%252C119%26w%3D1100%26h%3D119&w=3840&q=75)
如果你决定开发一个网页抓取工具,属性选择器将是使用最频繁的。它功能非常强大,其应用场景远不止我在这里展示的这些。关于如何使用属性选择器的更多信息,请参阅此处:W3 属性选择器。
组合多个选择器
获取所有具有该 ID 的 p 节点。
![JavaScript 控制台示例:选择 p[id],返回一个 id 为 text 的段落元素](/_next/image?url=https%3A%2F%2Fimages.prismic.io%2Fwebscrapingapi%2F71f8d44f-66a5-49c0-8b9d-aff2d8e50b64_image2.png%3Fauto%3Dformat%2Ccompress&w=3840&q=75)
选择所有作为 p 节点子节点的 span 节点。

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

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

这些选择器的组合方式多种多样。不妨复制上面的 HTML 代码,并向其中添加更多节点,然后尝试不同的选择器组合。如果你想更全面地了解 CSS 选择器,Mozilla 提供了一篇精彩的文章,详细讲解了CSS 选择器在网页开发中的工作原理。
摘要
如果你想学习新知识,我建议你先弄清楚它是如何运作的。没错,这虽然不是必须的步骤,但它能让你掌握一些别人所没有的信息。
在软件开发领域,这些信息将帮助您找到解决您的问题或错误的正确答案。您也可以亲力亲为,甚至创建一个自定义解决方案。
如果你真的想理解 CSS 选择器,就必须了解 DOM。它本质上只是一棵树(一个连通无环无向图),其中的节点拥有名称和一些属性。仅此而已。当你编写选择器时,你只是写一个字符串,该字符串会被解析并用于查询 DOM。




