puppeteer实现浏览器自动化和爬虫

6,242 阅读7分钟

puppeteer 是谷歌官方出品的一个通过 DevTools 协议控制 headless Chrome 的 Node 库,它提供了一个高级 API 来通过 DevTools 协议控制 Chromium 或 Chrome,执行常见的操作,就像在真实的浏览器中一样,可以用来实现浏览器自动化测试或爬虫

puppeteer 是浏览器自动化的产品。安装后,它会下载一个版本的 Chromium,然后使用puppeteer-core 驱动工作

  • puppeteer-core 是一个库,来帮助驱动任何支持 DevTools 协议的东西。puppeteer-core 在安装时不会下载 Chromium

在浏览器中手动执行的绝大多数操作都可以使用 puppeteer 来完成:

  • 页面截图,生成页面 PDF
  • 抓取 SPA(单页应用)并生成预渲染内容(即“SSR”服务器端渲染)
  • 自动提交表单,进行 UI 测试,键盘输入等
  • 创建一个时时更新的自动化测试环境, 使用最新的 JavaScript 和浏览器功能直接在最新版本的 Chrome 中执行测试
  • 捕获网站的 timeline trace,用来帮助分析性能问题
  • 测试浏览器扩展

安装

(Nodejs 6.4以上版本环境下)安装 puppeteer 时,它会下载最新版本的 Chromium,以保证可以使用 API

  • 使用 npm 安装

    npm install puppeteer

  • 使用 cnpm 安装

    cnpm install puppeteer

注:使用 async、await,Nodejs 的版本不能低于 v7.6.0

使用

puppeteer 默认以 headless 模式(无头模式,不显示浏览器界面)运行,但是可以通过修改配置文件运行 “有头” 模式(显示浏览器界面)

新建 puppeteer.js:

//引入puppeteer
const puppeteer = require("puppeteer");
//使用async/await处理异步
(async () => {
	//创建一个Browser(浏览器)实例
    const browser = await puppeteer.launch({
        //设置有头模式(默认为true,无头模式)
        headless: false
    });
    //在浏览器中创建一个新的页面
    const page = await browser.newPage();
    //打开指定页面
    await page.goto("https://blog.csdn.net/weixin_45426836?spm=1011.2124.3001.5343");
    
    //......(执行的操作)
    
    //关闭浏览器实例
    await browser.close();
})();
设置页面尺寸(默认为 800px x 600px):
  • 通过 Page.setViewport() 设置
    const puppeteer = require("puppeteer");
    (async () => {
        const browser = await puppeteer.launch();
        const page = await browser.newPage();
        //设置页面的尺寸
        await page.setViewport({
    	    width: 1400,
    	    height: 800,
    	});
        await page.goto("https://blog.csdn.net/weixin_45426836?spm=1011.2124.3001.5343");
    
        await browser.close();
    })();
    
  • 通过 Browser 对象参数的 defaultViewport 设置
    const puppeteer = require("puppeteer");
    (async () => {
        const browser = await puppeteer.launch({
        	//设置页面的尺寸
            defaultViewport: {
                width: 1400,
                height: 800
            }
    	});
        const page = await browser.newPage();
        await page.goto("https://blog.csdn.net/weixin_45426836?spm=1011.2124.3001.5343");
    
        await browser.close();
    })();
    
    自适应尺寸:将 defaultViewport 设为 null,启动之后还是半屏显示,点击浏览器最大化按后,页面根据分辨率自适应大小(使用该设置进行页面截图仍是半屏显示的截图)
常用 API:

Browser :当 puppeteer 连接到一个 Chromium 实例的时候会通过 puppeteer.launch 或 puppeteer.connect 创建一个 Browser 对象:

  • browser.newPage():创建一个新页面,返回一个新的 Page 对象
  • browser.close():关闭 Chromium 及其所有被打开的页面
  • browser.pages():返回包含 Chromium 中所有页面的数组
  • browser.userAgent():返回 Chromium 浏览器的 userAgent

Page:提供操作一个 tab 页或者 extension background page(扩展背景页) 的方法。一个 Browser 实例可以有多个 Page 实例:

  • page.goto(url[, options]):打开指定 url 的页面,返回请求的响应
  • page.close([options]):关闭页面
  • page.goBack([options]):导航到页面历史的前一个页面,返回请求的响应
  • page.goForward([options]):导航到页面历史的后一个页面,返回请求的响应
  • page.reload([options]):重新加载页面,返回请求的响应
  • page.content():返回页面的完整 html 代码,包括 doctype
  • page.title():返回页面的标题
  • page.url():返回页面的 url
  • page.browser():返回当前 page 实例所属的 browser 实例
  • page.cookies([...urls]):返回指定任何 url 下的 cookie,不指定返回当前页面域名的 cookie
  • page.setCookie(...cookies):设置页面的 cookie
  • page.deleteCookie(...cookies):删除页面的 cookie
  • page.emulate(options):根据指定的参数和 user agent 生成模拟器(例如 iPhone, Mac, Android 等,参数使用:puppeteer.devices["iPhone 6"])
  • page.emulateMedia(mediaType):改变页面的 css 媒体类型(支持的值为 'screen'、'print' 和 null,null 为禁用媒体模拟)
  • page.setUserAgent(userAgent):设置页面的 UserAgent 信息
  • page.setViewport(viewport):设置页面的 viewport 信息(默认尺寸为 800px x 600px)
  • page.viewport():返回页面的 viewport 信息
  • page.$(selector):此方法在页面内执行 document.querySelector(如果没有元素匹配指定选择器,返回 null)
  • page.$$(selector):此方法在页面内执行 document.querySelectorAll(如果没有元素匹配指定选择器,返回 [ ])
  • page.$eval(selector, pageFunction[, ...args]):此方法在页面内执行 document.querySelector,然后把匹配到的元素作为第一个参数传给 pageFunction 函数。可以用于获取元素,然后再通过函数获取元素的属性
  • page.click(selector[, options]):点击匹配到的元素
  • page.type(selector, text[, options]):向匹配的元素输入指定的内容(如果有多个匹配的元素,输入到第一个匹配的元素)
  • page.focus(selector):使匹配到的元素获得焦点
  • page.hover(selector):使匹配到的元素滚动到视野中,将鼠标悬停在元素的中心
  • page.evaluate(pageFunction[, ...args]):在页面执行自定义的 js 函数
  • page.waitFor(selectorOrFunctionOrTimeout[, options[, ...args]]):等待指定参数完成(指定匹配的元素出现在页面中,指定时间之后...)
  • page.waitForSelector(selector[, options]):等待指定匹配的元素出现在页面中
  • page.addScriptTag(options):注入一个指定 src(url) 或者代码(content)的 script 标签到当前页面
  • page.addStyleTag(options):添加一个指定 link(url) 或者代码(content)的 style 标签到当前页面
  • page.screenshot([options]):对当前页面截图,返回截图的buffer
  • page.pdf([options]):生成当前页面的 pdf 格式的文件,返回 pdf buffer

Keyboard:提供一个接口来管理虚拟键盘

  • keyboard.down(key[, options]):按下键盘上指定键,触发 keydown 事件(按下之后没有被释放,一般会持续的触发该按键,需要通过 keyboard.up() 释放)
  • keyboard.up(key):释放指定键,触发 keyup 事件
  • keyboard.press(key[, options]):按下键盘上指定键并释放(keyboard.down() 和 keyboard.up() 的快捷操作)
  • keyboard.type(text, options):向焦点元素中输入指定的文本

Mouse :提供一个接口来管理虚拟鼠标

  • mouse.move(x, y,[options]):移动鼠标指针到指定的位置,触发 mousemove 事件
  • mouse.down([options]):按下鼠标按键(默认按下左键),触发 mousedown 事件
  • mouse.up([options]):松开鼠标按键,触发 mouseup 事件
  • mouse.click(x, y,[options]):移动鼠标指针到指定的位置,然后按下鼠标按键(默认按下左键)(mouse.move() 和 mouse.down() 或 mouse.up() 的快捷操作)
使用 node 命令执行:

node puppeteer.js

puppeteer 页面截图:

//引入puppeteer
const puppeteer = require("puppeteer");
//使用async/await处理异步
(async () => {
    //创建一个Browser(浏览器)实例
    const browser = await puppeteer.launch();
    //在浏览器中创建一个新的页面
    const page = await browser.newPage();
    //设置页面的尺寸
    await page.setViewport({
	    width: 1400,
	    height: 800,
	});
    //打开页面
    await page.goto("https://baidu.com/");
    //页面截图(设置截图路径)
    await page.screenshot({path: "example.png"});
    //关闭浏览器实例
    await browser.close();
})();

执行命令会在根目录下生成 example.png(页面截图)

example.png:

在这里插入图片描述

puppeteer 生成页面 PDF

//引入puppeteer
const puppeteer = require("puppeteer");
//使用async/await处理异步
(async () => {
    //创建一个Browser(浏览器)实例
    const browser = await puppeteer.launch();
    //在浏览器中创建一个新的页面
    const page = await browser.newPage();
    //设置页面的尺寸
    await page.setViewport({
	    width: 1400,
	    height: 800,
	});
    //打开页面
    await page.goto("https://baidu.com/");
    //生成页面PDF(设置PDF路径)
    await page.pdf({path: "example.pdf", format: "A4"});
    //关闭浏览器实例
    await browser.close();
})();

执行命令会在根目录下生成 example.pdf(页面 PDF)

puppeteer 爬虫

使用 puppeteer 爬取:百度新闻 => 国内 => 即时新闻列表(标题和链接地址)

//引入puppeteer
const puppeteer = require("puppeteer");
//引入node文件系统模块(fs)
const fs = require("fs");
//引入node文件路径模块(path)
const path = require("path");
//创建爬取数据的函数
let getNewList = async () => {
    //创建一个Browser(浏览器)实例
    const browser = await puppeteer.launch();
    //在浏览器中创建一个新的页面
    const page = await browser.newPage();
    //打开百度新闻页面
    await page.goto("http://news.baidu.com/");
    //等待“国内”导航按钮出现
    await page.waitForSelector("#channel-all > div > ul > li:nth-child(3) > a");
    //点击“国内”导航按钮,进入国内新闻页面
    await page.click("#channel-all > div > ul > li:nth-child(3) > a");
    //等待“即时新闻列表”出现
    await page.waitForSelector("#instant-news > ul");
    //通过evaluate函数执行自定义的js代码获取要爬取的数据
    const newList = await page.evaluate(() => {
    	//创建一个空数组接收爬取的数据
        let data = [];
        //获取所有即时新闻列表li元素
        let elements = document.querySelectorAll("#instant-news > ul > li");
        //利用循环将即时新闻列表的标题和链接地址添加到一个数组中
        for (let i=0; i<elements.length; i++) {
        	//获取新闻的标签
            let title = elements[i].innerText;
            //获取新闻的链接地址
            let url = elements[i].firstChild.getAttribute('href');
            //将获取到的标题和链接地址添加到数组中
            data.push({
                title, 
                url
            });
        }
        //返回数组
        return data;
    });
    //关闭浏览器实例
    await browser.close();
    //返回爬取的数据
    return newList;
}
//执行函数获取爬取的数据
getNewList().then(res => {
    //将爬取的数据转为json格式
    let list = JSON.stringify(res);
    //指定存储数据的json文件
    let file = path.join(__dirname, "newList.json");
    //将爬取的数据写入json文件
    fs.writeFile(file, list, err => {
        if (err) {
            console.log(err);
        } else {
            console.log("success");
        }
    })
})

执行命令会在根目录下生成 newList.json(爬取的百度新闻国内即时新闻列表)

newList.json:
[
  {
    title: '倡导清洁能源取暖,让环保与温暖同行!',
    url: 'http://baijiahao.baidu.com/s?id=1685083001881996849'
  },
  {
    title: '关于减塑环保必胜客做了件大事 而且还做得挺漂亮',
    url: 'http://baijiahao.baidu.com/s?id=1685499582365297158'
  },
  {
    title: '「我的脱贫故事」脱贫前我们也害怕“开学”丨剑河',
    url: 'http://baijiahao.baidu.com/s?id=1685501813192308560'
  },
  {
    title: '山西留美学子靳蕾被聘为“中华环保志愿者公益形象',
    url: 'http://baijiahao.baidu.com/s?id=1685513310840028026'
  },
  {
    title: '忻城县2020年公开选拔县属国有企业领导人员面',
    url: 'http://baijiahao.baidu.com/s?id=1685544017875709933'
  },
  {
    title: '107名长沙市五星级环保好少年集中受表彰',
    url: 'http://baijiahao.baidu.com/s?id=1685597128244025396'
  },
  {
    title: '《圭塘河岸》一书发行 谱写新的“长江之歌”',
    url: 'http://baijiahao.baidu.com/s?id=1685606433440976520'
  },
  {
    title: '莫再错过!山东高考补报名1214-15日进',
    url: 'http://baijiahao.baidu.com/s?id=1685607182699503282'
  }
]