久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長(zhǎng)資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      淺析Node中怎么利用Puppeteer庫(kù)生成海報(bào)(實(shí)現(xiàn)方案分享)

      怎么利用Node生成海報(bào)?下面本篇文章給大家介紹一下使用Node+Puppeteer生成海報(bào)的方法,希望對(duì)大家有所幫助!

      淺析Node中怎么利用Puppeteer庫(kù)生成海報(bào)(實(shí)現(xiàn)方案分享)

      之前文章寫了一下前幾天因?yàn)槭褂昧?html2canvas 碰到了很多兼容性問題,差點(diǎn)提桶跑路。然后經(jīng)過評(píng)論區(qū)大佬們指導(dǎo),發(fā)現(xiàn)了一個(gè)操作簡(jiǎn)單,復(fù)用性高的海報(bào)生成方案—— Node+Puppeteer生成海報(bào) 。

      主要的設(shè)計(jì)思路為:訪問生成海報(bào)的接口,接口通過Puppeteer去訪問傳入的地址,將對(duì)應(yīng)的元素截圖返回。

      Puppeteer 生成海報(bào)相對(duì)于 Canvas 生成的優(yōu)勢(shì)有哪些:

      • 沒有瀏覽器兼容,平臺(tái)兼容等問題。
      • 代碼復(fù)用性高,h5、小程序、app的生成海報(bào)服務(wù)都可以使用。
      • 優(yōu)化操作空間更大。因?yàn)楦某闪私涌谏珊?bào)的形式,可以使用各種服務(wù)端的方式去優(yōu)化響應(yīng)速度,比如:加服務(wù)器、加緩存

      puppeteer介紹

      Puppeteer 是一個(gè) Nodejs 庫(kù),它提供了一個(gè)高級(jí) API 來通過 DevTools 協(xié)議控制 Chromium 或 Chrome。Puppeteer 默認(rèn)以 headless 模式運(yùn)行即“無頭”模式,但是可以通過修改配置 headless:false 運(yùn)行“有頭”模式。 在瀏覽器中手動(dòng)執(zhí)行的絕大多數(shù)操作都可以使用 Puppeteer 來完成! 下面是一些示例:

      • 生成頁(yè)面 PDF或者截圖。
      • 抓取 SPA(單頁(yè)應(yīng)用)并生成預(yù)渲染內(nèi)容(即“SSR”(服務(wù)器端渲染))。
      • 自動(dòng)提交表單,進(jìn)行 UI 測(cè)試,鍵盤輸入等。
      • 創(chuàng)建一個(gè)時(shí)時(shí)更新的自動(dòng)化測(cè)試環(huán)境。 使用最新的 JavaScript 和瀏覽器功能直接在最新版本的Chrome中執(zhí)行測(cè)試。
      • 捕獲網(wǎng)站的 timeline trace,用來幫助分析性能問題。
      • 測(cè)試瀏覽器擴(kuò)展。

      方案實(shí)現(xiàn)

      1. 寫一個(gè)簡(jiǎn)單的接口

      Express 是一個(gè)簡(jiǎn)潔而靈活的 node.js Web應(yīng)用框架。使用express寫一個(gè)簡(jiǎn)單的node服務(wù),定義一個(gè)接口,接收截圖所需的配置項(xiàng)傳遞給puppeteer。

      const express = require('express') const createError = require("http-errors") const app = express() // 中間件--json化入?yún)?app.use(express.json()) app.post('/api/getShareImg', (req, res) => {     // 業(yè)務(wù)邏輯 }) // 錯(cuò)誤攔截 app.use(function(req, res, next) {     next(createError(404)); }); app.use(function(err, req, res, next) {     let result = {         code: 0,         msg: err.message,         err: err.stack     }     res.status(err.status || 500).json(result) }) // 啟動(dòng)服務(wù)監(jiān)聽7000端口 const server = app.listen(7000, '0.0.0.0', () => {     const host = server.address().address;     const port = server.address().port;     console.log('app start listening at http://%s:%s', host, port); });

      2. 創(chuàng)建一個(gè)截圖模塊

      打開一個(gè)瀏覽器 => 打開一個(gè)標(biāo)簽頁(yè) => 截圖 => 關(guān)閉瀏覽器

      const puppeteer = require("puppeteer");  module.exports = async (opt) => {     try {         const browser = await puppeteer.launch();         const page = await browser.newPage();         await page.goto(opt.url, {             waitUntil: ['networkidle0']         });         await page.setViewport({             width: opt.width,             height: opt.height,         });         const ele = await page.$(opt.ele);         const base64 = await ele.screenshot({             fullPage: false,             omitBackground: true,             encoding: 'base64'         });         await browser.close();         return 'data:image/png;base64,'+ base64     } catch (error) {         throw error     } };
      • puppeteer.launch([options]):?jiǎn)?dòng)一個(gè)瀏覽器
      • browser.newPage():創(chuàng)建一個(gè)標(biāo)簽頁(yè)
      • page.goto(url[, options]):導(dǎo)航到某個(gè)頁(yè)面
      • page.setViewport(viewport):制定打開頁(yè)面的窗口
      • page.$(selector):元素選擇
      • elementHandle.screenshot([options]):截圖。其中encoding屬性可以指定返回值是base64或Buffer
      • browser.close():關(guān)閉瀏覽器及標(biāo)簽頁(yè)

      3. 優(yōu)化

      1. 請(qǐng)求時(shí)間優(yōu)化

      page.goto(url[, options]) 方法的配置項(xiàng) waitUntil 表示什么狀態(tài)下算執(zhí)行完畢, 默認(rèn)是load事件觸發(fā)時(shí)。事件包括:

       await page.goto(url, {      waitUntil: [          'load', //頁(yè)面“l(fā)oad” 事件觸發(fā)          'domcontentloaded', //頁(yè)面 “DOMcontentloaded” 事件觸發(fā)          'networkidle0', //在 500ms 內(nèi)沒有任何網(wǎng)絡(luò)連接          'networkidle2' //在 500ms 內(nèi)網(wǎng)絡(luò)連接個(gè)數(shù)不超過 2 個(gè)      ]  });

      如果使用 networkidle0 的方案等待頁(yè)面完成,會(huì)發(fā)現(xiàn)接口的響應(yīng)時(shí)間會(huì)比較長(zhǎng), 因?yàn)?networkidle0 需要等待500ms,真實(shí)業(yè)務(wù)場(chǎng)景下很多情況下不需要等待,所以可以封裝一個(gè)延時(shí)器,可以自定義等待時(shí)間。比如我們的海報(bào)頁(yè)只是渲染一個(gè)背景圖跟一個(gè)二維碼圖片,頁(yè)面觸發(fā) load 時(shí)已經(jīng)加載完成了,不需要等待時(shí)間,可以傳入0跳過等待時(shí)間。

       const waitTime = (n) => new Promise((r) => setTimeout(r, n));  //省略部分代碼  await page.goto(opt.url);  await waitTime(opt.waitTime || 0);

      如果這種方式不能滿足,需要頁(yè)面在某個(gè)時(shí)機(jī)通知puppeteer結(jié)束,還可以使用 page.waitForSelector(selector[, options]) 等待頁(yè)面某個(gè)指定的元素出現(xiàn)。比如:頁(yè)面執(zhí)行完某個(gè)操作時(shí),插入一個(gè) id="end" 的元素,puppereer 等待這個(gè)元素出現(xiàn)。

       await page.waitForSelector("#end")

      類似的方法共包括:

      • page.waitForXPath(xpath[, options]):等待 xPath 對(duì)應(yīng)的元素出現(xiàn)在頁(yè)面中。
      • page.waitForSelector(selector[, options]):等待指定的選擇器匹配的元素出現(xiàn)在頁(yè)面中,如果調(diào)用此方法時(shí)已經(jīng)有匹配的元素,那么此方法立即返回。
      • page.waitForResponse(urlOrPredicate[, options]):等待指定的響應(yīng)結(jié)束。
      • page.waitForRequest(urlOrPredicate[, options]):等待指定的響應(yīng)出現(xiàn)。
      • page.waitForFunction(pageFunction[, options[, …args]]):等待某個(gè)方法執(zhí)行。
      • page.waitFor(selectorOrFunctionOrTimeout[, options[, …args]]):此方法相當(dāng)于上面幾個(gè)方法的選擇器,根據(jù)第一個(gè)參數(shù)的不同結(jié)果不同,比如:傳入一個(gè)string類型,會(huì)判斷是不是xpath或者selector,此時(shí)相當(dāng)于waitForXPath或waitForSelector。

      2. 啟動(dòng)項(xiàng)優(yōu)化

      Chromium啟動(dòng)時(shí)還會(huì)開啟很多不需要的功能,可以通過參數(shù)禁用某些啟動(dòng)項(xiàng)。

          const browser = await puppeteer.launch({         headless: true,         slowMo: 0,         args: [             '--no-zygote',             '--no-sandbox',             '--disable-gpu',             '--no-first-run',             '--single-process',             '--disable-extensions',             "--disable-xss-auditor",             '--disable-dev-shm-usage',             '--disable-popup-blocking',             '--disable-setuid-sandbox',             '--disable-accelerated-2d-canvas',             '--enable-features=NetworkService',         ]     });

      3. 復(fù)用瀏覽器

      因?yàn)槊看谓涌诒徽{(diào)用都啟動(dòng)了一個(gè)瀏覽器,截圖之后關(guān)閉了這個(gè)瀏覽器,造成了資源的浪費(fèi),并且啟動(dòng)瀏覽器也需要耗費(fèi)時(shí)間。并且同時(shí)啟動(dòng)的瀏覽器過多,程序還會(huì)拋出異常。所以使用了連接池:?jiǎn)?dòng)多個(gè)瀏覽器,在其中一個(gè)瀏覽器下創(chuàng)建標(biāo)簽頁(yè)打開頁(yè)面,截圖完成后只關(guān)閉標(biāo)簽頁(yè),保留瀏覽器。下一次請(qǐng)求過來時(shí)直接創(chuàng)建標(biāo)簽頁(yè),達(dá)到復(fù)用瀏覽器的目的。當(dāng)瀏覽器使用次數(shù)達(dá)到一定數(shù)目或者一段時(shí)間內(nèi)沒有被使用時(shí)就關(guān)閉這個(gè)瀏覽器。 有大佬已經(jīng)對(duì)generic-pool這個(gè)連接池進(jìn)行了處理,我就直接拿來用了。

      const initPuppeteerPool = () => {  if (global.pp) global.pp.drain().then(() => global.pp.clear())  const opt = {    max: 4,//最多產(chǎn)生多少個(gè)puppeteer實(shí)例 。    min: 1,//保證池中最少有多少個(gè)puppeteer實(shí)例存活    testOnBorrow: true,// 在將實(shí)例提供給用戶之前,池應(yīng)該驗(yàn)證這些實(shí)例。    autostart: false,//是不是需要在池初始化時(shí)初始化實(shí)例    idleTimeoutMillis: 1000 * 60 * 60,//如果一個(gè)實(shí)例60分鐘都沒訪問就關(guān)掉他    evictionRunIntervalMillis: 1000 * 60 * 3,//每3分鐘檢查一次實(shí)例的訪問狀態(tài)    maxUses: 2048,//自定義的屬性:每一個(gè) 實(shí)例 最大可重用次數(shù)。    validator: () => Promise.resolve(true)  }  const factory = {    create: () =>      puppeteer.launch({        //啟動(dòng)參數(shù)參考第二條      }).then(instance => {        instance.useCount = 0;        return instance;      }),    destroy: instance => {      instance.close()    },    validate: instance => {      return opt.validator(instance).then(valid => Promise.resolve(valid && (opt.maxUses <= 0 || instance.useCount < opt.maxUses)));    }  };  const pool = genericPool.createPool(factory, opt)  const genericAcquire = pool.acquire.bind(pool)  // 重寫了原有池的消費(fèi)實(shí)例的方法。添加一個(gè)實(shí)例使用次數(shù)的增加  pool.acquire = () =>    genericAcquire().then(instance => {      instance.useCount += 1      return instance    })   pool.use = fn => {    let resource    return pool      .acquire()      .then(r => {        resource = r        return resource      })      .then(fn)      .then(        result => {          // 不管業(yè)務(wù)方使用實(shí)例成功與后都表示一下實(shí)例消費(fèi)完成          pool.release(resource)          return result        },        err => {          pool.release(resource)          throw err        }      )  }  return pool; } global.pp = initPuppeteerPool()

      4. 優(yōu)化接口防止圖片重復(fù)生成

      用同一組參數(shù)重復(fù)調(diào)用時(shí)每次都會(huì)開啟一個(gè)瀏覽器進(jìn)程去截圖,可以使用緩存機(jī)制優(yōu)化重復(fù)的請(qǐng)求??梢酝ㄟ^傳入唯一的key作為標(biāo)識(shí)位(比如用戶id+活動(dòng)id),將圖片base64存入redis或者寫入內(nèi)存中。當(dāng)接口被請(qǐng)求時(shí)先查看緩存里是否已經(jīng)生成過,如果生成過就直接從緩存取。否則就走生成海報(bào)的流程。

      結(jié)尾

      這個(gè)方案目前已經(jīng)開始在項(xiàng)目里試運(yùn)行了,這對(duì)于我一個(gè)前端開發(fā)來說簡(jiǎn)直太友好了,再也不用在小程序里一步一步去繪制canvas,不用考慮資源跨域,也不用考慮微信瀏覽器、各種自帶瀏覽器的兼容問題。省下了時(shí)間可以讓我寫這篇文章。其次,我比較擔(dān)心的還是性能問題,因?yàn)橹挥性诜窒淼膭?dòng)作才會(huì)觸發(fā),并發(fā)較小,目前使用還未暴露出性能的問題,有了解的大佬們可以指導(dǎo)我一下可以進(jìn)一步優(yōu)化或者預(yù)防的點(diǎn)。

      代碼

      完整代碼查看:github

      https://github.com/yuwuwu/markdown-code/tree/master/puppeteer%E6%88%AA%E5%9B%BE

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)