puppeteer在开发过程中的实践

4,739 阅读5分钟

上篇文章我们谈了一下puppeteer是什么,以及具体能做什么,但文中谈到的在我们日常开发中并不常用到,截图我们有截图工具、前端自动化测试有phantomjs、selenium。不仅如此,为了SEO,前端基本都会做SSR,爬取这样的页面的话python得益于强大的模块,具有得天独道的优势。也许有人会问,那对于某些后台管理系统,是没有做SSR的,此时puppeteer的优势不就体现出来了吗?对于此种情况,首先,爬管理系统的数据的话需要有账号密码,我们没有。那如果是我们自己的系统的话,数据都是自己的,我还需要爬吗?

额。。。。

pupueteer看来似乎并没有想象中的那么强大。但实际情况下并非如此,虽然有些功能之前存在的一些框架也能做到,但毕竟对于我们前端开发来说,要想掌握新的技术就需要去学习新的语言,这样的话成本就很大了。

pupueteer提供了简洁的API和丰富的接口,而且其是一个nodejs库,所以从学习角度来讲对前端很友好,上手快。接下来就来具体描述一个在实际开发过程中使用puppeteer的场景。

在我们公司前端开发的过程中,大部分项目要去接CAS,什么是CAS呢,走你,其目的是为了获取COOKIE,以便于与后端进行交互。我司目前常见的场景存在三种形式。

1.前端网关nodejs,主要用于转换dubbo协议,获取数据,其项目中会用nodejs去接cas

2.有些应用,服务端接口需要登录cas后拿到回调的cookie去请求数据,而此时前端要配置代理,模拟cas登录,拿到cookie后写在header里

3.有些应用,直接登录相关应用的开发环境,然后把cookie拿过来通过document.cookie写到本地环境进行开发

第一种情况因为线上需要cas登录,暂且不表。

但对于后面两种情况,这里可以简单分析下,其实2和3都是在本地开发的时候拿到登录cas后的cookie,然后在请求数据的时候把cookie带回去拿到数据。但要放到服务器的时候实际上是前端把静态资源全部打包成某一个或若干个js文件(一般不会超过3个)。所以问题就变成了我们怎么在开发环境方便的拿到登录cas成功后的cookie。如果对于一个熟悉cas sso原理的话,其对接一下cas可能会很快,但弊端就是要新搭一个node服务,写一些登录cas,拿cookie的流程,而且如果针对不同权限角色的话,每次登录新的角色,都要重新用不同角色去登录系统,去拿cookie(我们在开发运维发布平台即是如此,涉及admin,运维,开发,测试,测试经理等不同角色,每次功能有掺和都是一个痛苦的切换cas登录拿cookie的过程); 如果对于一个不明其理的开发来说,这无异于一枚张榜炸弹,你接口跨域我可以很简单的做下代理,cas, w**fk, 告辞.......

那如何在开发环境用puppeteer做到拿cookie的过程呢?

(敲黑板) 划重点啦。

以我负责开发的运维发布平台为例, 其实现思路如下

puppeteer

代码如下:

const puppeteer = require("puppeteer");

let cookie = {
  name: "JSESSIONID",
  value: "",
  domain: "localhost",
  url: "http://localhost:3000/",
  path: "/",
  httpOnly: true,
  secure: false
};
const role = process.argv.pop();

const getRole = role => {
  return {
    a: { //系统管理员
      username: "admin",
      password: "123456"
    },
    qa1: { //测试经理
      username: "04688",
      password: "123456"
    },
    qa: { //测试
      username: "01522",
      password: "123456"
    },
    dev: { //开发
      username: "04588",
      password: "123456"
    },
    ops: { //运维
      username: "04141",
      password: "123456"
    }
  }[role];
};

class Launch {
  constructor(username, password) {
    this.username = username;
    this.password = password;
    this.flag = true;   //用来判断拦截第一次302
  }
  async init(page, browser) {
    // <!--  模拟cas登录 -->
    const casBrowser = await puppeteer.launch();    
    const loginPage = await casBrowser.newPage();
    await loginPage.goto("CAS开发环境地址");
    await loginPage.type("#username", this.username);
    await loginPage.type("#password", this.password);
    await loginPage.click("input[type=submit]");
    await loginPage.waitFor(1000);
    const cookies = await loginPage.cookies();
    cookies.map(v => {
      if (v.name === cookie.name) {
        cookie.value = v.value;
      }
    });
    await casBrowser.close();
    // <!--  拿到cookie后,关闭该实例 -->
    
    // <!-- 打开本地环境流程 -->
    let appBrowser = browser, appPage = page;
    if (appPage) {  // 如果cookie过期,直接在该实例上setCookie,无需新开实例
      await appPage.setCookie(cookie);  
      await appPage.reload()
      this.flag = true;
    } else {
      appBrowser = await puppeteer.launch({
        headless: false,
      });
      appPage = (await appBrowser.pages())[0];  
      await appPage.setCookie(cookie); //设置cookie
      const {
        width,
        height
      } = await appPage.evaluate(() => {
        return {
          width: window.outerWidth,
          height: window.outerHeight
        };
      });
      await appPage.setViewport({
        width,
        height
      });
      await appPage.goto("http://localhost:3000/");
    }
    // <!-- 本地流程结束 -->
    
    //监听请求
    await appPage.on("response", async (res) => {
      const status = res.status();
      if (status === 302 && this.flag) {    //cookie过期后,服务接口会重定向到cas登录页,所以只需拦截302即可知道cookie是否过期,当然也可和后端约定一个状态,如401
        this.flag = false;
        this.init(appPage, appBrowser);  //cookie过期,重新模拟登录cas获取新的cookie
        }
    })
  }
}

const {
  username = "admin", password = "123456"
} = getRole(role) || {}  //默认登录admin账号

const launch = new Launch(username, password);

launch.init()

上述setCookie是根据我司统一cookie而写,具体情况具体分析

Cookie

简单明了的完成了拿cookie的过程,省去了繁琐的设置代理去登录cas的过程, 。至此,我们完成了puppeteer在实际开发中的应用,大大减少了登录,切换账号的过程。

其实我用QuickTime录制了一段小视频,但是转换gif的时候转不了,不知道为啥,所以gif图就不传了,自行脑补哈。

你们有没有发现我在告辞后面用了7个., 皮一下确实很开心,哈哈哈 点我点我 .......

关于puppeteer ssr的场景就不多说了,一般都会在服务端做掉,关于react ssr的例子,详见 嘿嘿嘿