puppeteer初识
puppeteer是一个node库,他能提供一系列操作Chrome的API,通过这些API我们可以用程序代码来操纵Chrome去完成各种操作。
他运行在node环境,由Chorme官方团队在维护,既然是操作浏览器,那么我们可以手动进行的操作在pupeteer上都能进行。
另外pupeteer的英文就是木偶的意思,我们使用他就像操作木偶玩耍一样,可以轻松的做到:
- 生成页面 PDF。
- 抓取页面并生成预渲染内容。
- 自动提交表单,进行 UI 测试,键盘输入等。
- 创建一个时时更新的自动化测试环境。 使用最新的 JavaScript 和浏览器功能直接在最新版本的Chrome中执行测试。
- 捕获网站的 timeline trace,用来帮助分析性能问题。
- 测试浏览器扩展。
- ...... 当然puppeteer不止可以做这些,通过在代码中注入js脚本,我们几乎可以做到所有我们可以用代码实现的功能。
在puppeteer中,Chrome默认运行在"无头"模式,而所谓的无头模式,不过是不加载浏览器的UI界面,实际上并不会影响我们的操作。
使用无头模式,在无界面的情况下按照我们编写的代码去运行Chrome,可以减少人为因素的影响,使运行更加稳定。
当然我们可以通过他的属性值headless来控制让页面友好的显示出来,甚至可以控制页面显示多大。
在前端我们可以用它来爬取一些页面数据进行分析,可以进行网络请求的拦截来达到某种业务效果,总之puppeteer非常的好用。
既然puppeteer是调用的API来操作的浏览器,那么我们可以想到,puppeterr下的浏览器,与我们平时所见到的浏览器有什么不一样呢?
又是什么在吊起了浏览器进程,让我们可以使用代码与浏览器进行通信的呢?
首先让我们了解一下puppeteer对浏览器的整体架构的分解
- Browser: 其实对应一个浏览器实例,有了这个实例之后我们就可以接着去打开一个浏览器窗口了。
- BrowserContext:而一个浏览器实例,是可以打开好几个窗口的,他们可以拥有各自的cookie ,session 。
- Page:表示一个窗口中的一个页面,当然一个窗口可以打开很多个页面。
- Frame:页面中的 一个框架,每个页面有一个主框架,主要由 iframe 标签创建产生的
- ExecutionContext: 是 javascript 的执行环境,每一个 Frame 都一个默认的 javascript 执行环境 其次,node库中的这些API,他们实现的原理其实是通过Chrome DevTools Protocol(CDP) 协议与浏览器进行通信的。
而node所做的就是将底层通过协议控制的浏览器操作,进行简化封装,让我们可以简单的使用。
比如下面是通过CDP来进行页面的跳转:
const cdp = new CDP();
await cdp.connect(wsEndpoint);
const targetsResponse = await cdp.send('Target.getTargets');
const pageTarget = targetsResponse.targetInfos.find((t) => t.type === 'page');
const attachResponse = await cdp.send('Target.attachToTarget', { targetId: pageTarget.targetId, flatten: true });
const sessionId = attachResponse.sessionId;
const navigateResponse = await cdp.send('Page.navigate', { url: 'https://m.jk.cn' }, sessionId);
console.log('navigateResponse', navigateResponse);
而使用puppeteer提供的API的话我们两行代码足以搞定:
const page = await browser.newPage();
await page.goto('https://m.jk.cn');
总体来看对话,他先是将我们日常用到的浏览分成了各个部分,然后实例化出一个浏览器对象,包括浏览器上下文,各个页面等。可以看到,puppeteer其实是对CDP操作浏览器对代码进行了简化,再由node封装导出API供我们使用。
再把与浏览器通信对CDP操作进行简化封装,最后使用node进行导出API,省去了中间复杂的CDP通信过程,让我们直接使用代码的方式操作浏览器。
puppeteer实践
我们使用浏览器,大多的操作都是在页面上进行点击操作,键盘输入操作等,其实浏览器本身提供的还有截图,保存页面pdf等功能。
在通过puppeteer对功能的封装简化之后,我们使用几行代码便可以完成截图,保存pdf。
值得一提的是,使用puppeteer几乎所有操作都是异步的,会返回Promise,我们可以使用.then去处理,但是在node环境下我们可以使用async,await优雅处理
因此我们的运行环境Nodejs 的版本不能低于 v7.6.0, 需要支持 async,await。
首先在项目目录下
npm install puppeteer --save
这样我们就可以开始使用puppeteer以编程的方式去操作浏览器啦。
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://www.baidu.com');
await page.screenshot({path: 'baidu.png'});
await browser.close();
})();
上面的这几行代码就已经实现了我们从打开浏览器,跳转页面,截图,到关闭浏览器的所有过程
先通过 puppeteer.launch() 创建一个浏览器实例 Browser 对象 然后通过 Browser 对象创建页面 Page 对象 然后 page.goto() 跳转到指定的页面 调用 page.screenshot() 对页面进行截图 关闭浏览器 同样的,如果我们要获取页面的pdf我们只需要调用他提供的page下的API
await page.pdf({
path: 'page.pdf',
printBackground:true,
format: 'A4'
});
可以看到,每个API都可以传入参数,这些参数可以确定我们要将获取到的文件保存在哪path,printBackground是否需要背景图,以及要什么样的尺寸format。
更多的参数属性可以参考官方提供的参数说明。
通过puppeteer我们可以调用简单的API完成一些普通的浏览器操作,除了这些他还能做的事情其实还很多,我们可以模拟用户实现登录,或是爬取页面的数据。
结合入门的基础操作,这里以登录我们豌豆app为列做一个模拟登录的demo,以下为部分代码:
(async () => {
try{
const browser = await puppeteer.launch({
headless:false,
slowMo:300
});
const page = await browser.newPage();
await page.emulate(puppeteer.devices['iPhone 6']);
await page.goto('https://m.wandougongzhu.cn/user/login');
await page.tap('.link-btn');
await page.type('#app > div > div > div:nth-child(3) > input', '12345678901', {delay:300});
await page.type('#app > div > div > div:nth-child(4) > input', '***********', {delay:300});
await page.screenshot({ path: 'full.png', fullPage: true });
await page.tap('#app > div > div > div.btn-box > div');
await page.waitForTimeout(2000);
await page.screenshot({ path: 'login.png', fullPage: true });
await browser.close();
} catch (error) {
console.log(error);
}
})();
为了方便展示,我们可以设置headless为false这样就可以显示浏览器的页面,接着我们分析一下代码
page.emulate调用此方法可以让页面展示为一种手机模式 page.tap是对页面上对DOM元素进行点击,这里我们点击使用账号密码登录 page.type这个方法可以获取页面上对文本输入DOM,获取焦点,并输入内容,这里我们输入账号密码 然后获取页面上对登录按钮,进行点击登录 在使用page.waitForTimeout做一个页面加载对等待,最后截图关闭
这样我们就实现了以代码对方式模拟用户进行登录操作,并且我们可以设置输入信息对间隔时间,这样可以绕过一些页面对反自动化对规则,保证代码对稳定性。
基于这种操作我们可以实现一些自动化的测试,比如一些表单的提交,按钮的点击测试,数据的输入等等。
通过上面的demo我们可以看出,puppeteer的操作大多是针对与页面上的DOM元素展开的,只要获取到页面上的DOM元素我们就可以展开一系列的操作。
现在的一些网页大多数都是采用js进行后期的渲染加载,使用puppeteer这种获取到DOM之后在对DOM进行操作对方法,会更稳定一些。
puppeteer 实现爬虫
puppeteer是根据页面上的真实存在的DOM进行各种操作的,那么只要我们可以获取到DOM元素,我们就可以获取到元素在页面上展示的值。
根据这个思路,我们就可以结合js完成一个简单的网页爬虫,通过注入的js代码,切换页面,获取页面DOM元素,进而拿到数据。
(async () => {
let data = [];
const browser = await puppeteer.launch({
headless: false,
});
const page = await browser.newPage();
for (let mo = 1; mo < 4; mo++) {
for (let pg = 1; pg <= 10; pg++) {
mo = mo.toString().padStart(2, "0");
await page.goto(
"https://www.bilibili.com/v/music/cover/?spm_id_from=333.5.b_7375626e6176.3#" +
`/all/click/0/${pg}/2021-${mo}-01,2021-${mo}-28`
);
await page.waitForSelector(".vd-list-cnt > ul > li > div > div.r > a");
let titles = await page.$$eval(".vd-list-cnt > ul > li > div > div.r > a",
(links) => links.map((item) => item.innerText)
);
console.log(titles);
data = data.concat(titles);
}
}
fs.writeFile("data.json", JSON.stringify(data, null, "\t"), function (err) {
if (err) {
console.log(err);
}
});
})();
上面就是结合js进行的简单的爬虫,一切我们在页面上看到的,都可以使用puppeteer去爬取,并且可以稳定的爬取到数据,避免一些页面的渲染都在js中,使用平常的爬虫很难在拿到数据。
通过这个简单的爬虫相信大家对puppeteer的印象又加深了不少,希望大家通过puppeteer与js代码的结合,可以完成一些业务上的难题。