简介
Puppeteer
是一个node
库,它提供了一个高级 API
来通过 DevTools
协议控制 Chromium
或 Chrome
我们手工可以在浏览器上做的事情 Puppeteer
都能胜任
Puppeteer
默认以 headless
模式运行,但是可以通过修改配置文件运行“有头”模式
主要应用
- 生成网页截图或者
- 高级爬虫,可以爬取大量异步渲染内容的网页
- 模拟键盘输入、表单自动提交、登录网页等,实现
UI
自动化测试- 捕获站点的时间线,以便追踪你的网站,帮助分析网站性能问题
Nodejs
的版本不能低于 v7.6.0
, 需要支持 async
, await
.
需要最新的 chrome driver
, 这个你在通过 npm
安装 Puppeteer
的时候系统会自动下载的
基本用法
- 下载
puppeteer
:npm 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
:所有浏览器任务时长JSHeapUsedSize
:JavaScript
占用堆大小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.move
和mouse.down
或mouse.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();
});
备注:
puppeteer
的API
比较多。用的时候直接查看文档即可。官方文档API
部分基本上自Puppeteer 入门教程上面搬运过来的
发表评论