玩转Puppeteer

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了玩转Puppeteer相关的知识,希望对你有一定的参考价值。

参考技术A

1 简介

Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。

Puppeteer 默认以无头模式(headless)运行,也就是运行一个无界面的 Chrome 浏览器。

2 应用场景

2.1 页面生成 PDF

Puppeteer 提供了页面生成 PDF 的方法,我们可以利用这个方法来将页面导出为 PDF ,导出的 PDF 效果和 Chrome 浏览器打印功能导出的 PDF 一致。

具体的应用场景有:

2.2 页面截图

Puppeteer 提供了截图的方法,我们可以利用这个方法来将页面的指定区域导出为 jpeg 或 png 图片。

具体的应用场景有:

2.3 服务端渲染

单页应用(SPA)的主要内容是在 javascript 向服务端请求数据后渲染的,存在爬虫难以抓取主要内容、首屏加载慢等问题,而使用 Next.js、Nuxt.js 等服务端渲染框架改造的成本较高。

如果只是为了搜索引擎优化,我们可以考虑利用 Puppeteer 来实现。我们可以在网关层判断请求的来源,如果是爬虫,直接返回由 Puppeteer 服务端渲染的 html 文件。

2.4 自动化UI测试

使用 Puppeteer 可以模拟 Chrome 浏览器环境,结合 JavaScript 测试框架(如 Jest)可以实现自动化 UI 测试。

Puppeteer 提供了 Mouse 类来模拟鼠标操作,提供了 Keyboard 类来模拟键盘操作,提供了 Touchscreen 类来模拟触屏操作,并且 Puppeteer 提供的 Page 类里有很多方法可以用来操作元素,比如点击元素、聚焦元素等操作。

2.5 页面检测分析

使用 Puppeteer 提供的 page.tracing 系列方法捕获网站的 timeline trace 来对页面进行性能分析。

使用 Puppeteer 提供的 page.coverage 系列方法来获取 JavaScript 和 CSS 覆盖率。

使用 Puppeteer 提供的 page.metrics() 方法来获取某个时间点页面的指标数据,包括页面的 documents 数量、iframe 数量、js 事件数量、dom 节点数量、布局数量、样式重新计算数量、布局时间、样式重新计算总时间、js 代码执行总时间、任务执行总时间、占用堆内存大小、总的堆内存大小。

使用 Puppeteer 提供的 Request 类和 Response 类来监控页面发送的请求和接受的响应。

3 基础概念

Puppeteer API 是分层次的,反映了浏览器结构。


Puppeteer 使用 DevTools 协议与浏览器进行通信。

Browser 是浏览器实例,可以有多个浏览器上下文。

BrowserContext 是浏览器上下文实例,定义了一个浏览会话并可拥有多个页面。

Page 是页面实例,至少拥有一个框架(主框架mainFrame),可能还有由 iframe 创建的其他框架。

Frame 是框架实例,至少有一个默认的 JavaScript 执行上下文。可能还有与扩展插件关联的执行上下文。

Worker 表示一个WebWorker,具有单一执行上下文。

4 快速上手

4.1 安装 puppeteer-core

npm i puppeteer-core

puppeteer-core 是一个轻量级的 Puppeteer 版本,自 1.7.0 版本以来,官方都会发布一个 puppeteer-core 包,安装这个包时,默认不会下载 Chromium。

4.2 下载 Chromium

Puppeteer 官网: http://www.puppeteerjs.com/

chromium 下载链接: https://registry.npmmirror.com/binary.html?path=chromium-browser-snapshots/


找到对应环境对应版本的 chromium 并下载。比如,我的环境是 Win_x64,安装的 Puppeteer 是 13.6.0 版本,查看 Puppeteer 的版本记录,发现对应的 chromium 是 982053。

4.3 启动无头浏览器

在项目根目录下新建一个 chrome 目录,在 chrome 目录下新建 win64-938248 目录,将下载的 chromium 解压放入其中。

在项目根目录下新建一个 src 目录,在 src 目录下新建 util 目录,在 util 目录下新建 browser.js文件。

上述代码简单封装了一个启动无头浏览器的方法,通过设置 chromium 路径的方式来启动方便在不同操作系统下启动不同的 chromium,并在启动时传入一些参数进行优化。

4.4 导出 HTML 文件

这里以导出 HTML 文件的小功能为例,介绍如何使用 Puppeteer。

在 src 目录下新建 example 目录, 在 example 目录下新建 exportHtml.js 文件。

这里启动了一个无头浏览器,并创建了一个页面,去访问百度首页,然后导出网页的 HTML 源码并写入 exportHtml.html 文件中。

使用 node 运行这个 js 文件。

node ./src/example/exportHtml.js

运行完毕后,example 目录下出现了 exportHtml.html 文件。打开这个文件,就能看到静态的百度首页了。因为这里只导出了 HTML 源码,没有对其他资源进行处理,直接打开页面会有一些资源路径问题。

5 更多示例

5.1 网页导出 PDF 文件

网页导出 PDF 文件这个功能往往需要配合网页的打印样式(@media print...)使用。

在 @media print 里编写的样式代码只在打印的时候生效,通过编写打印样式,我们可以对页面元素的样式进行调整,控制打印的效果。

这里我们以将 vue2 官方文档导出为 PDF 为例,介绍 Puppeteer 导出 PDF 的功能。

首先,我们打开 vue2 的官方文档( https://cn.vuejs.org/v2/guide/index.html )。使用 Chrome 浏览器的打印功能,在打印预览中我们可以看到打印效果和实际网页的内容并不一致。这是因为 vue2 的官方文档网页添加了一些打印样式。访问打印样式所在的文件( https://cn.vuejs.org/css/page.css )并搜索 @media print 就能明白为什么在打印预览中一些元素(如顶栏、侧边栏等)被隐藏或者样式不同了。

接下来进入正题,让我们使用 Puppeteer 来实现同样的打印(导出 PDF)功能。

在 example 目录下新建 exportPdf.js 文件。

pdf 方法会返回 PDF 文件的 Buffer 数据,以便后续处理。这里我们只是演示一下这个功能,传入 path 参数就能让 pdf 方法将 PDF 文件写到指定路径了。

使用 node 运行这个 js 文件。

node ./src/example/exportPdf.js

运行完毕后,example 目录下出现了 exportPdf.pdf 文件。打开这个文件便能看到 vue2 官方文档了。

5.2 网页截图

在这一部分,我们演示一下整个网页截图的功能。

在 example 目录下新建 exportImg.js 文件。

使用 node 运行这个 js 文件。

node ./src/example/exportImg.js

运行完毕后,example 目录下出现了 exportImg.png 文件。打开这个文件便能看到 vue2 官方文档了。

Puppeteer,保存网页和图像

【中文标题】Puppeteer,保存网页和图像【英文标题】:Puppeteer, save webpage and images 【发布时间】:2019-05-07 12:01:13 【问题描述】:

我正在尝试保存网页,以供 Nodejs 和 puppeteer 离线使用。我看到很多例子:

await page.screenshot(path: 'example.png');

但是对于更大的网页,这不是一个选择。所以在 puppeteer 中一个更好的选择是加载页面然后保存如下:

const html = await page.content();
// ... write to file

好的,这行得通。现在我要像推特一样滚动页面。所以我决定屏蔽 puppeteer 页面中的所有图片:

page.on('request', request => 
    if (request.resourceType() === 'image') 
        const imgUrl = request.url()
        download(imgUrl, 'download').then((output) => 
            images.push(url: output.url, filename: output.filename)
        ).catch((err) => 
            console.log(err)
        )
        request.abort()
     else 
        request.continue()
    
)

好的,我现在使用“npm 下载”库来下载所有图像。是的,下载图片没问题:D。

现在当我保存内容时,我想将它指向源中的离线图像。

const html = await page.content();

但现在我喜欢替换所有的

<img src="/pic.png?id=123"> 
<img src="https://twitter.com/pics/1.png">

还有类似的东西:

<div style="background-image: url('this_also.gif')></div>

那么有没有办法(在 puppeteer 中)抓取一个大页面并离线存储整个内容?

Javascript 和 CSS 也不错

更新

现在我将用 puppeteer 再次打开大 html 文件。

然后将所有文件截取为: https://dom.com/img/img.jpg, /file.jpg, ....

request.respond(
    status: 200,
    contentType: 'image/jpeg',
    body: '..'
);

我也可以使用 chrome 扩展来做到这一点。但是我喜欢有一些选项page.html()的函数,和page.pdf()一样

【问题讨论】:

我认为网页太动态了,不能做这样的事情......(取决于你想在上面花费多少时间)你的最终目标是什么,只是查看它?跨度> 请问如何操作html?如果是这样,您将使用 node 中的cheerio 或 page.evaluate 中的 jQuery。 问题是如何指向本地下载。当你有 css、javascript 图像时。 @Cody,目标是拯救大型网站(如 Twitter、Facebook 等)。离线使用 【参考方案1】:

我们回到第一个,你可以用fullPage截图。

await page.screenshot(path: 'example.png', fullPage: true);

如果你真的想将所有资源下载到离线,是的,你可以:

const fse = require('fs-extra');

page.on('response', (res) => 
    // save all the data to SOMEWHERE_TO_STORE
    await fse.outputFile(SOMEWHERE_TO_STORE, await res.buffer());
);

然后,你就可以通过 puppeteer 离线浏览网站了。

await page.setRequestInterception(true);
page.on('request', (req) => 
    // handle the request by responding data that you stored in SOMEWHERE_TO_STORE
    // and of course, don't forget THE_FILE_TYPE
    req.respond(
        status: 200,
        contentType: THE_FILE_TYPE,
        body: await fse.readFile(SOMEWHERE_TO_STORE),
    );
);

【讨论】:

最好依赖requestfinished事件。【参考方案2】:

现在我将使用:

https://github.com/dosyago/22120

这个项目的目标:

This project literally makes your web browsing available COMPLETELY OFFLINE. 
Your browser does not even know the difference. It's literally that amazing. Yes.

【讨论】:

很好,但任何 MITM 代理都可以做到这一点。

以上是关于玩转Puppeteer的主要内容,如果未能解决你的问题,请参考以下文章

puppeteer环境搭建——新自动化工具(同webdriver)

玩转Ubuntu专栏目录

从零玩转Nginx-从零玩转nginx

带你从零玩转云服务器

每天5分钟玩转容器技术 整理目录

玩转Puppeteer