puppeteer是一个node库,他能提供一系列操做Chrome的API,经过这些API咱们能够用程序代码来操纵Chrome去完成各类操做。javascript
他运行在node环境,由Chorme官方团队在维护,既然是操做浏览器,那么咱们能够手动进行的操做在pupeteer上都能进行。前端
另外pupeteer的英文就是木偶的意思,咱们使用他就像操做木偶玩耍同样,能够轻松的作到:java
固然puppeteer不止能够作这些,经过在代码中注入js脚本,咱们几乎能够作到全部咱们能够用代码实现的功能。node
在puppeteer中,Chrome默认运行在"无头"模式,而所谓的无头模式,不过是不加载浏览器的UI界面,实际上并不会影响咱们的操做。npm
使用无头模式,在无界面的状况下按照咱们编写的代码去运行Chrome,能够减小人为因素的影响,使运行更加稳定。编程
固然咱们能够经过他的属性值headless来控制让页面友好的显示出来,甚至能够控制页面显示多大。json
在前端咱们能够用它来爬取一些页面数据进行分析,能够进行网络请求的拦截来达到某种业务效果,总之puppeteer很是的好用。网页爬虫
既然puppeteer是调用的API来操做的浏览器,那么咱们能够想到,puppeterr下的浏览器,与咱们平时所见到的浏览器有什么不同呢?浏览器
又是什么在吊起了浏览器进程,让咱们可使用代码与浏览器进行通讯的呢?markdown
首先让咱们了解一下puppeteer对浏览器的总体架构的分解
其次,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通讯过程,让咱们直接使用代码的方式操做浏览器。
咱们使用浏览器,大多的操做都是在页面上进行点击操做,键盘输入操做等,其实浏览器自己提供的还有截图,保存页面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是根据页面上的真实存在的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代码的结合,能够完成一些业务上的难题。