JavaScript 网页抓取——如何使用 Puppeteer 抓取网页

JavaScript 网页抓取——如何使用 Puppeteer 抓取网页
lerhxx
2023-03-02
905
阅读10分钟
原文:www.freecodecamp.org/news/web-sc…

欢迎来到爬虫的世界!你是否曾经需要来自网站的数据,却发现很难以特定的结构访问?这就是爬虫的用武之地。

使用脚本,我们可以从网站中提取需要的数据用于各种用途,如创建数据库,进行某些分析等。

免责声明: 需要注意,确保网站是允许爬取的,并且爬取行为是合情合法的。

JavaScript 和 Node.js 提供了各种便于爬取网站的库。对于简单的数据提取,你可以使用 Axios 获取 API 或者网站 HTML。

但是如果你想做包括自动化在内的更高级的任务,则需要 Puppeteer, Cheerio, 或者 Nightmare(别为它的名字是"梦魇"而担心,它用起来还不错😆) 这样的库。

本文将介绍使用 Puppeteer 在 JavaScript 和 Node.js 中抓取网页的基础知识。向你展示在网站上获取信息和点击按钮(如翻页)的一些基础知识。

最后,我会推荐一些我们刚创建的项目的改进点来练习和学习。

先决条件
在开始使用 JavaScript,Node.js,和 HTML DOM 深入了解我们的第一个网站之前,建议对这些技术有一个基本的了解,这有助于你对该主题的学习和理解。

让我们开始吧!🤿

如何初始化第一个 Puppeteer 爬虫
新项目。。。新文件夹!首先,创建名为 first-puppeteer-scraper-example的文件夹。我们爬虫的代码将保存在这里面。

mkdir first-puppeteer-scraper-example
使用 mkdir 创建新项目

现在,该初始化带有 package.json 文件的 Node.js 仓库了。它能将仓库和 NPM 包(如 Puppeteer 库)的信息存储起来。

npm init -y
使用命令 npm init 初始化 package.json 文件

输入这个命令之后,你能够在仓库中找到package.json文件了。

{
“name”: “first-puppeteer-scraper-example”,
“version”: “1.0.0”,
“main”: “index.js”,
“scripts”: {
“test”: “echo “Error: no test specified” && exit 1”
},
“keywords”: [],
“author”: “”,
“license”: “ISC”,
“dependencies”: {
“puppeteer”: “^19.6.2”
},
“type”: “module”,
“devDependencies”: {},
“description”: “”
}
用命令npm init -y初始化package.json

在继续之前,需要确保项目配置项是支持处理 ES6 的。可以在配置项末尾添加"types": “module”。

{
“name”: “first-puppeteer-scraper-example”,
“version”: “1.0.0”,
“main”: “index.js”,
“scripts”: {
“test”: “echo “Error: no test specified” && exit 1”
},
“keywords”: [],
“author”: “”,
“license”: “ISC”,
“dependencies”: {
“puppeteer”: “^19.6.2”
},
“type”: “module”,
“description”: “”,
“types”: “module”
}
启用 ES6 功能的package.json

初始化的最后一步是安装 Peppeteer 库。如下:

npm install puppeteer
使用命令 npm install 安装 Puppeteer

哇!我们准备一起抓取我们的第一个网站。🤩

如何抓取第一条数据
我们将会使用ToScrape 作为学习平台。这个在线沙盒提供了两个专为爬虫而设计的项目。使它成为一个很好的学习数据提取和页面导航的起点。

对于初学者,我们更多的关注 Quotes to Scrape。

如何初始化脚本
在项目的根目录中,创建index.js文件。这是我们应用的入口。

为简单起见,我们的脚本包含一个负责获取网站 quotes 的函数(getQuotes)。

在这个函数里,需要执行几个步骤:

puppeteer.launch 启动 Puppeteer(实例化用于操作浏览器的browser变量)
browser.newPage 打开一个新页面/标签页(实例化用于操作页面的page变量)
page.goto 将新页面导航到 http://quotes.toscrape.com/
以下是附有注释的初始化脚本:

import puppeteer from “puppeteer”;

const getQuotes = async () => {
// 启动 Puppeteer:
// - browser 变量 (headless: false - 便于调试,因为能看到浏览器的性温)
// - 无默认视窗 (defaultViewport: null - 网页将会全屏展示)
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
});

// 打开新页面
const page = await browser.newPage();

// 在新页面中:
// - 打开网站 “http://quotes.toscrape.com/”
// - 等待文档加载完成 (HTML 准备就绪)
await page.goto(“http://quotes.toscrape.com/”, {
waitUntil: “domcontentloaded”,
});
};

// 开始抓取
getQuotes();
你觉得运行我们的爬虫会看到什么?用以下命令试试:

node index.js
node index.js 命令启动我们的应用

启动之后能够看到一个新浏览器被启动,并且在新页面加载了 Quotes 网站,神奇吧?🪄

image-353 Quotes to Scrape 主页被我们的脚本初始化了

注意: 第一个版本没有关闭浏览器,也就是说需要手动关闭浏览器才能停止应用运行。

如何获取第一个 Quote
当你想抓取网站时,需要使用 HTML DOM。我建议通过检查页面查找需要的元素。

这里我们将会一步一步的获取第一个 quote,作者,以及文本内容。

看了页面 HTML 结构后,可以发现一个 quote 被带有类名 quote(class=“quote”) 的

元素包裹,这是很重要的信息,因为会用到CSS selectors (如 .quote)来抓取数据。

image-354 浏览器检查器选中第一个 quote 的

image-355 这是 HTML 中如何渲染示每一个 quote 的例子

现在我们有了这些知识,可以回到getQuotes,改进我们的代码,选择第一个 quote 并提取里面的数据。

在page.goto后面添加以下内容:

page.evaluate 从页面中提取数据(它将执行一个以页面上下文为入参的函数并将结果返回)
document.querySelect获取 quote 的 HTML 节点(将获取并返回第一个带有quote类名的

)
quote.querySelector从上一步的 quote 节点中获取 quote 文本和作者(获取并返回<div class="quote>下类名为 text和author元素)
以下是带有详细注释的新版本:

import puppeteer from “puppeteer”;

const getQuotes = async () => {
// 启动 Puppeteer:
// - browser 变量 (headless: false - 便于调试,因为能看到浏览器的性温)
// - 无默认视窗 (defaultViewport: null - 网页将会全屏展示)
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
});

// 打开新页面
const page = await browser.newPage();

// 在新页面中:
// - 打开网站 “http://quotes.toscrape.com/”
// - 等待文档加载完成 (HTML 准备就绪)
await page.goto(“http://quotes.toscrape.com/”, {
waitUntil: “domcontentloaded”,
});

// 获取页面数据
const quotes = await page.evaluate(() => {
// 获取第一个类名为 “quote” 的元素
const quote = document.querySelector(“.quote”);

// 从先前的 quote 元素中获取子节点
// Get the displayed text and return it (`.innerText`)
const text = quote.querySelector(".text").innerText;
const author = quote.querySelector(".author").innerText;

return { text, author };

});

// 输出 quotes
console.log(quotes);

// 关闭浏览器
await browser.close();
};

// 开始爬取
getQuotes();
有趣的是,这里获取元素使用的函数名称和浏览器检查器中使用的是一样的。看:

image-362

在浏览器检查器中运行document.querySelector后,输出了第一个 quote(和 Puppeteer 一样)

再次运行我们的脚本,能看到以下输出:

{
text: ‘“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”’,
author: ‘Albert Einstein’
}
运行node index.js 后的输出

我们成功了!我们抓取的第一个元素就在终端里。让我们更进一步,获取当前页面所有的 quote。🔥

如何获取页面中所有的 Quote
我们已经知道了如何获取一个 quote,改进一下我们的代码,获取所有的 quote,逐一提起它们的数据。

原先我们使用document.getQuerySelector获取第一个匹配的元素(第一个 quote)。为了获取到所有的 quotes,将替换为document.qu4rySelectorAll。

需要以下的步骤:

document.getQuerySelector替换为document.querySelectorAll(获取并返回全部带有类名quote的

元素)
Array.from(quoteList)将获取到的元素转换为列(确保列表是可迭代的)
将原来获取 quote 文本和作者的代码移至循环内并返回(提取每一个
下类名为text和author的元素)
更新后的代码如下:

import puppeteer from “puppeteer”;

const getQuotes = async () => {
// 启动 Puppeteer:
// - browser 变量 (headless: false - 便于调试,因为能看到浏览器的性温)
// - 无默认视窗 (defaultViewport: null - 网页将会全屏展示)
const browser = await puppeteer.launch({
headless: false,
defaultViewport: null,
});

// 打开新页面
const page = await browser.newPage();

// 在新页面中:
// - 打开网站 “http://quotes.toscrape.com/”
// - 等待文档加载完成 (HTML 准备就绪)
await page.goto(“http://quotes.toscrape.com/”, {
waitUntil: “domcontentloaded”,
});

// 获取页面数据
const quotes = await page.evaluate(() => {
// 获取第一个类名为 “quote” 的元素
const quoteList = document.querySelectorAll(“.quote”);

// 将 quoteList 转换为可迭代的数组
// 获取每一个 quote 的文本和作者
return Array.from(quoteList).map((quote) => {
  // 在先前得到的 quote 元素中获取子元素
  // 获取显示的文本并返回(`.innerText`)
  const text = quote.querySelector(".text").innerText;
  const author = quote.querySelector(".author").innerText;

  return { text, author };
});

});

// 输出所有 quote
console.log(quotes);

// 关闭浏览器
await browser.close();
};

// 开始抓取
getQuotes();
再运行一次脚本,将会输出一个 quote 列表。列表中的每个元素都包含 text 和 author 属性。

[
{
text: ‘“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”’,
author: ‘Albert Einstein’
},
{
text: ‘“It is our choices, Harry, that show what we truly are, far more than our abilities.”’,
author: ‘J.K. Rowling’
},
{
text: ‘“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”’,
author: ‘Albert Einstein’
},
{
text: ‘“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”’,
author: ‘Jane Austen’
},
{
text: ““Imperfection is beauty, madness is genius and it’s better to be absolutely ridiculous than absolutely boring.””,
author: ‘Marilyn Monroe’
},
{
text: ‘“Try not to become a man of success. Rather become a man of value.”’,
author: ‘Albert Einstein’
},
{
text: ‘“It is better to be hated for what you are than to be loved for what you are not.”’,
author: ‘André Gide’
},
{
text: ““I have not failed. I’ve just found 10,000 ways that won’t work.””,
author: ‘Thomas A. Edison’
},
{
text: ““A woman is like a tea bag; you never know how strong it is until it’s in hot water.””,
author: ‘Eleanor Roosevelt’
},
{
text: ‘“A day without sunshine is like, you know, night.”’,
author: ‘Steve Martin’
}
]
运行node index.js后的输出结果

太棒了!我们的脚本现在能够获取到第一页的所有 quote。 👏

如何导航到下一页
现在我们的脚本能够获取到一个页面的所有 quote。如果能够点击页面底部的"下一页"并对它进行同样的操作岂不更有趣。

image-363 页面底部的 “下一页” 按钮

让我们回到浏览器检查器中,找到获取它的 CSS 选择器。

可以发现,按钮放在类名为pager的无序列表

    • )中。列表有一个类名为next的
    • 元素(

在 CSS 中,有不同的方式定位到这个链接:

.next > a:但这种方式是有风险的,因为如果有其它以.next为作为包含链接的父元素,将会点击它。
.pager > .next > a:这是安全的,这样确保了连接是包含在.pager元素下的.next元素。拥有多个此层次结构的机率很低。
image-356

“下一页”按钮在 HTML 中的渲染例子

要点击这个按钮,需要在脚本末尾,既console.log(quotes);后添加await page.click(“.pager > .next > a”);。

将await browser.cllose()注释掉,才能在爬虫打开的浏览器中看到第二页。

这只是为了测试而做的临时改动,getQuotes函数现在应该如下:

// 输出 quote
console.log(quotes);

// 点击 “下一个 按钮
await page.click(“.pager > .next > a”);

// 关闭浏览器
// await browser.close();
如果再次运行我们的爬虫,浏览器就会停留在第二页:

image-357

Quotes to Scrape 的第二页在点击"下一页"按钮后被下载了

轮到你了!你可以做以下的事情
恭喜你完成了使用 Puppeteer 获取数据的介绍! 👏

现在轮到你来改进这个爬虫了,让它能够获取到 Quotes to Scrape 更多的数据。以下是你可以做的改进:

使用“下一页”按钮导航到所有页面并获取页面上所有的 quote。
获取 quote 的标签(每一个 quote 都有一个标签列表)。
爬取作者介绍页(通过点击作者名字)。
通过标签或者作者对 quote 进行分类(这和爬虫没有 100% 的关联,但却是个很好的改进点)。
尽情的发挥创意,做任何你觉得合适的事情🚀

GitHub 上提供了爬虫的代码
在 GitHub 上能够查看最终版本的爬虫!你可以随意的保存、fork 或者使用它。

=> 第一个 Puppeteer 爬虫 (示例)

成功开始抓取:非常感谢阅读本文!
我希望本文能够有效的向你介绍如何使用 JavaScript 和 Puppeteer 进行网络爬取。写这篇网站是一种乐趣,同时也希望你能感受到文章的充实和愉快。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值