Node.js - Puppeteer基本使用笔记

简介

Puppeteer 是一个node库,它提供了一个高级 API 来通过 DevTools 协议控制 ChromiumChrome
我们手工可以在浏览器上做的事情 Puppeteer 都能胜任

Puppeteer 默认以 headless 模式运行,但是可以通过修改配置文件运行“有头”模式

主要应用

  • 生成网页截图或者 PDF
  • 高级爬虫,可以爬取大量异步渲染内容的网页
  • 模拟键盘输入、表单自动提交、登录网页等,实现 UI 自动化测试
  • 捕获站点的时间线,以便追踪你的网站,帮助分析网站性能问题

Nodejs 的版本不能低于 v7.6.0, 需要支持 async, await.
需要最新的 chrome driver, 这个你在通过 npm 安装 Puppeteer 的时候系统会自动下载的

基本用法

  • 下载puppeteernpm i puppeteer --save
const puppeteer = require('puppeteer'); (async () => { // 通过 puppeteer.launch() 创建一个浏览器实例 Browser 对象 const browser = await puppeteer.launch(); // 通过 Browser 对象创建页面 Page 对象 const page = await browser.newPage(); // 跳转到指定的页面 await page.goto('https://example.com'); // 对页面进行截图 await page.screenshot({path: 'example.png'}); // 关闭浏览器 await browser.close(); })();

比较有用的API

Page

获取元素

  • page.$(selector):获取单个元素,底层是调用的是 document.querySelector() , 所以选择器的 selector 格式遵循 css 选择器规范
let inputElement = await page.$("#search", input => input); //下面写法等价 let inputElement = await page.$('#search');
  • page.$$(selector):获取一组元素,底层调用的是 document.querySelectorAll(). 返回 Promise(Array(ElemetHandle)) 元素数组.
const links = await page.$$("a"); //下面写法等价 const links = await page.$$("a", links => links);

获取元素属性

  • page.$eval(selector, pageFunction[, ...args])page.$$eval(selector, pageFunction[, ...args]): 获取单个元素的属性。
const value = await page.$eval('input[name=search]', input => input.value); const href = await page.$eval('#a', ele => ele.href); const content = await page.$eval('.content', ele => ele.outerHTML);

执行自定义的 JS 脚本

  • page.evaluate(pageFunction[, ...args]): 返回一个可序列化的普通对象,pageFunction 表示要在页面执行的函数, args 表示传入给 pageFunction 的参数
const result = await page.evaluate(() => { return Promise.resolve(8 * 7); }); console.log(result); // prints "56"
(async () => { const browser = await puppeteer.launch({headless:true}); const page = await browser.newPage(); await page.goto('https://www.xxxx.com'); await page.setViewport({width:1920, height:1080}); // 这里我们获取到页面的宽度和高度 const documentSize = await page.evaluate(() => { return { width: document.documentElement.clientWidth, height : document.body.clientHeight, } }) // 截屏的时候只截取当前浏览器窗口的尺寸大小 await page.screenshot({path:"example.png", clip : {x:0, y:0, width:1920, height:documentSize.height}}); await browser.close(); })();
  • page.evaluateHandle(pageFunction[, ...args]): 在 Page 上下文执行一个 pageFunction, 返回 JSHandle 实体
// Handle for the window object const aWindowHandle = await page.evaluateHandle(() => Promise.resolve(window)); // page.evaluateHandle() 方法也是通过 Promise.resolve 方法直接把 Promise 的最终处理结果返回, // 只不过把最后返回的对象封装成了 JSHandle 对象 // Handle for the 'document' const aHandle = await page.evaluateHandle('document'); // 实现获取页面的动态(包括js动态插入的元素) HTML 代码 const aHandle = await page.evaluateHandle(() => document.body); const resultHandle = await page.evaluateHandle(body => body.innerHTML, aHandle); console.log(await resultHandle.jsonValue()); await resultHandle.dispose();
  • page.evaluateOnNewDocument(pageFunction[, ...args]): 在文档页面载入前调用 pageFunction, 如果页面中有 iframe 或者 frame, 则函数调用 的上下文环境将变成子页面的,即iframe 或者 frame, 由于是在页面加载前调用,这个函数一般是用来初始化 javascript 环境的,比如重置或者 初始化一些全局变量。

  • page.exposeFunction(name, puppeteerFunction):用来在页面注册全局函数

// 给 Page 上下文的 window 对象添加 md5 函数 const puppeteer = require('puppeteer'); const crypto = require('crypto'); puppeteer.launch().then(async browser => { const page = await browser.newPage(); page.on('console', msg => console.log(msg.text)); await page.exposeFunction('md5', text => crypto.createHash('md5').update(text).digest('hex') ); await page.evaluate(async () => { // use window.md5 to compute hashes const myString = 'PUPPETEER'; const myHash = await window.md5(myString); console.log(`md5 of ${myString} is ${myHash}`); }); await browser.close(); });
// 给 window 对象注册 readfile 全局函数 const puppeteer = require('puppeteer'); const fs = require('fs'); puppeteer.launch().then(async browser => { const page = await browser.newPage(); page.on('console', msg => console.log(msg.text)); await page.exposeFunction('readfile', async filePath => { return new Promise((resolve, reject) => { fs.readFile(filePath, 'utf8', (err, text) => { if (err) reject(err); else resolve(text); }); }); }); await page.evaluate(async () => { // use window.readfile to read contents of a file const content = await window.readfile('/etc/hosts'); console.log(content); }); await browser.close(); });
  • page.setViewport(): 修改浏览器视窗大小

  • page.setUserAgent(): 设置浏览器的 UserAgent 信息

  • page.emulateMedia(): 更改页面的CSS媒体类型,用于进行模拟媒体仿真。 可选值为 “screen”, “print”, “null”, 如果设置为 null 则表示禁用媒体仿真。

  • page.emulate(): 模拟设备,参数设备对象,比如 iPhone, Mac, Android

page.setViewport({width:1920, height:1080}); //设置视窗大小为 1920x1080 page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36'); page.emulateMedia('print'); //设置打印机媒体样式
  • puppeteer/DeviceDescriptors:设备模拟仿真。支持很多设备模拟仿真,比如Galaxy, iPhone, IPad
// 模拟 iPhone 6 访问google const puppeteer = require('puppeteer'); const devices = require('puppeteer/DeviceDescriptors'); const iPhone = devices['iPhone 6']; puppeteer.launch().then(async browser => { const page = await browser.newPage(); await page.emulate(iPhone); await page.goto('https://www.google.com'); // other actions... await browser.close(); });
  • page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]): 下面三个的综合 API

  • page.waitForFunction(pageFunction[, options[, ...args]]): 等待 pageFunction 执行完成之后

  • page.waitForNavigation(options): 等待页面基本元素加载完之后,比如同步的 HTML, CSS, JS 等代码

  • page.waitForSelector(selector[, options]): 等待某个选择器的元素加载之后,这个元素可以是异步加载的。

// 获取某个通过 js 异步加载的元素 // 等待元素加载之后,否则获取不到异步加载的元素 await page.waitForSelector('.gl-item'); const links = await page.$$eval('.gl-item > .gl-i-wrap > .p-img > a', links => { return links.map(a => { return { href: a.href.trim(), name: a.title } }); });
  • page.getMetrics():得到一些页面性能数据, 捕获网站的时间线跟踪,以帮助诊断性能问题
  • Timestamp:度量标准采样的时间戳
  • Documents:页面文档数
  • Frames:页面 frame
  • JSEventListeners:页面内事件监听器数
  • Nodes:页面 DOM 节点数
  • LayoutCount:页面布局总数
  • RecalcStyleCount:样式重算数
  • LayoutDuration:所有页面布局的合并持续时间
  • RecalcStyleDuration:所有页面样式重新计算的组合持续时间。
  • ScriptDuration:所有脚本执行的持续时间
  • TaskDuration:所有浏览器任务时长
  • JSHeapUsedSizeJavaScript 占用堆大小
  • JSHeapTotalSize: JavaScript 堆总量

keyboard

  • keyboard.down(key[, options]): 触发 keydown 事件

  • keyboard.press(key[, options]): 按下某个键,key 表示键的名称,比如 ‘ArrowLeft’ 向左键,详细的键名映射请戳这里

  • keyboard.sendCharacter(char): 输入一个字符

  • keyboard.type(text, options): 输入一个字符串

  • keyboard.up(key): 触发 keyup 事件

page.keyboard.press("Shift"); //按下 Shift 键 page.keyboard.sendCharacter('嗨'); page.keyboard.type('Hello'); // 一次输入完成 page.keyboard.type('World', {delay: 100}); // 像用户一样慢慢输入

mouse

  • mouse.click(x, y, [options]): 移动鼠标指针到指定的位置,然后按下鼠标,这个其实 mouse.movemouse.downmouse.up 的快捷操作

  • mouse.down([options]): 触发 mousedown 事件,options 可配置:

  • mouse.move(x, y, [options]): 移动鼠标到指定位置, options.steps 表示移动的步长

  • mouse.up([options]): 触发 mouseup 事件

demo

//延时函数 function sleep(delay) { return new Promise((resolve, reject) => { setTimeout(() => { try { resolve(1) } catch (e) { reject(0) } }, delay) }) } const puppeteer = require('puppeteer'); puppeteer.launch({ ignoreHTTPSErrors:true, headless:false,slowMo:250, timeout:0}).then(async browser => { let page = await browser.newPage(); await page.setJavaScriptEnabled(true); await page.goto("https://www.jd.com/"); const searchInput = await page.$("#key"); await searchInput.focus(); //定位到搜索框 await page.keyboard.type("手机"); const searchBtn = await page.$(".button"); await searchBtn.click(); await page.waitForSelector('.gl-item'); //等待元素加载之后,否则获取不异步加载的元素 const links = await page.$$eval('.gl-item > .gl-i-wrap > .p-img > a', links => { return links.map(a => { return { href: a.href.trim(), title: a.title } }); }); page.close(); const aTags = links.splice(0, 10); for (var i = 1; i < aTags.length; i++) { page = await browser.newPage() page.setJavaScriptEnabled(true); await page.setViewport({width:1920, height:1080}); var a = aTags[i]; await page.goto(a.href, {timeout:0}); //防止页面太长,加载超时 //注入代码,慢慢把滚动条滑到最底部,保证所有的元素被全部加载 let scrollEnable = true; let scrollStep = 500; //每次滚动的步长 while (scrollEnable) { scrollEnable = await page.evaluate((scrollStep) => { let scrollTop = document.scrollingElement.scrollTop; document.scrollingElement.scrollTop = scrollTop + scrollStep; return document.body.clientHeight > scrollTop + 1080 ? true : false }, scrollStep); await sleep(100); } await page.waitForSelector("#footer-2014", {timeout:0}); //判断是否到达底部了 let filename = "images/items-"+i+".png"; //这里有个Puppeteer的bug一直没有解决,发现截图的高度最大只能是16384px, 超出部分被截掉了。 await page.screenshot({path:filename, fullPage:true}); page.close(); } browser.close(); });

备注:


创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: