前言

Puppeteer的中文直译是操纵木偶的人,是一个提供顶层API来控制基于DevTools ProtocolChrome/ChromiumNode库。默认,它是运行在Chrome/Chromiumheadless模式下,但是也能改变它的配置,使其运行在full(non-headless)模式下。

总结一句话就是,Puppeteer就是一个运行在Node环境的浏览器

Puppeteer为我们提供了丰富的能力,其中包括:

  • 为页面生成截图或者PDF文件
  • 爬取SPA(Single-Page Application)或者SSR(Sever-Side Rendering)
  • 自动表单提交,UI测试,键盘输入等
  • 使用最新的JavaScript和浏览器特性创建一个自动化的测试环境
  • 捕获网页的timeline trace,帮助诊断性能问题
  • 测试Chrome插件

而这篇文章主要是介绍上面列出的第二点,抓取页面数据

环境搭建

  • 首先新建一个项目文件夹叫puppeteer-crawl-page-example并初始化项目
mkdir puppeteer-crawl-page-example
cd puppeteer-crawl-page-example && yarn init -y
  • 添加typescript(当然如果你更喜欢直接使用javascript编写项目,可以直接跳过关于typescript的配置介绍即可)
yarn add typescript ts-node nodemon --dev

ts-node是为了动态编译ts文件为js文件,nodemon则是为了监听编译后的js文件是否发生了变化,从而决定是否重启项目

  • package.json中添加运行命令,其中index.ts是一个文件,是我们编写代码的地方
{
  // 省略部分代码
  "script": {
    "start": "nodemon --watch . --exec \"ts-node\" index.ts"
  }
}
  • 安装Puppeteer,当我们安装的时候,它会自动安装一个最新版的Chromium,以保证Puppeteer能正常的工作,所以对于无法安装的,可能需要借助科学上网工具,当然也有其他解决方式就自行百度啦
yarn add puppeteer
  • 安装成功后,在index.ts文件中,首先我们需要先启动(launch)一个浏览器实例(Browser),也就是调用Puppeteerlaunch()方法创建一个Browser对象

PuppeteerAPI都是Promise类型

(async function() {
  // 启动一个浏览器, launch接收一个options用于配置信息
  const browser = await puppeteer.launch({
    // 是否在headless模式下运行浏览器
    headless: false,
    // 是否打开Devtool,如果设置为true,headless将强制为false
    devtools: true,
  });

  // 在browser上下文中,创建一个Page对象
  const page = await browser.newPage();

  // 关闭Chromium和所有的Page
  await browser.close();
}());
  • 启动项目
yarn run start

当看到以下信息,则说明项目启动成功,并且你会看到打开了浏览器,并且马上关闭了

$ nodemon --watch . --exec "ts-node" index.ts
[nodemon] 2.0.20
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node index.ts`
[nodemon] clean exit - waiting for changes before restart

小试牛刀

我们先来写个简单的截图功能,主要是使用Page实例上的screenshot()方法

// ...

// 加载页面
await page.goto('', {
  // 超时时间,0为禁用超时
  // timeout: 0,
})

// 对当前页面进行截图
await page.screenshot({
  type: 'png',
  // 保存文件的路径
  path: 'screenshot.png',
  // 截取整个可滚动页面的屏幕截图
  // fullPage: true
});

// ...

运行结束后,你会看到在项目的根目录下多了个screenshot.png文件,点开你会发现和我们在正常的浏览器中输入的链接加载出来的页面是一模一样,是不是感受到了Puppeteer的强大🚀

大显神通

接下来,我们可以使用Puppeteer来抓取页面信息,但在抓取页面之前我们需要知道要抓取的内容的信息,也就是该怎么访问这些元素。

我们需要获取整个文章列表(也就是<div class="mainContent"></div>中的article标签中的内容),并获取每个文章的title, description和阅读数。

整个代码如下:

// ...

// 加载页面
await page.goto('', {
  // 超时时间,0为禁用超时
  // timeout: 0,
});

// 相当于浏览器中的document.querySelectorAll,但是返回的是 ElementHandle 类型
const articles = await page.$$('.mainContent article a');
// 用于保存一组Promise,方便Promise.all直接处理
const collects: any[] = [];

// 获取文章信息
for (const article of articles) {    
  // evaluate(),对Page上下文进行计算,并返回一个值
  collects.push(await page.evaluate(article => {
    // 这里的代码是放到Page 上下文中执行的,所以在这里是不能访问外部的作用域(也就是Node环境)

    // 获取文章标题节点
    const title = article.querySelector('h4');
    // 获取文章描述节点
    const description = article.querySelector('.blog-list-content') as HTMLDivElement;
    // 获取文章阅读数节点
    const readNum = article.querySelector('.blog-list-footer .view-num');

    // 提取我们需要的文章信息
    return {
      title: title?.innerText, 
      description: description?.innerText, 
      readNum: readNum?.childNodes[0].textContent,
    };
  }, article));
}

// 等待所有数据成功返回
const data = await Promise.all(collects);

// 输出获取的数据到控制台
console.log('[Data]\t', data);

// ...

可以看到控制台输出了我们想要的结果


🎉恭喜,到此为止本篇教程就已结束

获取完整代码 https://github.com/yxlazy/puppeteer-crawl-page-example

总结

现在我们来做个简单的总结,首先我们简单介绍了Puppeteer是什么,它是一个运行在Node环境下的浏览器,为我们提供了丰富的能力,例如为页面截图等,然后在这里我们通过介绍如何抓取页面的数据从而简单的感受了Puppeteer强大的功能之一