使用 Cheerio 进行抓取:如何轻松从网页中收集数据

Raluca Penciuc,2022 年 12 月 21 日

手动收集和处理数据帮助您启动项目的时代已经一去不复返了。无论是电子商务网站还是潜在客户生成算法,有一点是肯定的:数据收集过程既繁琐又耗时。

在本文中,您将了解 Cheerio 如何利用其广泛的标记语言解析功能为您提供帮助,首先是一些琐碎的示例,然后是一个真实的使用案例。

Cheerio 简介

"你可能会问自己:"Cheerio 是什么?为了澄清一个常见的误解,我将从 Cheerio不是浏览器说起。

Cheerio 会解析以标记语言编写的文档,然后提供一个 API 来帮助你操作生成的数据结构,这可能是造成混淆的起因。但与浏览器不同的是,Cheerio 不会直观地渲染文档、加载 CSS 文件或执行 Javascript。

因此,Cheerio 的基本功能是接收 HTML 或 XML 输入,解析字符串并返回 API。这让它变得异常快速且易于使用,因此深受 Node.js 开发人员的欢迎。

设置环境

现在,让我们看看 Cheerio 能做什么的一些实际例子。首先,您需要确保您的环境已全部设置完毕。

不言而喻,您的机器上必须安装 Node.js。如果没有,只需根据您的操作系统,按照其官方网站上的说明进行操作即可。

确保下载长期支持版本(LTS),同时不要忘记 Node.js 包管理器(NPM)。您可以运行这些命令来确保安装顺利进行:

node -v
npm -v

输出结果应该是这样的

博客图片

现在,关于集成开发环境的争论:在本教程中,我将使用 Visual Studio Code,因为它非常灵活易用,但也欢迎你使用任何你喜欢的集成开发环境。

只需创建一个存放小项目的文件夹,然后打开终端。运行以下命令来建立一个 Node.js 项目:

npm init -y

这将创建package.json文件的默认版本,可随时修改。

下一步:我将安装 TypeScript 以及 Node.js 的类型定义:

npm install typescript @types/node -save-dev

在本教程中,我选择了 TypeScript,因为它为 JavaScript 对象提供了可选的静态类型,这使得代码在遇到类型错误时更加无懈可击。 

根据CircleCI 最近一项关于最流行编程语言的调查,正是这一优势使 JavaScript 在社区中的受欢迎程度稳步上升。

要验证前一个命令的安装是否正确,可以运行

npx tsc --version

现在,我将在项目目录根目录下创建tsconfig.json配置文件,它将定义编译器选项。如果你想更好地了解该文件及其属性,TypeScript 官方文档会为你提供帮助。 

如果没有,则只需复制并粘贴以下内容即可:

{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist"
},
"lib": ["es2015"]
}

差不多完成了!现在你必须安装 Cheerio(很明显):

npm install cheerio

最后但并非最不重要的一点是,创建src 目录,该目录将存放代码文件。说到代码文件,请在src 目录中创建并放置index.ts文件。

Cheerio 如何工作

太完美了现在你可以开始了。

目前,我将使用一个静态 HTML 文档来说明 Cheerio 的一些基本功能。只需将下面的内容复制并粘贴到项目中新建的static.html文件中即可:

<!DOCTYPE html>
<html lang="en">
<head>
<title>Page Name - Static HTML Example</title>
</head>
<body>
<div class="page-heading">
<h1>Page Heading</h1>
</div>
<div class="page-container">
<div class="page-content">
<ul>
<li>
<a href="#">Item 1</a>
<p class="price">$100</p>
<p class="stock">12</p>
</li>
<li>
<a href="#">Item 2</a>
<p class="price">$200</p>
<p class="stock">422</p>
</li>
<li>
<a href="#">Item 3</a>
<p class="price">$150</p>
<p class="stock">5</p>
</li>
</ul>
</div>
</div>
<footer class="page-footer">
<p>Last Updated: Friday, September 23, 2022</p>
</footer>
</body>
</html>

接下来,您必须将 HTML 文件作为输入提供给 Cheerio,Cheerio 将返回生成的 API:

import fs from 'fs'
import * as cheerio from 'cheerio'

const staticHTML = fs.readFileSync('static.html')
const $ = cheerio.load(staticHTML)

如果在此步骤中出现错误,请确保输入文件包含有效的 HTML 文档,因为从 Cheerio 1.0.0 版开始也会验证这一标准。

现在,你可以开始尝试 Cheerio 的功能了。NPM 软件包以其类似 jQuery 的语法和使用 CSS 选择器提取所需的节点而闻名。你可以查看他们的官方文档,了解更多信息。

比方说,您想提取页面标题:

const title = $('title').text()
console.log("Static HTML page title:", title)

我们应该测试一下,对吗?您使用的是 Typescript,因此您必须编译代码,创建dist 目录,然后执行相关的index.js文件。为了简单起见,我将在package.json文件中定义以下脚本:

"scripts": {
"test": "npx tsc && node dist/index.js",
}

这样一来,我只要跑就行了:

npm run test

脚本将处理我刚才描述的两个步骤。

好吧,但如果选择器匹配的 HTML 元素不止一个呢?让我们尝试提取无序列表中项目的名称和股票价值:

const itemStocks = {}
$('li').each((index, element) => {
const name = $(element).find('a').text()
const stock = $(element).find('p.stock').text()
itemStocks[name] = stock
})
console.log("All items stock:", itemStocks)

现在再次运行快捷脚本,终端输出应该是这样的:

Static HTML page title: Page Name - Static HTML Example
All items stock: { 'Item 1': '12', 'Item 2': '422', 'Item 3': '5' }

Cheerio 的使用案例

所以,这基本上只是冰山一角。Cheerio 还能解析 XML 文档,提取 HTML 元素的样式,甚至更改节点的属性。

但是,Cheerio 在实际应用中能提供什么帮助呢?

比方说,我们想收集一些数据来训练一个机器学习模型,以用于未来更大的项目。通常,您会在谷歌上搜索一些训练文件并下载,或者使用网站的 API。

但是,如果您找不到相关文件,或者您正在查看的网站没有提供应用程序接口、对数据有速率限制,或者没有提供您在页面上看到的全部数据,该怎么办呢?

这时,网络搜索就派上用场了。如果您想了解更多网络刮擦的实际应用案例,可以查看我们博客上这篇写得很好的文章

回到我们的绵羊,为了举例说明,让我们考虑一下我们现在的确切情况:想要数据,却找不到数据。请记住,Cheerio 不会处理 HTML 提取、CSS 加载或 JS 执行。

因此,在我们的教程中,我将使用Puppeteer浏览网站,抓取 HTML 并将其保存到文件中。然后,我将重复上一节的过程。

更具体地说,我想从 Reddit 上收集一些关于流行鼓模块的公众意见,并将数据集中到一个文件中,然后再进一步输入到潜在的 ML 训练模型中。接下来要做的事情也可能各不相同:情感分析、市场调研等等,不一而足。

博客图片

要求使用 HTML

让我们来看看这个用例如何写入代码。首先,你需要安装 Puppeteer NPM 软件包:

npm install puppeteer

我还将创建一个新文件reddit.ts,使项目更有条理,并在package.json文件中定义一个新脚本:

"scripts": {
"test": "npx tsc && node dist/index.js",
"parse": "npx tsc && node dist/reddit.js"
},

为了获取 HTML 文档,我将定义一个如下所示的函数:

import fs from 'fs'
import puppeteer from 'puppeteer'
import * as cheerio from 'cheerio'

async function getContent(url: string): Promise<void> {

// Open the browser and a new tab
const browser = await puppeteer.launch()
const page = await browser.newPage()

// Navigate to the URL and write the content to file
await page.goto(url)
const pageContent = await page.content()
fs.writeFileSync("reddit.html", pageContent)

// Close the browser
await browser.close()
console.log("Got the HTML. Check the reddit.html file.")
}

要快速测试这一点,请在代码中添加一个入口点并调用该函数:

async function main() {


const targetURL = 'https://old.reddit.com/r/Drumming/comments/r3tidc/yamaha_ead10/'
await getContent(targetURL)
}

main()
.then(() => {console.log("All done!")})
.catch(e => {console.log("Unexpected error occurred:", e.message)})

reddit.html文件应出现在你的项目树中,其中将包含我们想要的 HTML 文档。

我的节点在哪里?

现在,有一个更具挑战性的部分:您必须确定我们的用例所感兴趣的节点。返回浏览器(真正的浏览器)并导航到目标 URL。将鼠标光标移至注释部分,单击右键,然后选择 "检查 "选项。

打开 "开发工具 "选项卡,显示的正是你之前保存在机器上的 HTML 文档。

博客图片

要只提取注释,就必须确定页面这一部分所独有的选择器。您可以注意到,整个评论列表位于一个带有sitetable 嵌套列表类的div容器中。

再往下看,每条评论都有一个表单元素作为父元素,并带有usertext warning-on-unload类。然后,在底部,你可以看到每条评论的文本都被多个p元素分割开来。

解析和保存数据

让我们看看代码是如何工作的:

function parseComments(): void {

// Load the HTML document
const staticHTML = fs.readFileSync('reddit.html')
const $ = cheerio.load(staticHTML)

// Get the comments section
const commentsSection = $('div.sitetable.nestedlisting')

// Iterate each comment
const comments = []


$(commentsSection).find('form.usertext.warn-on-unload').each((index, comment) => {
let commentText = ""

// Iterate each comment section and concatenate them
$(comment).find('p').each((index, piece) => {
commentText += $(piece).text() + '\n'
})

comments.push(commentText)
})

// Write the results to external file
fs.writeFileSync("comments.json", JSON.stringify({comments}))
}

好了,现在让我们用新定义的函数更新入口点,看看这些代码是如何协同工作的:

async function main() {

const targetURL = 'https://old.reddit.com/r/Drumming/comments/r3tidc/yamaha_ead10/'
await getContent(targetURL)
parseComments()
}

main()
.then(() => {console.log("All done. Check the comments.csv file.")})
.catch(e => {console.log("Unexpected error occurred:", e.message)})

使用之前定义的脚本执行代码:

npm run parse

无头浏览器打开并导航到目标 URL 大约需要 5 到 10 秒。如果您对它更好奇,可以在我们每个函数的开始和结束处添加时间戳,以真正了解 Cheerio 的速度有多快。

comments.json 文件应该就是我们的最终结果:

博客图片

这个用例可以很容易地扩展到解析每条评论的向上投票数和向下投票数,或者获取评论的嵌套回复。可能性是无限的。

外卖

感谢您阅读完本教程。我希望你能理解 Cheerio 在数据提取过程中是多么不可或缺,以及如何将它快速集成到你的下一个搜索项目中。

我们的产品 WebScrapingAPI 也在使用 Cheerio。如果您发现自己被网络抓取中遇到的许多难题(IP 屏蔽、僵尸检测等)所困扰,不妨考虑试试它

新闻和更新

订阅我们的时事通讯,了解最新的网络搜索指南和新闻。

We care about the protection of your data. Read our <l>Privacy Policy</l>.Privacy Policy.

相关文章

缩图
指南网络抓取 API 快速入门指南

开始使用 WebScrapingAPI - 终极网络搜索解决方案!收集实时数据,绕过反僵尸系统,享受专业支持。

米赫内亚-奥克塔维安-马诺拉什
作者头像
米赫内亚-奥克塔维安-马诺拉什
9 分钟阅读
缩图
网络抓取科学轻松进行网络抓取:数据解析的重要性

了解如何通过数据解析、HTML 解析库和 schema.org 元数据有效地提取和组织数据,以便进行网络搜刮和数据分析。

Suciu Dan
作者头像
Suciu Dan
12 分钟阅读
缩图
指南如何使用 Puppeteer 制作刮刀并下载文件

了解如何使用 Puppeteer 下载文件的 3 种方法,并构建一个能完全做到这一点的网络搜刮器。

米赫内亚-奥克塔维安-马诺拉什
作者头像
米赫内亚-奥克塔维安-马诺拉什
8 分钟阅读