Puppeteer⼊门教程
1、Puppeteer 简介
Puppeteer 是⼀个node库,他提供了⼀组⽤来操纵Chrome的API, 通俗来说就是⼀个 headless chrome浏览器 (当然你也可以配置成有UI的,默认是没有的)。既然是浏览器,那么我们⼿⼯可以在浏览器上做的事情 Puppeteer 都能胜任, 另外,Puppeteer 翻译成中⽂是”⽊偶”意思,所以听名字就知道,操纵起来很⽅便,你可以很⽅便的操纵她去实现:
1)⽣成⽹页截图或者 PDF
2)⾼级爬⾍,可以爬取⼤量异步渲染内容的⽹页
3)模拟键盘输⼊、表单⾃动提交、登录⽹页等,实现 UI ⾃动化测试
4)捕获站点的时间线,以便追踪你的⽹站,帮助分析⽹站性能问题
如果你⽤过 PhantomJS 的话,你会发现她们有点类似,但Puppeteer是Chrome官⽅团队进⾏维护的,⽤俗话说就是”有娘家的⼈“,前景更好。
2、运⾏环境
查看 Puppeteer 的官⽅ API 你会发现满屏的 async, await 之类,这些都是 ES7 的规范,所以你需要:
1. Nodejs 的版本不能低于 v7.6.0, 需要⽀持 async, await.
2. 需要最新的 chrome driver, 这个你在通过 npm 安装 Puppeteer 的时候系统会⾃动下载的
npm install puppeteer --save
3、基本⽤法
先开看看官⽅的⼊门的 DEMO
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = wPage();
('example');
nodeselectorawait page.screenshot({path: 'example.png'});
await browser.close();
})();
上⾯这段代码就实现了⽹页截图,先⼤概解读⼀下上⾯⼏⾏代码:
1. 先通过 puppeteer.launch() 创建⼀个浏览器实例 Browser 对象
2. 然后通过 Browser 对象创建页⾯ Page 对象
3. 然后 () 跳转到指定的页⾯
4. 调⽤ page.screenshot() 对页⾯进⾏截图
5. 关闭浏览器
是不是觉得好简单?反正我是觉得⽐ PhantomJS 简单,⾄于跟 selenium-webdriver ⽐起来,那更不⽤说了。下⾯就介绍⼀下 puppeteer 的常⽤的⼏个 API。
3.1 puppeteer.launch(options)
使⽤ puppeteer.launch() 运⾏ puppeteer,它会 return ⼀个 promise,使⽤ then ⽅法获取 browser 实例,当然⾼版本的的 nodejs 已经⽀持await 特性了,所以上⾯的例⼦使⽤ await 关键字,这⼀点需要特殊说明⼀下,Puppeteer ⼏乎所有的操作都是异步的, 为了使⽤⼤量的then 使得代码的可读性降低,本⽂所有 demo 代码都是⽤ async, await ⽅式实现。这个也是 Puppeteer 官⽅推荐的写法。对 async/await ⼀脸懵逼的同学狠狠的
options 参数详解
参数名称参数类型参数说明
ignoreHTTPSErrors boolean在请求的过程中是否忽略 Https 报错信息,默认为 false
headless boolean是否以”⽆头”的模式运⾏ chrome, 也就是不显⽰ UI,默认为 true
executablePath string可执⾏⽂件的路劲,Puppeteer 默认是使⽤它⾃带的 chrome webdriver, 如果你想指定⼀个⾃⼰的
webdriver 路径,可以通过这个参数设置
slowMo number使 Puppeteer 操作减速,单位是毫秒。如果你想看看 Puppeteer 的整个⼯作过程,这个参数将⾮常有
⽤。
slowMo number
⽤。
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可见的环境变量。默认为v。
devtools boolean是否为每个选项卡⾃动打开DevTools⾯板,这个选项只有当 headless 设置为 false 的时候有效
3.2 Browser 对象
当 Puppeteer 连接到⼀个 Chrome 实例的时候就会创建⼀个 Browser 对象,有以下两种⽅式:
Puppeteer.launch 和 t.
下⾯这个 DEMO 实现断开连接之后重新连接浏览器实例
const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
// 保存 Endpoint,这样就可以重新连接 Chromium
const browserWSEndpoint = browser.wsEndpoint();
// 从Chromium 断开连接
browser.disconnect();
// 使⽤endpoint 重新和 Chromiunm 建⽴连接
const browser2 = t({browserWSEndpoint});
// Close Chromium
await browser2.close();
});
Browser 对象 API
⽅法名称返回值说明
browser.close()Promise关闭浏览器
browser.disconnect()void断开浏览器连接
browser.pages()Promise(Array(Page))获取所有打开的 Page 实例
browser.targets()Array(Target)获取所有活动的 targets
browser.version()Promise(String)获取浏览器的版本
browser.wsEndpoint()String返回浏览器实例的 socket 连接 URL, 可以通过这个 URL 重连接 chrome 实例
好了,Puppeteer 的API 就不⼀⼀介绍了,官⽅提供的详细的 API,
4、Puppeteer 实战
了解 API 之后我们就可以来⼀些实战了,在此之前,我们先了解⼀下 Puppeteer 的设计原理,简单来说 Puppeteer 跟 webdriver 以及PhantomJS 最⼤的的不同就是它是站在⽤户浏览的⾓度,⽽ webdriver 和 PhantomJS 最初设计就是⽤来做⾃动化测试的,所以它是站在机器浏览的⾓度来设计的,所以它们使⽤的是不同的设计哲学。举个栗⼦,加⼊我需要打开京东的⾸页并进⾏⼀次产品搜索,分别看看使⽤Puppeteer 和 webdriver 的实现流程:
Puppeteer 的实现流程:
1. 打开京东⾸页
2. 将光标 focus 到搜索输⼊框
3. 键盘点击输⼊⽂字
4. 点击搜索按钮
webdriver 的实现流程:
1. 打开京东⾸页
2. 到输⼊框的 input 元素
3. 设置 input 的值为要搜索⽂字
4. 触发搜索按钮的单机事件
个⼈感觉 Puppeteer 设计哲学更符合任何的操作习惯,更⾃然⼀些。
下⾯我们就⽤⼀个简单的需求实现来进⾏ Puppeteer 的⼊门学习。这个简单的需求就是:
在京东商城抓取10个⼿机商品,并把商品的详情页截图。
⾸先我们来梳理⼀下操作流程
1. 打开京东⾸页
2. 输⼊“⼿机”关键字并搜索
3. 获取前10个商品的 A 标签,并获取 href 属性值,获取商品详情链接
4. 分别打开10个商品的详情页,截取⽹页图⽚
要实现上⾯的功能需要⽤到查元素,获取属性,键盘事件等,那接下来我们就⼀个⼀个的讲解⼀下。
4.1 获取元素
Page 对象提供了2个 API 来获取页⾯元素
(1). Page.$(selector) 获取单个元素,底层是调⽤的是 document.querySelector() , 所以选择器的 selector 格式遵循
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 对象
4.2 获取元素属性
Puppeteer 获取元素属性跟我们平时写前段的js的逻辑有点不⼀样,按照通常的逻辑,应该是现获取元素,然后在获取元素的属性。但是上⾯我们知道获取元素的 API 最终返回的都是 ElemetHandle 对象,⽽你去查看 ElemetHandle 的 API 你会发现,它并没有获取元素属性的API.
事实上 Puppeteer 专门提供了⼀套获取属性的 API, Page.$eval() 和 Page.$$eval()
(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);
4.3 执⾏⾃定义的 JS 脚本
Puppeteer 的 Page 对象提供了⼀系列 evaluate ⽅法,你可以通过他们来执⾏⼀些⾃定义的 js 代码,主要提供了下⾯三个 API
(1). page.evaluate(pageFunction, …args) 返回⼀个可序列化的普通对象,pageFunction 表⽰要在页⾯执⾏的函数, args 表⽰传
⼊给 pageFunction 的参数,下⾯的 pageFunction 和 args 表⽰同样的意思。
const result = await page.evaluate(() => {
solve(8 * 7);
});
console.log(result); // prints "56"
这个⽅法很有⽤,⽐如我们在获取页⾯的截图的时候,默认是只截图当前浏览器窗⼝的尺⼨⼤⼩,默认值是800x600,那如果我们需要获取整个⽹页的完整截图是没办法办到的。Page.screenshot() ⽅法提供了可以设置截图区域⼤⼩的参数,那么我们只要在页⾯加载完了之后获取页⾯的宽度和⾼度就可以解决这个问题了。
(async () => {
const browser = await puppeteer.launch({headless:true});
const page = wPage();
('jr.dayi35');
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();
})();
(2). Page.evaluateHandle(pageFunction, …args) 在 Page 上下⽂执⾏⼀个 pageFunction, 返回 JSHandle 实体
const aWindowHandle = await page.evaluateHandle(() => solve(window));
aWindowHandle; // Handle for the window object.
const aHandle = await page.evaluateHandle('document'); // Handle for the 'document'.
从上⾯的代码可以看出,page.evaluateHandle() ⽅法也是通过 solve ⽅法直接把 Promise 的最终处理结果返回,只不过把最后返回的对象封装成了 JSHandle 对象。本质上跟 evaluate 没有什么区别。
下⾯这段代码实现获取页⾯的动态(包括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();
(3). page.evaluateOnNewDocument(pageFunction, …args), 在⽂档页⾯载⼊前调⽤ pageFunction, 如果页⾯中有 iframe 或者
frame, 则函数调⽤的上下⽂环境将变成⼦页⾯的,即iframe 或者 frame, 由于是在页⾯加载前调⽤,这个函数⼀般是⽤来初始化javascript 环境的,⽐如重置或者初始化⼀些全局变量。
4.poseFunction
除此上⾯三个 API 之外,还有⼀类似的⾮常有⽤的 API,那就是 poseFunction,这个 API ⽤来在页⾯注册全局函数,⾮常有⽤:
因为有时候需要在页⾯处理⼀些操作的时候需要⽤到⼀些函数,虽然可以通过 Page.evaluate() API 在页⾯定义函数,⽐如:
const docSize = await page.evaluate(()=> {
function getPageSize() {
return {
width: document.documentElement.clientWidth,
height : document.body.clientHeight,
}
}
return getPageSize();
});
但是这样的函数不是全局的,需要在每个 evaluate 中去重新定义,⽆法做到代码复⽤,在⼀个就是 nodejs 有很多⼯具包可以很轻松的实现很复杂的功能⽐如要实现 md5 加密函数,这个⽤纯 js 去实现就不太⽅便了,⽽⽤ nodejs 却是⼏⾏代码的事情。
下⾯代码实现给 Page 上下⽂的 window 对象添加 md5 函数:
const puppeteer = require('puppeteer');
const crypto = require('crypto');
puppeteer.launch().then(async browser => {
const page = wPage();
<('console', msg => console.));
poseFunction('md5', text =>
);
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();
});
可以看出,poseFunction API 使⽤起来是很⽅便的,也⾮常有⽤,在⽐如给 window 对象注册 readfile 全局函数:
const puppeteer = require('puppeteer');
const fs = require('fs');
puppeteer.launch().then(async browser => {
const page = wPage();
<('console', msg => console.));
poseFunction('readfile', async filePath => {
return new Promise((resolve, reject) => {
if (err)
reject(err);
else
resolve(text);
});
});
});
await page.evaluate(async () => {
// adfile to read contents of a file
const content = adfile('/etc/hosts');
console.log(content);
});
await browser.close();
});
5、ulate 修改模拟器(客户端)运⾏配置
Puppeteer 提供了⼀些 API 供我们修改浏览器终端的配置
1. Page.setViewport() 修改浏览器视窗⼤⼩
2. Page.setUserAgent() 设置浏览器的 UserAgent 信息
3. ulateMedia() 更改页⾯的CSS媒体类型,⽤于进⾏模拟媒体仿真。可选值为 “screen”, “print”, “null”, 如果设置为 null 则表⽰禁
⽤媒体仿真。
4. ulate() 模拟设备,参数设备对象,⽐如 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');
除此之外我们还可以模拟⾮ PC 机设备, ⽐如下⾯这段代码模拟 iPhone 6 访问google
const puppeteer = require('puppeteer');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone = devices['iPhone 6'];
puppeteer.launch().then(async browser => {
const page = wPage();
ulate(iPhone);
('le');
//
await browser.close();
});
Puppeteer ⽀持很多设备模拟仿真,⽐如Galaxy, iPhone, IPad 等,想要知道详细设备⽀持,请戳这⾥
6、键盘和⿏标
键盘和⿏标的API⽐较简单,键盘的⼏个API如下:
keyboard.down(key[, options]) 触发 keydown 事件
keyboard.press(key[, options]) 按下某个键,key 表⽰键的名称,⽐如 ‘ArrowLeft’ 向左键,详细的键名映射请
keyboard.sendCharacter(char) 输⼊⼀个字符
keyboard.up(key) 触发 keyup 事件
page.keyboard.press("Shift"); //按下 Shift 键
page.keyboard.sendCharacter('嗨');
pe('Hello'); // ⼀次输⼊完成
pe('World', {delay: 100}); // 像⽤户⼀样慢慢输⼊
⿏标操作:
mouse.click(x, y, [options]) 移动⿏标指针到指定的位置,然后按下⿏标,这个其实 ve 和 mouse.down 或 mouse.up 的快捷操作
mouse.down([options]) 触发 mousedown 事件,options 可配置:
options.button 按下了哪个键,可选值为[left, right, middle], 默认是 left, 表⽰⿏标左键
options.clickCount 按下的次数,单击,双击或者其他次数
delay 按键延时时间
mouse.up([options]) 触发 mouseup 事件
7、另外⼏个有⽤的 API
Puppeteer 还提供⼏个⾮常有⽤的 API,⽐如:
7.1 Page.waitFor 系列 API
page.waitFor(selectorOrFunctionOrTimeout[, options[, …args]]) 下⾯三个的综合 API
page.waitForFunction(pageFunction[, options[, …args]]) 等待 pageFunction 执⾏完成之后
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论