结构 、方法 、属性

Puppeteer 安装

  • 安装第三方库:puppeteer

    1. npm i puppeteer
  • 安装谷歌 Chromium:若 npm 安装失败,需要手动下载 chromium 并解压至相应文件夹

    语法

    API 分层结构

  • Browser: 对应一个浏览器实例,一个 Browser 可以包含多个 BrowserContext
  • BrowserContext: 对应浏览器一个上下文会话,就像我们打开一个普通的 Chrome 之后又打开一个隐身模式的浏览器一样,BrowserContext 具有独立的 Session(cookie 和 cache 独立不共享),一个 BrowserContext 可以包含多个 Page
  • Page:表示一个 Tab 页面,通过 browserContext.newPage()/browser.newPage() 创建,browser.newPage() 创建页面时会使用默认的 BrowserContext,一个 Page 可以包含多个 Frame
  • Frame: 一个框架,每个页面有一个主框架(page.MainFrame()),也可以多个子框架,主要由 iframe 标签创建产生的
  • ExecutionContext: 是 javascript 的执行环境,每一个 Frame 都一个默认的 javascript 执行环境
  • ElementHandle: 对应 DOM 的一个元素节点,通过该该实例可以实现对元素的点击,填写表单等行为,我们可以通过选择器,xPath 等来获取对应的元素
  • JsHandle:对应 DOM 中的 javascript 对象,ElementHandle 继承于 JsHandle,由于我们无法直接操作 DOM 中对象,所以封装成 JsHandle 来实现相关功能
  • CDPSession:可以直接与原生的 CDP 进行通信,通过 session.send 函数直接发消息,通过 session.on 接收消息,可以实现 Puppeteer API 中没有涉及的功能
  • Coverage:获取 JavaScript 和 CSS 代码覆盖率
  • Tracing:抓取性能数据进行分析
  • Response: 页面收到的响应
  • Request: 页面发出的请求

    puppeteer实例

    ```javascript const puppeteer = require(‘puppeteer’);

(async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(‘https://www.baidu.com‘); await page.close(); await browser.close(); })();

  1. puppeteer 提供了两种方法用于创建一个 Browser 实例:
  2. - puppeteer.connect :连接一个已经存在的 Chrome 实例
  3. - puppeteer.launch 每次都启动一个 Chrome 实例
  4. puppeteer.launch() 的参数解释:其使用字典进行配置输入(常用参数如下)
  5. - headless 是否使用浏览器界面启动,turn false
  6. 默认为 turn 无头浏览器,不展示浏览器页面<br />设置 false 参数,展示浏览器页面
  7. - **executablePath:**指定浏览器执行地址
  8. - timeout 等待浏览器实例启动的最长时间(以毫秒为单位)。默认为3000030秒)。设置为 0 禁用超时
  9. - slowMo 放慢浏览器执行速度,方便测试观察
  10. - ignoreHTTPSErrorsfalse 表示忽略 https 报错
  11. - args 传递给浏览器实例的其余参数 (参见[官方文档](https://peter.sh/experiments/chromium-command-line-switches/))
  12. 例如:窗口最大化:['--start-maximized']
  13. <a name="ESzbI"></a>
  14. ## 加载导航页面
  15. - **page.goto( ):** 打开新页面
  16. - **page.goBack( ) :** 回退到上一个页面
  17. - **page.goForward( ) :** 前进到下一个页面
  18. - **page.reload( ) :** 重新加载页面
  19. - **page.waitForNavigation( ) :** 等待页面跳转
  20. Pupeeteer 中的基本上所有的操作都是异步的,以上几个 API 都涉及到关于打开一个页面,什么情况下才能判断这个函数执行完毕呢,这些函数都提供了两个参数 waitUtil timeout waitUtil 表示直到什么出现就算执行完毕,timeout 表示如果超过这个时间还没有结束就抛出异常。
  21. <a name="gBZyy"></a>
  22. ## 等待元素、请求、响应
  23. - **page.waitForXPath:** 等待 XPath 对应的元素出现,返回对应的 ElementHandle 实例
  24. - **page.waitForSelector :** 等待选择器对应的元素出现,返回对应的 ElementHandle 实例
  25. - **page.waitForResponse :** 等待某个响应结束,返回 Response 实例
  26. - **page.waitForRequest **等待某个请求出现,返回 Request 实例
  27. <a name="vQDZ6"></a>
  28. ## 自定义等待
  29. - **page.waitForFunction :** 等待在页面中自定义函数的执行结果,返回 JsHandle 实例
  30. - **page.waitFor :** 设置等待时间,实在没办法的做法(官方即将弃用)
  31. - **page.waitForTimeout : **设置等待时间 几乎等同于**waitFor **的作用(ms
  32. <a name="QtVhj"></a>
  33. ## 元素定位(CSS选择器 原则)
  34. - **page**.**$**('#uniqueId'): 获取某个选择器对应的第一个元素
  35. - **page**.**$$**('div'): 获取某个选择器对应的所有元素
  36. - **page**.**$x**('//img'): 获取某个 XPath 对应的所有元素
  37. - **page**.**waitForXPath**('//img'): 等待某个 XPath 对应的元素出现
  38. - **page**.**waitForSelector**('#uniqueId'): 等待某个选择器对应的元素出现
  39. <a name="IYYCZ"></a>
  40. ## 用户模拟操作(鼠标事件)
  41. - **elementHandle.click( )**: 点击某个元素
  42. - **elementHandle.tap( ):** 模拟手指触摸点击
  43. - **elementHandle.focus( )**: 聚焦到某个元素
  44. - **elementHandle.hover( ):** 鼠标 hover 到某个元素上
  45. - **elementHandle.type('hello'):** 在输入框输入文本
  46. 我们查找的时候 page. 或者 page.$ 是返回jshandle对象,<br />而模拟用户操作 事件是 elementHandle 对象 才可以执行,所以需要 asElement( ) 转换对象类型。
  47. ```javascript
  48. let btn = await page.$('div[aria-label="Create a post"]')
  49. await page.waitForTimeout(400);
  50. let btn2 = await btn.asElement().$$('div[data-visualcompletion="ignore"]')
  51. await page.waitForTimeout(400);
  52. await btn2[1].asElement().click()

用户模拟操作(键盘事件)

// 模拟键盘“回车”键
await page.keyboard.press('Enter');
// 模拟键盘 文本输入
let txt ='hello';
await page.keyboard.type('#key', txt, { delay: 400 });

// 模拟键盘 实现复制/粘贴 Ctrl+c 、Ctrl+v
 //1) 获取元素-实现复制
  document.body.querySelector('input[fao="fao"]').select()
  document.execCommand("copy", false, null);
//2) focus 聚焦光标
  await page.focus('input.nav-search-input')
//3) 实现粘贴
  await page.keyboard.down('Control');
  await page.keyboard.press('KeyV', { delay: 100 });
  await page.keyboard.up('Control');

请求拦截

我们可以在监听 Page 的 request 事件,并进行请求拦截,前提是要开启请求拦截 page.setRequestInterception(true)

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    const blockTypes = new Set(['image', 'media', 'font']);
    await page.setRequestInterception(true); //开启请求拦截
    page.on('request', request => {
        const type = request.resourceType();
        const shouldBlock = blockTypes.has(type);
        if(shouldBlock){
            //直接阻止请求
            return request.abort();
        }else{
            //对请求重写
            return request.continue({
                //可以对 url,method,postData,headers 进行覆盖
                headers: Object.assign({}, request.headers(), {
                    'puppeteer-test': 'true'
                })
            });
        }
    });
    await page.goto('https://demo.youdata.com');
    await page.close();
    await browser.close();
})();

page 页面提供的 触发事件

  • page.on(‘close’) 页面关闭
  • page.on(‘console’) console API 被调用
  • page.on(‘error’) 页面出错
  • page.on(‘load’) 页面加载完
  • page.on(‘request’) 收到请求
  • page.on(‘requestfailed’) 请求失败
  • page.on(‘requestfinished’) 请求成功
  • page.on(‘response’) 收到响应
  • page.on(‘workercreated’) 创建 webWorker
  • page.on(‘workerdestroyed’) 销毁 webWorker

    获取 WebSocket 响应

    Puppeteer 目前没有提供原生的用于处理 WebSocket 的 API 接口,但是我们可以通过更底层的 Chrome DevTool Protocol (CDP) 协议获得

    (async () => {
      const browser = await puppeteer.launch();
      const page = await browser.newPage();
      //创建 CDP 会话
      let cdpSession = await page.target().createCDPSession();
      //开启网络调试,监听 Chrome DevTools Protocol 中 Network 相关事件
      await cdpSession.send('Network.enable');
      //监听 webSocketFrameReceived 事件,获取对应的数据
      cdpSession.on('Network.webSocketFrameReceived', frame => {
          let payloadData = frame.response.payloadData;
          if(payloadData.includes('push:query')){
              //解析payloadData,拿到服务端推送的数据
              let res = JSON.parse(payloadData.match(/\{.*\}/)[0]);
              if(res.code !== 200){
                  console.log(`调用websocket接口出错:code=${res.code},message=${res.message}`);
              }else{
                  console.log('获取到websocket接口数据:', res.result);
              }
          }
      });
      await page.goto('https://netease.youdata.163.com/dash/142161/reportExport?pid=700209493');
      await page.waitForFunction('window.renderdone', {polling: 20});
      await page.close();
      await browser.close();
    })();
    

    植入 JavaScript 代码

    —————————————————————————————-
    await page.evaluate(() => { 逻辑实现 })
    —————————————————————————————-

    await page.evaluate(() => {
      // document.getElementById("#menus").classList.add("menus-show");
      // let menus = document.querySelector('#menus') as HTMLDivElement
      // menus.classList.add('menus-show')
      // console.log(menus)
    
      let button = document.querySelector('#all_menus_item') as HTMLDivElement
      button.addEventListener('click', function () {
          console.log("点击【全部产品】")
      })
    })
    

    在浏览器环境中执行代码的函数

  • page.evaluate(pageFunction[, …args]): 在浏览器环境中执行函数

  • page.evaluateHandle(pageFunction[, …args]): 在浏览器环境中执行函数,返回 JsHandle 对象
  • page.$$eval(selector, pageFunction[, …args]):

把 selector 对应的所有元素传入到函数并在浏览器环境执行

  • page.$eval(selector, pageFunction[, …args]):

    把 selector 对应的第一个元素传入到函数在浏览器环境执行

  • page.evaluateOnNewDocument(pageFunction[, …args]):

创建一个新的 Document 时在浏览器环境中执行,会在页面所有脚本执行之前执行

  • page.exposeFunction(name, puppeteerFunction):

在 window 对象上注册一个函数,这个函数在 Node 环境中执行,有机会在浏览器环境中调用 Node.js 相关函数库

页面性能分析

Puppeteer 提供了对页面性能分析的工具,目前功能还是比较弱的,只能获取到一个页面性能执行的数据
—————————————————————————————-
await page.tracing.start({path: ‘./files/trace.json’});
await page.goto(‘https://www.google.com‘);
await page.tracing.stop( );
await page.close( );
—————————————————————————————-

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.tracing.start({path: './files/trace.json'});
    await page.goto('https://www.google.com');
    await page.tracing.stop();
    /*
        continue analysis from 'trace.json'
    */
    browser.close();
})();

文件的上传和下载

—————————————————————————————————————————
//通过 CDP 会话设置下载路径
const cdp = await page.target().createCDPSession();
await cdp.send(‘Page.setDownloadBehavior’, {
behavior: ‘allow’, //允许所有下载请求
downloadPath: ‘path/to/download’ //设置下载路径
});
//点击按钮触发下载
await (await page.waitForSelector(‘#someButton’)).click();
//等待文件出现,轮训判断文件是否出现
await waitForFile(‘path/to/download/filename’);
—————————————————————————————————————————
//上传时对应的 inputElement 必须是元素
let inputElement = await page.waitForXPath(‘//input[@type=”file”]’);
await inputElement.uploadFile(‘/path/to/file’);
—————————————————————————————————————————

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    //通过 CDP 会话设置下载路径
    const cdp = await page.target().createCDPSession();
    await cdp.send('Page.setDownloadBehavior', {
        behavior: 'allow', //允许所有下载请求
        downloadPath: 'path/to/download'  //设置下载路径
    });
    //点击按钮触发下载
    await (await page.waitForSelector('#someButton')).click();
    //等待文件出现,轮训判断文件是否出现
    await waitForFile('path/to/download/filename');

    //上传时对应的 inputElement 必须是<input>元素
    let inputElement = await page.waitForXPath('//input[@type="file"]');
    await inputElement.uploadFile('/path/to/file');
    browser.close();
})();

跳转新 tab 页处理

let page = await browser.newPage();
await page.goto(url);
let btn = await page.waitForSelector('#btn');
//在点击按钮之前,事先定义一个 Promise,用于返回新 tab 的 Page 对象
const newPagePromise = new Promise(res => 
  browser.once('targetcreated', 
    target => res(target.page())
  )
);
await btn.click();
//点击按钮后,等待新tab对象
let newPage = await newPagePromise;

模拟不同的设备

Puppeteer 提供了模拟不同设备的功能,其中 puppeteer.devices 对象上定义很多设备的配置信息,
这些配置信息主要包含 viewport userAgent,然后通过函数 page.emulate 实现不同设备的模拟

const puppeteer = require('puppeteer');
const iPhone = puppeteer.devices['iPhone 6'];
puppeteer.launch().then(async browser => {
  const page = await browser.newPage();
  await page.emulate(iPhone);
  await page.goto('https://www.google.com');
  await browser.close();
});

分页监听

await mpage.setRequestInterception(true);
page.on("request", async (request) => {
    request.continue();
});
page.on("response", async (response) => {
    const req = response.request();
    if (req.url().indexOf("分页请求的地址") > 0) {
    //操作
    }
})