前言
Puppeteer 是 Node.js 的一个函数库,可用来操控浏览器,是 Google 的项目,可以应用的范围包括:前端的自动化测试、爬虫、表单提交等。
Selenium vs Puppeteer
之前有过用 Python 配合 Selenium 的经验,不过如果是做爬虫、自动化操作用 Puppeteer 还是非常方便的,安装简单快速,API 也容易使用。美中不足的是它只支持 Chromium 以下是两者的比较,仅供参考:
Puppeteer | Selenium | |
---|---|---|
支持浏览器 | Chromium | 多种浏览器 |
支持语言 | Node.js | 多种语言 |
安装 | 方便快速 | 稍微麻烦 |
使用 | 易上手 | 较难上手 |
安装
由于 Puppeteer 是用 Node.js 写的,所以必须要先安装 Node。
- 可以至官网 下载
- 如果用 mac 可以使用
https://nodejs.org/en/
安装完后可以在 terminal 输入 node -v
检查是否安装成功
然后到要开发的项目路径下输入 npm init -y
初始化项目,接着 npm i puppeteer
,安装的时候会发现它会连同 Chromium 一同安装。
一个简单的例子
新增一个文件 main.js
,并复制以下代码
const puppeteer = require('puppeteer');
接着到终端下输入 `node main.js` 执行。
(async () => {
// 开启 browser
const browser = await puppeteer.launch({
headless: false
});
// 新增分页
const page = await browser.newPage();
// 到自己的博客网站
await page.goto(`https://juejin.cn/user/2576910986784957`);
// 等待订阅按钮出现
await page.waitForSelector("button[class='setting-btn btn']");
// 点击订阅按钮
await page.click("button[class='setting-btn btn']");
})();
接着到终端下输入 node main.js
执行。
解释代码
前面的代码先引入 Puppeteer,以便后续使用,接下来可以看到用 async
以及 () =>
,async
表示函数要用到异步操作,() =>
则是 JS 的箭头函数。
const puppeteer = require('puppeteer');
接下来是用 Puppeteer 打开一个浏览器 ( Chromium ),其中可以看到我们设了参数 headless : false
,如果是设定为 true
,会开启没有界面的无头浏览器,如果设定 false
,就会开一个浏览器窗口。
const browser = await puppeteer.launch({
headless: false
});
在浏览器开一个新的分页。
const page = await browser.newPage();
导向后方指定的网址。
await page.goto(`https://juejin.cn/user/2576910986784957`);
最后这段代码用到了 click
这个方法,它能够帮你点击后面指定的元素,可以看到我是指定 class = setting-btn btn
的 <button>
。
waitForSelector
的作用是,在执行时整个操作速度会很快,有时可能这个元素都很没出现,就让它去点击,有可能会找不到。所以先让它等待指定元素出现后,再去点击。
await page.waitForSelector("button[class='setting-btn btn']");
await page.click("button[class='setting-btn btn']");
把项目容器化
首先下载 Docker,这里就不再赘述。接下来在项目目录下创建 Dockerfile
,把下面的脚本代码复制粘贴。
然后构建镜像:docker build -t puppeteer-bot .
。
构建完成后就执行 docker run -d --name puppeteer-bot-timeline puppeteer-bot:latest
。
之后可以通过 docker logs puppeteer-bot-timeline
查看 console.log 的内容 ( 如果有的话 )。
要记得 headless 要设定为 true 才能运行。
FROM node:11-slim
# 下载 chromium 在 docker 运行时所需组件
RUN apt-get update && apt-get install -yq libgconf-2-4
RUN apt-get update && apt-get install -y wget --no-install-recommends \
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
&& apt-get update \
&& apt-get install -y google-chrome-unstable \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get purge --auto-remove -y curl \
&& rm -rf /src/*.deb
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init
RUN chmod +x /usr/local/bin/dumb-init
USER root
ENV TZ=Asia/Shanghai # 转换时区,非必要
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
COPY . /app/
WORKDIR app
RUN npm install
EXPOSE 8084
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "main.js"]
容器打包时的一些坑
接下来是我在用 Docke 打包过程遇到的一些问题:
1. Puppeteer 安装 Chromium 时会缺少一些组件
本来用 docker 封装是很容易的,安装 node 然后 npm install 就行了,但是在实际操作时一直报错说 Chromium 缺少组件。后来去 Puppeteer 的 issue 上查到原来安装 Puppeteer 时会自动安装 Chromium,但要在 Docker 上运行的相关组件并不会自动下载。
2. 页面焦点问题
在开发时我是通过开启一个浏览器,然后持续开三个分页来进行操作,希望能加快处理的速度。但是发现当 headless:false
时,会同时开启三个分页,但只有被设置为焦点的当前页面在执行后面的脚本,另外两页并没有。因为在开发过程中执行时 tab 页会被关闭,所以接下来第二个 tab 中的页面获得焦点后会再开始运行。
在 issue 中也看到有人遇到了同样的问题,只有在 headless:true
的时候会同时处理,但目前还没找到其他解法。
3. Page Crash 问题
上面有提到我在一个浏览器上操作三个分页,放在 docker 中运行,总是遇到 Page Crash 问题,第一反应是可能内存不足,在 issue 上查到原来在打开浏览器时要加上 --disable-dev-shm-usage
。
原文是这样说的:
By default, Docker runs a container with a /dev/shm shared memory space 64MB. This is typically too small for Chrome and will cause Chrome to crash when rendering large pages. To fix, run the container with docker run –shm-size=1gb to increase the size of /dev/shm. Since Chrome 65, this is no longer necessary. Instead, launch the browser with the –disable-dev-shm-usage flag: const browser = await puppeteer.launch({ args: [‘–disable-dev-shm-usage’] });
4. 时区问题
这个问题与 Puppeteer 无关,有的服务器时区默认是 GMT,这时就要在 Dockerfile 指定容器的时区,不然代码中涉及到时间的操作时会被自动加 8 小时。