在编写网页爬虫之前,你需要了解即将爬取的数据以及如何访问这些数据。访问网页数据的方法有很多,最常见的是使用 CSS 选择器。另一种方法是使用 XPath。你可以在此处查看 XPath 速查表。
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 标签。
要查看浏览器控制台,你需要打开开发者工具并切换到“控制台”选项卡,或者按 Esc 键。你可以通过右键点击某个元素并从菜单中选择“检查”,或者使用快捷键 Ctrl + Shift + I 来打开开发者工具。
如何使用 CSS 选择器?
我们将使用两种方法:querySelector 和 querySelectorAll。这些方法出现在所有类型为 Element 的对象上。我们要抓取的节点类型为 HTMLElement,它继承自 Element 类型。
querySelector 将返回第一个匹配选择器的节点。querySelectorAll 将返回一个包含所有匹配选择器的节点的列表。要复现之前展示的示例,我们只需调用 querySelectorAll 并遍历返回的列表。
document.querySelectorAll('p').forEach(node => console.log(node))
你可以看到我使用了 document.querySelectorAll,这是因为在 window 上下文中,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 中成对标签的精确名称。您可以在选择器中使用该名称,以获取所有具有特定名称的节点。
[属性] 选择器
您可能会遇到需要选择所有具有特定属性的节点的情况。
您还可以指定属性值。
甚至可以限定属性值应包含的内容。您可以在等号前使用波浪号 ~,以此定义属性值应包含一组特定词汇。
如果您决定构建一个爬虫,属性选择器将是使用最频繁的。它非常强大,其应用场景远比我在这里展示的要多。您可以在 W3 属性选择器中找到有关如何使用属性选择器的更多信息。
组合多个选择器
获取所有具有 id 的 p 节点。
选择所有作为 p 节点子节点的 span 节点。
获取所有作为 body 节点直接子节点的 div 节点。
获取所有具有 .text 类的 p 节点
这些选择器的组合方式无穷无尽。试着复制上面的 HTML 代码,并向其中添加更多节点。然后尝试不同的选择器组合。如果你想更全面地了解 CSS 选择器,Mozilla 提供了一篇精彩的文章,详细解释了 CSS 选择器在网页开发中的工作原理。
总结
若想学习新知识,我建议你先了解其运作原理。没错,这虽是可选步骤,但它能为你提供他人所不具备的洞察。
在软件开发领域,这些知识将帮助你针对问题或错误找到正确的解决方案。你甚至可以亲力亲为,创建自定义的解决方案。
若想真正理解 CSS 选择器,就必须理解 DOM。它本质上只是一棵树(即一个带名称和属性的节点的连通无环无向图),仅此而已。当你编写选择器时,其实只是编写了一段字符串,该字符串会被解析并用于查询 DOM。




