按:

转了一圈,发现目前搭配 puppeteer-record 是最省心的。那就重新收拾一下。2020-8-11

初次更新 2019-11-15
Puppeteer Recorder 近期用过 puppeteer 可以记录操作

再次更新 2020-12-23:

看了文章 《效率提高十倍,Puppeteer 如何启动交互模式?》,说是 node.js v14 可以 node -i 进入交互环境,然后再chrome的控制台找到链接入口,选择 connect

image.png

然后可以进行交互环境了,可以写一行看一行代码了,这挺方便的。

image.png

  1. import puppeteer from 'puppeteer-core'
  2. const getDefaultOsPath = () => {
  3. if (process.platform === 'win32') {
  4. return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
  5. } else {
  6. return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
  7. }
  8. }
  9. let browser = await puppeteer.launch({
  10. executablePath: getDefaultOsPath()
  11. }))

相关资源

文档和工具:

辅助阅读材料:

前置概念

Puppeteer 是一个 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome。Puppeteer 默认以 headless 模式运行,但是可以通过修改配置文件运行“有头”模式。

  • 生成页面 PDF。
  • 抓取 SPA(单页应用)并生成预渲染内容(即“SSR”(服务器端渲染))。
  • 自动提交表单,进行 UI 测试,键盘输入等。
  • 创建一个时时更新的自动化测试环境。 使用最新的 JavaScript 和浏览器功能直接在最新版本的Chrome中执行测试。
  • 捕获网站的 timeline trace,用来帮助分析性能问题。
  • 测试浏览器扩展。

下载会失败 。在安装 puppeteer的时候会自动下载一个 Chromium,但网址被墙没办法下载,需要先安装中国镜像源,具体见下方。后来推出了 puppeteer-core 包,不在捆绑下载浏览器,可以使用电脑自带的chrome,这样只要保证本机的chrome版本不要太低就可以。

安装

刚才提到需要配置国内源,补充:核心是修改 项目中 .npmrc puppeteer-download-host=https://npm.taobao.org/mirrors

使用 cnpm

  1. # npm i -g mirror-config-china --registry=https://registry.npm.taobao.org
  2. # 检查是否安装成功
  3. npm config list
  4. npm i puppeteer
  5. # puppeteer-download-host=https://npm.taobao.org/mirrors
  6. # 如果始终安装失败,使用 cnpm i
  7. npm i puppeteer
  8. cnpm i puppeteer

也可以考虑 puppeteer-core ,这样没有自带浏览器,那就需要在项目启动的时候设置chrome路径:

  1. npm i puppeteer-core

代码如下:

  1. const getDefaultOsPath = () => {
  2. if (process.platform === 'win32') {
  3. return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
  4. } else {
  5. return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
  6. }
  7. }
  8. const getDefaultOsPath = require('./getDefalutOsPath')
  9. module.exports = {
  10. executablePath: getDefaultOsPath(),
  11. userDataDir: 'test-profile-dir',
  12. headless: false,
  13. defaultViewport: {
  14. width: 1600,
  15. height: 900
  16. },
  17. args: [
  18. '–disable-gpu',
  19. '–disable-dev-shm-usage',
  20. '–disable-setuid-sandbox',
  21. '–no-first-run',
  22. '–no-sandbox',
  23. '–no-zygote',
  24. '–single-process'
  25. ]
  26. }

跑一个demo

创建个 demo1.js 来试跑一下。

  1. // demo1.js
  2. const puppeteer = require('puppeteer-core');
  3. (async () => {
  4. const browser = await puppeteer.launch(launchOptions);
  5. const page = await browser.newPage();
  6. await page.goto("http://www.baidu.com");
  7. await page.screenshot({ path: "baidu.png" });
  8. browser.close();
  9. })();

bash页面执行:

  1. node demo1.js

如果没有报错的话,就是打开了浏览器,并截图。这样就安装成功了。
代码文件命名为 demo1.js 可以在目录里查找。

如果下载有问题,就可以考虑使用本地的chrome,参考上面。

这样就掌握基本的用法了。

经验技巧

recored插件

利用 puppeteer-record 插件,可以快速生成一些基础代码,必备。

sleep函数

  1. // 延时器
  2. let timeout = function (delay) {
  3. console.log('延迟函数:', `延迟 ${delay} 毫秒`)
  4. return new Promise((resolve, reject) => {
  5. setTimeout(() => {
  6. try {
  7. resolve(1)
  8. } catch (error) {
  9. reject(error)
  10. }
  11. }, delay);
  12. })
  13. }

Linux下缺少依赖

有可能在Linux下因为缺少依赖,安装不上,考虑安装下面的依赖文件:

  1. gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget

Linux 下中文不支持

考虑安装以下依赖,来支持中文,避免乱码:

  1. sudo apt-get install -y --force-yes --no-install-recommends fonts-wqy-microhei ttf-wqy-zenhei

模拟手机

模拟手机打开页面

  1. const puppeteer = require("puppeteer");
  2. const devices = require("puppeteer/DeviceDescriptors");
  3. const iPhone6 = devices["iPhone 6"];
  4. const browser = await puppeteer.launch();
  5. const page = await browser.newPage();
  6. await page.emulate(iPhone6);
  7. await page.goto("http://baidu.com");
  1. await page.setViewport({ width: 1280, height: 800 });

拦截请求

不想加载图片和js,想让页面打开更快。可以考虑拦截

  1. // 页面设置拦截
  2. await page.setRequestInterception(true);
  3. // 过滤拦截
  4. page.on("request", interceptedRequest => {
  5. // 符合匹配就 abort ,其他 continue
  6. // 这里是操作的 url 字符串
  7. if (
  8. interceptedRequest.url().endsWith(".png") ||
  9. interceptedRequest.url().endsWith(".jpg") ||
  10. interceptedRequest.url().endsWith(".js") ||
  11. ["image", "script"].indexOf(interceptedRequest.resourceType()) !== -1
  12. ) {
  13. interceptedRequest.abort();
  14. } else {
  15. interceptedRequest.continue();
  16. }
  17. await page.goto("http://www.wangxiao.cn/cfe/dt_bm/");
  18. //demo2
  19. const browser = await puppeteer.launch();
  20. const page = await browser.newPage();
  21. const resList = []
  22. await page.setRequestInterception(true);
  23. page.on('request', request => {
  24. if (request.resourceType() === 'image')
  25. // request.abort();
  26. resList.push(request.url())
  27. request.continue();
  28. // else
  29. // request.continue();
  30. });
  31. await page.goto('https://news.google.com/news/');
  32. // await page.screenshot({ path: 'news.png', fullPage: true });
  33. await browser.close();

获取页面源代码

  1. await page.goto("https://www.youtube.com", { waitUntil: "networkidle0" });
  2. const html = await page.content();

屏幕滚动

解决懒加载和数据滚动加载,这就是我认为 puppeteer 最有魅力的地方了。
以往只能去慢慢分析页面结构、研究接口api ,来获取异步加载的数据,现在直接无脑模拟浏览器,滚动页面来获取页面内容。

  1. await page.goto("https://www.jd.com/", {
  2. waitUntil: "networkidle2"
  3. });
  4. await autoScroll(page);
  5. async function autoScroll(page) {
  6. await page.evaluate(async () => {
  7. await newPromise((resolve, reject) => {
  8. var totalHeight = 0;
  9. var distance = 200;
  10. var timer = setInterval(() => {
  11. var scrollHeight = document.body.scrollHeight;
  12. window.scrollBy(0, distance);
  13. totalHeight += distance;
  14. if (totalHeight >= scrollHeight) {
  15. clearInterval(timer);
  16. resolve();
  17. }
  18. }, 200);
  19. });
  20. });
  21. }

操作页面

在浏览器中使用自己的js 这里好像没办法传入自己的function

  1. const result = await page.$eval("#hotwords", el => {
  2. return el.innerText;
  3. });
  4. // 或者
  5. const result = await page.evaluate(() => {
  6. let element = document.querySelector("#hotwords");
  7. return element.innerText;
  8. });

键盘方法

  1. await page.type("#search input", "macbook pro", {
  2. delay: 300
  3. });
  4. await page.keyboard.press("Enter");
  5. // 或者
  6. const inputEle = await page.$("#search input");
  7. await inputEle.type("macbook pro", {
  8. delay: 300
  9. });

键盘大写、快捷键

  1. await page.focus("input");
  2. await page.keyboard.down("Shift");
  3. await page.keyboard.press("KeyM");
  4. await page.keyboard.up("Shift");

等待节点

想等到某个元素出现

  1. await page.waitForSelector('div.title > a'); //等待节点出现

launch 选项

headless: false
slowMo: 200 减速显示,有时候为了模拟人会特意减速
devtools: true 显示dev工具

页面中的iframe

如果网页内有用iframe等标签,这时page对象是无法读取 iframe 里面的内容的,需要用到page.frames()。返回一个Frame对象数组。 通常iframe会有name属性,判断name属性可以快速获取单个Frame对象的内容。

  1. let iframe = await page.frames();
  2. iframe.find(f => f.name() === 'name')

监听 console的输出

想看页面里的console,可以监听

  1. page.on('console',msg => console.log(msg.text());
  2. await page.evaluate(() =>{
  3. console.log()
  4. })

使用 coverage trace

可以使用 chrome自带的分析工具来分析页面

  1. const puppeteer = require("puppeteer");
  2. puppeteer.launch().then(async browser => {
  3. const page = await browser.newPage();
  4. //start coverage trace
  5. await Promise.all([
  6. page.coverage.startJSCoverage(),
  7. page.coverage.startCSSCoverage()
  8. ]);
  9. await page.goto("https://www.cnn.com");
  10. //stop coverage trace
  11. const [jsCoverage, cssCoverage] = await Promise.all([
  12. page.coverage.stopJSCoverage(),
  13. page.coverage.stopCSSCoverage()
  14. ]);
  15. let totalBytes = 0;
  16. let usedBytes = 0;
  17. const coverage = [...jsCoverage, ...cssCoverage];
  18. for (const entry of coverage) {
  19. totalBytes += entry.text.length;
  20. for (const range of entry.ranges)
  21. usedBytes += range.end - range.start - 1;
  22. }
  23. const usedCode = ((usedBytes / totalBytes) * 100).toFixed(2);
  24. console.log("Code used by only", usedCode, "%");
  25. await browser.close();
  26. })
  27. }
  1. const puppeteer = require("puppeteer");
  2. const devices = require("puppeteer/DeviceDescriptors");
  3. const iPhonex = devices["iPhone X"];
  4. (async () => {
  5. const browser = await puppeteer.launch();
  6. const page = await browser.newPage();
  7. await page.emulate(iPhonex);
  8. //start the tracing
  9. await page.tracing.start({ path: "trace.json", screenshots: true });
  10. await page.goto("https://www.bmw.com");
  11. //stop the tracing
  12. await page.tracing.stop();
  13. await browser.close();
  14. })();

简言之,在goto页面之前,打开特定的功能

监控页面

监控页面。写一个app.js setInerval 每隔5分钟运行一次,截图页面,如果有问题可以存下来,再说其他task
也可以使用 node-cron 来制作定时任务

关于新开页面

如果a链接,新开了页面。
如果a标签target属性为_blank,新开了页面,可以使用let pages = await browser.pages(),它返回当前的页面的page类数组集合,想要哪个页面数组中拿就行,不过原来的page还是一直指向原来的页面。

wsl 使用

Hi! Here is how I got it running under WSL:
install chromium browser through apt-get
$ sudo apt-get install chromium-browser
then:
const browser = await puppeteer.launch({executablePath:’/usr/bin/chromium-browser’});