Puppeteer学习笔记(一)
Puppeteer学习笔记(一)
概述
Puppeteer 是一个完整的 Chrome 自动化解决方案。使用 Puppeteer 的主要优势在于它可以访问 DevTools 协议和控制 Chrome。由于 Puppeteer 是一个 Node 库,因此可以使用 npm 或 Yarn 轻松安装。
官网:https://zhaoqize.github.io/puppeteer-api-zh_CN/#/
Puppeteer 运行速度极快,而 Selenium 需要 WebDriver 将脚本命令发送到浏览器。
Puppeteer 提供了重要的性能管理功能,例如记录运行时和负载性能、捕获屏幕截图,甚至限制 CPU 性能以模拟移动设备上的性能。
在浏览器中手动完成的大多数事情都可以通过使用 Puppeteer 完成,下面是一些入门的例子:
- 生成屏幕截图和 PDF 页面
- 检索 SPA 并生成预渲染内容(即 “SSR”)
- 从网站上爬取内容
- 自动提交表单,UI 测试,键盘输入等
- 创建一个最新的自动测试环境。使用最新的 JavaScript 和浏览器功能,在最新版本的 Chrome 中直接运行测试
- 捕获网站的时间线跟踪,以帮助诊断性能问题
Puppteer 和puppteer-core的区别
-
puppeteer-core 在安装时不
会自动下载 Chromium。 -
puppeteer-core忽略所有的 PUPPETEER_* env 变量.
安装
需提前安装node.js 和npm。
安装命令:
npm i puppeteer
使用示例
示例1:
const puppeteer = require('puppeteer');
(async () => {
//默认是无头模式,options参数headless设置为false则是有头
const browser = await puppeteer.launch({headless:false});
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
示例2:
const puppeteer = require('puppeteer');
const run = async () => {
const browser = await puppeteer.launch({ headless: false, defaultViewport: { width: 1366, height: 768 } });
const page = await browser.newPage();
await page.goto('https://www.baidu.com', { waitUntil: 'domcontentloaded' });
//如果语句之前加一句 await page.waitFor("#kw") :它指定了程序将等待,直至等到该元素被渲染至页面上再继续执行接下来的操作
const input_baiduyixia = await page.$("#kw");
await input_baiduyixia.type("puppeteer");
const btn_baiduyixia = await page.$("#su");
btn_baiduyixia.click();
}
run();
puppteer 中更多常用options 参数详解:
参数名称 | 参数类型 | 参数说明 |
---|---|---|
ignoreHTTPSErrors | boolean | 在请求的过程中是否忽略 Https 报错信息,默认为 false |
headless | boolean | 是否以”无头”的模式运行 chrome, 也就是不显示 UI, 默认为 true |
executablePath | string | 可执行文件的路劲,Puppeteer 默认是使用它自带的 chrome webdriver, 如果你想指定一个自己的 webdriver 路径,可以通过这个参数设置 |
slowMo | number | 使 Puppeteer 操作减速,单位是毫秒。如果你想看看 Puppeteer 的整个工作过程,这个参数将非常有用。 |
args | Array(String) | 传递给 chrome 实例的其他参数,比如你可以使用”–ash-host-window-bounds=1024x768” 来设置浏览器窗口大小。更多参数参数列表可以参考这里 |
handleSIGINT | boolean | 是否允许通过进程信号控制 chrome 进程,也就是说是否可以使用 CTRL+C 关闭并退出浏览器. |
timeout | number | 等待 Chrome 实例启动的最长时间。默认为30000(30秒)。如果传入 0 的话则不限制时间 |
dumpio | boolean | 是否将浏览器进程stdout和stderr导入到process.stdout和process.stderr中。默认为false。 |
userDataDir | string | 设置用户数据目录,默认linux 是在 ~/.config 目录,window 默认在 C:\Users{USER}\AppData\Local\Google\Chrome\User Data, 其中 {USER} 代表当前登录的用户名 |
env | Object | 指定对Chromium可见的环境变量。默认为process.env。 |
devtools | boolean | 是否为每个选项卡自动打开DevTools面板, 这个选项只有当 headless 设置为 false 的时候有效 |
常用API
官方提供的详细的 API
官网:https://zhaoqize.github.io/puppeteer-api-zh_CN/#/
Browser 对象 API
方法名称 | 返回值 | 说明 |
---|---|---|
browser.close() | Promise | 关闭浏览器 |
browser.disconnect() | void | 断开浏览器连接 |
browser.newPage() | Promise(Page) | 创建一个 Page 实例 |
browser.pages() | Promise(Array(Page)) | 获取所有打开的 Page 实例 |
browser.targets() | Array(Target) | 获取所有活动的 targets |
browser.version() | Promise(String) | 获取浏览器的版本 |
browser.wsEndpoint() | String | 返回浏览器实例的 socket 连接 URL, 可以通过这个 URL 重连接 chrome 实例 |
获取元素
Page 对象提供了2个 API 来获取页面元素
(1). Page.$(selector) 获取单个元素,底层是调用的是 document.querySelector() , 所以选择器的 selector 格式遵循 css 选择器规范
let inputElement = await page.$("#search", input => input);
//下面写法等价
let inputElement = await page.$('#search');
(2). Page.$$(selector) 获取一组元素,底层调用的是 document.querySelectorAll(). 返回 Promise(Array(ElemetHandle)) 元素数组.
const links = await page.$$("a");
//下面写法等价
const links = await page.$$("a", links => links);
最终返回的都是 ElemetHandle 对象
获取元素属性
(1). Page.$$eval(selector, pageFunction[, …args]), 获取单个元素的属性,这里的选择器 selector 跟上面 Page.$(selector) 是一样的。
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 脚本
Puppeteer 的 Page 对象提供了一系列 evaluate 方法,你可以通过他们来执行一些自定义的 js 代码,主要提供了下面三个 API
(1). page.evaluate(pageFunction, …args) 返回一个可序列化的普通对象,pageFunction 表示要在页面执行的函数, args 表示传入给 pageFunction 的参数, 下面的 pageFunction 和 args 表示同样的意思。
const result = await page.evaluate(() => {
return Promise.resolve(8 * 7);
});
console.log(result); // prints "56"
这个方法很有用,比如我们在获取页面的截图的时候,默认是只截图当前浏览器窗口的尺寸大小,默认值是800x600,那如果我们需要获取整个网页的完整 截图是没办法办到的。Page.screenshot() 方法提供了可以设置截图区域大小的参数,那么我们只要在页面加载完了之后获取页面的宽度和高度就可以解决 这个问题了。
(async () => {
const browser = await puppeteer.launch({headless:true});
const page = await browser.newPage();
await page.goto('https://jr.dayi35.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.exposeFunction
这个方法可以向网页中注入自定义函数,解决了evaluate 传递的数据只能是基本类型。注意函数命名的问题,不要和当前环境下的命名冲突,不然执行时会出现函数名未定义的情况。
const puppeteer = require('puppeteer');
const crypto = require('crypto');
(async () => {
const browser = await puppeteer.launch();
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();
})();
page.on
这个方法可以用来监听网页的事件,比较常用的是来监听请求的。
this.page.on('response', (res) => {
const url = res.url();
const resourceType = res.request().resourceType();
});
page.type
这个方法用来给输入框赋值的,不同于直接给value,type会模拟键盘的输入。
用value赋值会出现change方法不会触发的问题,在提交表单的时候可能就丢失数据了。
await page.type('#mytextarea', 'Hello');
// Types instantly
await page.type('#mytextarea', 'World', { delay: 100 });
截屏
await page.screenshot({path: 'screenshot.png'});
Page.emulate 修改模拟器(客户端)运行配置
Puppeteer 提供了一些 API 供我们修改浏览器终端的配置
- Page.setViewport() 修改浏览器视窗大小
- Page.setUserAgent() 设置浏览器的 UserAgent 信息
- Page.emulateMedia() 更改页面的CSS媒体类型,用于进行模拟媒体仿真。 可选值为 “screen”, “print”, “null”, 如果设置为 null 则表示禁用媒体仿真。
- Page.emulate() 模拟设备,参数设备对象,比如 iPhone, Mac, Android 等
键盘和鼠标
键盘和鼠标的API比较简单,键盘的几个API如下:
- 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.click(x, y, [options]) 移动鼠标指针到指定的位置,然后按下鼠标,这个其实 mouse.move 和 mouse.down 或 mouse.up 的快捷操作
- mouse.down([options]) 触发 mousedown 事件,options 可配置:
- options.button 按下了哪个键,可选值为[left, right, middle], 默认是 left, 表示鼠标左键
- options.clickCount 按下的次数,单击,双击或者其他次数
- delay 按键延时时间
- mouse.move(x, y, [options]) 移动鼠标到指定位置, options.steps 表示移动的步长
- mouse.up([options]) 触发 mouseup 事件
Page.waitFor 系列 API
- page.waitFor(selectorOrFunctionOrTimeout[, options[, …args]]) 下面三个的综合 API
- page.waitForFunction(pageFunction[, options[, …args]]) 等待 pageFunction 执行完成之后
- page.waitForNavigation(options) 等待页面基本元素加载完之后,比如同步的 HTML, CSS, JS 等代码
- page.waitForSelector(selector[, options]) 等待某个选择器的元素加载之后,这个元素可以是异步加载的,这个 API 非常有用,你懂的。
比如我想获取某个通过 js 异步加载的元素,那么直接获取肯定是获取不到的。这个时候就可以使用 page.waitForSelector 来解决:
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
}
});
});
其实上面的代码就可以解决我们最上面的需求,抓取京东的产品,因为是异步加载的,所以使用这种方式。
age.getMetrics()
通过 page.getMetrics() 可以得到一些页面性能数据, 捕获网站的时间线跟踪,以帮助诊断性能问题。
- Timestamp 度量标准采样的时间戳
- Documents 页面文档数
- Frames 页面 frame 数
- JSEventListeners 页面内事件监听器数
- Nodes 页面 DOM 节点数
- LayoutCount 页面布局总数
- RecalcStyleCount 样式重算数
- LayoutDuration 所有页面布局的合并持续时间
- RecalcStyleDuration 所有页面样式重新计算的组合持续时间。
- ScriptDuration 所有脚本执行的持续时间
- TaskDuration 所有浏览器任务时长
- JSHeapUsedSize JavaScript 占用堆大小
- JSHeapTotalSize JavaScript 堆总量
总结
总的来说,Puppeteer 真是一款不错的运行速度像火箭一样快的headless 工具,操作简单,功能强大。用来做UI自动化测试,和一些小工具都是很不错的。
需求:
- 打开京东首页
- 输入“手机”关键字并搜索
- 获取前10个商品的 A 标签,并获取 href 属性值,获取商品详情链接
- 分别打开10个商品的详情页,截取网页图片
代码示例:
//延时函数
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();
});
参考:
https://www.w3cschool.cn/puppeteer/puppeteer-jyc537sa.html
Puppeteer 入门教程 http://t.csdn.cn/dak7Z
https://zhuanlan.zhihu.com/p/516095953
自动化神器Puppeteer使用教程 http://t.csdn.cn/jIOiQ