puppeteer在開發過程中的實踐

Whirring發表於2018-07-27

上篇文章我們談了一下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的例子,詳見 嘿嘿嘿

相關文章