阅读 1202

【第1291期】Puppeteer入门简介

原文链接: mp.weixin.qq.com

前言

现在的节日可真多,也成为了展现每家公司或团队机会。今日早读文章由美团@红烧牛肉面投稿分享。

@红烧牛肉面,服务于美团点评到综前端团队,有5年的前端开发经验,专注于h5页面的开发,正努力学习node相关开发。

Puppeteer是什么

Puppeteer简单来说就是一个Node库,它提供了封装良好的API来控制 headless Chrome或者Chromium。它是由GoogleChrome团队进行维护的,有着良好的兼容性和应用前景,它的出现让PhantomJS、Selenium/WebDriver等其他headless浏览器貌似停止维护了。

写下本文时PhantomJS最新版v2.1,发布时间为2016.01.23

Puppeteer is a Node library which provides a high-level API to control headless Chrome or Chromium over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome or Chromium.

Puppeteer能干什么

你用浏览器能做的事,Puppeteer大部分都能做。

Most things that you can do manually in the browser can be done using Puppeteer!

Here are a few examples to get you started:

  • Generate screenshots and PDFs of pages.

  • Crawl a SPA and generate pre-rendered content (i.e. “SSR”).

  • Automate form submission, UI testing, keyboard input, etc.

  • Create an up-to-date, automated testing environment. Run your tests directly in the latest version of Chrome using the latest JavaScript and browser features.

  • Capture a timeline trace of your site to help diagnose performance issues.

应用最多的恐怕是上面提到的爬取网页以及自动化测试。

Puppeteer怎么用

准备工作

npm i puppeteer

puppeteer npm包还是比较大的,有70M左右,并且需要翻墙下载Chromium。可以考虑 -g 全局安装。

Puppeteer需要 Node v6.40及以上版本,同时官网的例子都使用了 async/await 语法,所以最好是 Node v7.60及以上版本。

Caution: Puppeteer requires at least Node v6.4.0, but the examples below use async/await which is only supported in Node v7.6.0 or greater.

个人也是建议尽量用比较新的Node版本,后面的例子也将大量使用 async/await 语法,保证代码易于阅读和理解。

其实Puppeteer的使用就是怎么使用API接口,Puppeteer API文档也是写得非常好,所以下面就直接举例子了。

例子1:多种尺寸截屏
const puppeteer = require('puppeteer');(async () => {    const browser = await puppeteer.launch({        headless:false    });    const page = await browser.newPage();
    await example1(page,browser); //后续例子替换这里就好了
    await browser.close();})();async function example1(page,browser){    await page.goto('https://www.jd.com/',{        waitUntil:'load'    });
    /**     * pdf功能需要设置launch的headless为true
     */    // await page.pdf({    //     path: 'example1.pdf',     //     format: 'A4'    // });
    //默认截屏可视区域 800*600    await page.screenshot({        path: 'example1-1.png'    });
    /**     * setViewport重新定义可视区域 1420*1000
     */    await page.setViewport({        width: 1420,         height: 1000    });    await page.screenshot({        path: 'example1-2.png'    });
    //全屏截取    await page.screenshot({        path: 'example1-3.png',        fullPage: true    });}复制代码

puppeteer.launch创建了一个Browser实例,类似于我们打开了浏览器。

browser.newPage创建了一个Page实例,类似于我们在浏览器中新开了一个页面。

browser.close不用浏览器了当然要记得关掉,防止内存泄露。

以上三个方法是最基本的,是程序完整运行必备的。

page.goto就是导航到要访问的页面,注意可以提供 waitUntil 参数来配置什么时候导航结束,毕竟你不能指望网页一打开就好了吧。例子中等待页面 load 事件触发,当然还有其他可选值”domcontentloaded”、”networkidle0”、”networkidle2”,详情请参考文档说明。

page.screenshot截屏的默认尺寸是800*600,可以用page.setViewport重新定义可视区域。如果一心想截全屏,page.screenshot的 fullPage 配置可以满足你。

例子2:懒加载及数据滚动加载

如果成功运行例子1的话,会发现jd的首页fullPage截屏有很多空白的地方,这是因为jd首页采用了懒加载以及数据滚动加载的技术。解决办法当然就是让网页滚动起来。

async function example2(page,browser){await page.goto('https://www.jd.com/',{    waitUntil:'networkidle2'});await autoScroll(page);//全屏截取await page.screenshot({    path: 'example2.png',    fullPage: true});}async function autoScroll(page){    await page.evaluate(async () => {        await new Promise((resolve, reject) => {            var totalHeight = 0;            var distance = 200;            var timer = setInterval(() => {                var scrollHeight = document.body.scrollHeight;                window.scrollBy(0, distance);                totalHeight += distance;
                if(totalHeight >= scrollHeight){                    clearInterval(timer);                    resolve();                }            }, 200);        });    });}复制代码

核心函数autoScroll使用了 page.evaluate,这是一个非常强大的函数,它创建了一个页面上下文,类似于浏览器的开发者工具,可以执行原生js代码,DOM操作自然不在话下。

例子3:模拟移动设备
const devices = require('puppeteer/DeviceDescriptors');const iPhone6 = devices['iPhone 6'];async function example3(page,browser){    await page.goto('https://www.jd.com/',{        waitUntil:'networkidle2'  //默认值 load    });    //await page.emulate(iPhone6);    await page.emulate({        viewport: {            width: 375,            height: 667,            isMobile: true        },        userAgent: '"Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1"'    });
    await autoScroll(page);    await page.waitFor(3*1000); }复制代码

page.emulate本质上是page.setViewport和page.setUserAgent二者的快捷调用方式。

通过参数viewport配置你想要的屏幕大小,要是嫌麻烦,puppeteer还贴心的准备了DeviceDescriptors映射表,通过定义好的手机型号获取相应参数。

page.waitFor传入以毫秒为单位的时间间隔可以让页面暂停一段时间,这点在自动化测试查看页面时很有用,当然waitFor不仅仅就这么简单,文档还介绍了更多的功能,后面的例子也会有所提及。

例子4:页面爬取及自动化页面操作
    async function example4(page,browser){    await page.goto('https://www.jd.com/',{        waitUntil:'networkidle2'  //默认值 load    });    await page.setViewport({        width: 1185,         height: 800    });

    /**     * 获取element内容-方法1
     */    const result = await page.$eval('#hotwords',el=>{        return el.innerText    });    console.log('page.$eval',result)
    /**     * 获取element内容-方法2
     */    // const result = await page.evaluate(()=>{    //     let element = document.querySelector('#hotwords');    //     return element.innerText;    // })    //console.log('page.evalute',result)
    await page.waitFor(3*1000); 
    /**     * element键盘-方法1
     */    // await page.type('#search input', 'macbook pro', {    //     delay: 300    // });    // await page.keyboard.press('Enter');
    /**     * element键盘-方法2
     */    const inputEle = await page.$('#search input');    await inputEle.type('macbook pro',{        delay:300    });    // await inputEle.screenshot({    //     path: 'example5.png'    // });    //await inputEle.press('Enter');
    /**     * element点击-方法1
     */    //await page.click('#search button');
    /**     * element点击-方法2
     */    const btnEle = await page.$('#search button');    await btnEle.click();
    await page.waitFor(5*1000); }复制代码

前面提到page.evaluate是很强大的,但如果只是想拿一个element的文本,未免小题大作了。可以使用page.$eval,它将 document.querySelector 获取到的结果作为参数传给回调函数处理。

在文档中还有类似的 page.?eval,它的作用是将 document.querySelectorAll 获取到的结果作为参数传给回调函数处理。

page.$调用document.querySelector,但它的区别在于没有回调函数,直接返回一个包装好的 ElementHandle类。

类似的,page.?调用的是document.querySelectorAll,也是返回一个包装好的ElementHandle类。

ElementHandle就拥有后面所用到的 type、click、press等方法,特别提一下screenshot,它只截取ElemetHandle所在区域的图片。

elementHandle.screenshot([options])

This method scrolls element into view if needed, and then uses page.screenshot to take a screenshot of the element. If the element is detached from DOM, the method throws an error.

会提问题的才是好孩子

组合键要怎么做,比如输入一个大写字母

输入一个大写的M

await page.focus('input');await page.keyboard.down('Shift');await page.keyboard.press('KeyM');await page.keyboard.up('Shift');复制代码
像点击a标签一样,跳转到新页面要怎么处理

分两种情况

  • 如果像a标签target属性不为_blank一样,没有新开页面,则可以不用担心,page会一直指向最新页面,如果想返回原来的页面,page.goBack、page.goForward、page.goto可以满足你。

  • 如果a标签target属性为_blank,新开了页面,可以使用let pages = await browser.pages(),它返回当前的页面的page类数组集合,想要哪个页面数组中拿就行,不过原来的page还是一直指向原来的页面。

文章一开始说Puppeterr能做大部分事,意思是还有不能做的
  • Puppeteer只支持 Chromium or Chrome。

  • Puppeteer对audio和video支持不是很好,原因点官方回答

结语

Puppeteer使用起来还是挺方便的,API也很强大,文章里只提了很小的一部分功能,更多功能还是建议阅读官方文档。

参考

Puppeteer:模拟浏览器操作行为的利器:

https://github.com/chenxiaochun/blog/issues/38

Puppeteer: 更友好的 Headless Chrome Node API:

https://www.cnblogs.com/dolphinX/p/7715268.html

爬虫利器 Puppeteer 实战:

https://cnodejs.org/topic/5a4d8d2299d207fa49f5cbbc

关于本文作者:@红烧牛肉面原文:https://github.com/masterkong/blog/issues/6

最后,为你推荐

【第1115期】Puppeteer: 更友好的 Headless Chrome Node API

文章分类
前端
文章标签