成人怡红院-成人怡红院视频在线观看-成人影视大全-成人影院203nnxyz-美女毛片在线看-美女免费黄

站長資訊網
最全最豐富的資訊網站

淺析Node中怎么利用Puppeteer庫生成海報(實現方案分享)

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

淺析Node中怎么利用Puppeteer庫生成海報(實現方案分享)

之前文章寫了一下前幾天因為使用了 html2canvas 碰到了很多兼容性問題,差點提桶跑路。然后經過評論區大佬們指導,發現了一個操作簡單,復用性高的海報生成方案—— Node+Puppeteer生成海報

主要的設計思路為:訪問生成海報的接口,接口通過Puppeteer去訪問傳入的地址,將對應的元素截圖返回。

Puppeteer 生成海報相對于 Canvas 生成的優勢有哪些:

  • 沒有瀏覽器兼容,平臺兼容等問題。
  • 代碼復用性高,h5、小程序、app的生成海報服務都可以使用。
  • 優化操作空間更大。因為改成了接口生成海報的形式,可以使用各種服務端的方式去優化響應速度,比如:加服務器、加緩存

puppeteer介紹

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

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

方案實現

1. 寫一個簡單的接口

Express 是一個簡潔而靈活的 node.js Web應用框架。使用express寫一個簡單的node服務,定義一個接口,接收截圖所需的配置項傳遞給puppeteer。

const express = require('express') const createError = require("http-errors") const app = express() // 中間件--json化入參 app.use(express.json()) app.post('/api/getShareImg', (req, res) => {     // 業務邏輯 }) // 錯誤攔截 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) }) // 啟動服務監聽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. 創建一個截圖模塊

打開一個瀏覽器 => 打開一個標簽頁 => 截圖 => 關閉瀏覽器

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]):啟動一個瀏覽器
  • browser.newPage():創建一個標簽頁
  • page.goto(url[, options]):導航到某個頁面
  • page.setViewport(viewport):制定打開頁面的窗口
  • page.$(selector):元素選擇
  • elementHandle.screenshot([options]):截圖。其中encoding屬性可以指定返回值是base64或Buffer
  • browser.close():關閉瀏覽器及標簽頁

3. 優化

1. 請求時間優化

page.goto(url[, options]) 方法的配置項 waitUntil 表示什么狀態下算執行完畢, 默認是load事件觸發時。事件包括:

 await page.goto(url, {      waitUntil: [          'load', //頁面“load” 事件觸發          'domcontentloaded', //頁面 “DOMcontentloaded” 事件觸發          'networkidle0', //在 500ms 內沒有任何網絡連接          'networkidle2' //在 500ms 內網絡連接個數不超過 2 個      ]  });

如果使用 networkidle0 的方案等待頁面完成,會發現接口的響應時間會比較長, 因為 networkidle0 需要等待500ms,真實業務場景下很多情況下不需要等待,所以可以封裝一個延時器,可以自定義等待時間。比如我們的海報頁只是渲染一個背景圖跟一個二維碼圖片,頁面觸發 load 時已經加載完成了,不需要等待時間,可以傳入0跳過等待時間。

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

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

 await page.waitForSelector("#end")

類似的方法共包括:

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

2. 啟動項優化

Chromium啟動時還會開啟很多不需要的功能,可以通過參數禁用某些啟動項。

    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. 復用瀏覽器

因為每次接口被調用都啟動了一個瀏覽器,截圖之后關閉了這個瀏覽器,造成了資源的浪費,并且啟動瀏覽器也需要耗費時間。并且同時啟動的瀏覽器過多,程序還會拋出異常。所以使用了連接池:啟動多個瀏覽器,在其中一個瀏覽器下創建標簽頁打開頁面,截圖完成后只關閉標簽頁,保留瀏覽器。下一次請求過來時直接創建標簽頁,達到復用瀏覽器的目的。當瀏覽器使用次數達到一定數目或者一段時間內沒有被使用時就關閉這個瀏覽器。 有大佬已經對generic-pool這個連接池進行了處理,我就直接拿來用了。

const initPuppeteerPool = () => {  if (global.pp) global.pp.drain().then(() => global.pp.clear())  const opt = {    max: 4,//最多產生多少個puppeteer實例 。    min: 1,//保證池中最少有多少個puppeteer實例存活    testOnBorrow: true,// 在將實例提供給用戶之前,池應該驗證這些實例。    autostart: false,//是不是需要在池初始化時初始化實例    idleTimeoutMillis: 1000 * 60 * 60,//如果一個實例60分鐘都沒訪問就關掉他    evictionRunIntervalMillis: 1000 * 60 * 3,//每3分鐘檢查一次實例的訪問狀態    maxUses: 2048,//自定義的屬性:每一個 實例 最大可重用次數。    validator: () => Promise.resolve(true)  }  const factory = {    create: () =>      puppeteer.launch({        //啟動參數參考第二條      }).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)  // 重寫了原有池的消費實例的方法。添加一個實例使用次數的增加  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 => {          // 不管業務方使用實例成功與后都表示一下實例消費完成          pool.release(resource)          return result        },        err => {          pool.release(resource)          throw err        }      )  }  return pool; } global.pp = initPuppeteerPool()

4. 優化接口防止圖片重復生成

用同一組參數重復調用時每次都會開啟一個瀏覽器進程去截圖,可以使用緩存機制優化重復的請求。可以通過傳入唯一的key作為標識位(比如用戶id+活動id),將圖片base64存入redis或者寫入內存中。當接口被請求時先查看緩存里是否已經生成過,如果生成過就直接從緩存取。否則就走生成海報的流程。

結尾

這個方案目前已經開始在項目里試運行了,這對于我一個前端開發來說簡直太友好了,再也不用在小程序里一步一步去繪制canvas,不用考慮資源跨域,也不用考慮微信瀏覽器、各種自帶瀏覽器的兼容問題。省下了時間可以讓我寫這篇文章。其次,我比較擔心的還是性能問題,因為只有在分享的動作才會觸發,并發較小,目前使用還未暴露出性能的問題,有了解的大佬們可以指導我一下可以進一步優化或者預防的點。

代碼

完整代碼查看:github

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

贊(0)
分享到: 更多 (0)
?
網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
欧美老熟妇乱子伦视频| 人妻无码久久久久久久久久久| √天堂资源地址在线官网| 日日摸夜夜添夜夜添无码免费视频 | 亚洲AV日韩AV综合AⅤXXX| 亚洲成AV人片在线观看橙子| 亚洲人成色777777精品音频| 亚洲综合激情另类专区| 性一交一乱一色一视频| 少妇人妻无码精品视频APP| 欧美性狂猛XXXXX深喉| 日韩AV一卡2卡3卡4卡新区乱| 他扒开内裤把舌头进去会有影响吗 | 无码中文字幕AV免费放| 少妇大叫太大太爽受不了| 无人高清视频免费观看在线| 亚洲国产精品久久久就秋霞| 真实国产乱啪福利露脸| 锕锕锕锕锕锕锕好痛免费网址 | 无码人妻丰满熟妇片毛片 | 激情欧美成人久久综合| 没带罩子让他捏了一节课| 人妻无码一区二区三区免费| 无码日韩精品一区二区免费| 亚洲欧洲第一的日产SUV| 99久RE热视频这只有精品6| 丰满少妇偷人51视频在线观看| 国产又爽又黄又刺激的视频| 国产精品无码专区在线观看| 精品无码三级在线观看视频| 精品久久久噜噜噜久久久| 好男人影视官网在线WWW| 国产亚洲精品第一综合另类灬| 久久久久久精品一区二区三区日本| 精品久久久久久无码专区不卡 | 黑人巨大超大VIDEOSGRA| 噜噜噜噜噜18禁私人影视| 日韩人妻无码一区二区三区综合部| 香港经典A毛片免费观看变态| 天美传媒MV在线看免费下载安装| 亚洲成人无码AV| CHINESE熟女老女人HD视| 国产精品自在拍首页视频8| 久久综合精品国产一区二区三区无| 日本VS亚洲VS韩国一区三区| 亚洲AV日韩精品久久久久久久| 中文日产幕无限码一区有限公司| 国产69精品久久久久观看软件| 精品无码久久久久久久久久| 欧美一级内射黑人内射| 亚洲AV无码AV有码AV| B里可以放多少个鸡蛋| 国产我和子的与子乱视频| 男人J桶进女人P无遮挡的图片| 偷偷鲁2019丫丫久久| 中国大陆高清AⅤ毛片| 国产丰滿老熟女多毛hD| 国产日韩AV免费无码一区二区三| 国产女人高潮抽搐喷水嗷嗷叫| 美女扒开奶罩露出奶头视频网站| 天美传媒MV免费观看完整| 真人荫道口图片100张| 国产成人亚洲精品无码青| 蜜臀AV午夜福利一区二区三区| 无码免费一区二区三区免费播放| 6080电影网站| 大BBW大BBW大BBW| 久久无码人妻一区二区三区| 天天躁日日躁狠狠躁婷婷| 最新中文字幕AV无码专区| 国产精品亚洲А∨天堂2018| 欧美高潮抽搐喷水大叫| 少妇厨房愉情理伦BD在线观看| 一本久久知道综合久久| 国产精品国产自线拍免费软件| 狠狠躁夜夜躁人人爽天天| 人妻无码久久久久久久久久久| 亚洲无人区码一二三四区别| 国产成人AV综合亚洲色欲美女 | 日日摸夜夜添夜夜添无码免费视频| 一本色道久久88加勒比—综合| 国产清纯白嫩初高生在线观看| 人伦亲情父母儿女的句子简短| 野花高清完整版免费观看视频大全 | 人成AAAAA毛天堂片| 曰韩无码AV一区二区免费| 国产午夜精品一区二区三区老| 搡老女人P老熟妇老熟女| 中文字幕一本性无码| 娇小性XXXX摘花HD| 婷婷五月六月综合缴情 | 国产三级精品三级在线专区 | 波多野结衣迅雷下载| 蜜桃AV自慰久久久久免费网站| 亚洲AV一二三区成人影片| 国产A级毛片久久久久久精品| 欧美精品偷自拍另类在线观看 | 亚洲AV成人片在线观看香蕉资源| 大伊香蕉精品一区视频在线| 欧美变态口味重另类在线视频| 四虎成人精品无码永久在线| H无码精品动漫在线观看| 老色鬼久久亚洲AV综合0男男| 亚洲成人AV在线播放| 国产无套粉嫩白浆在线观看| 十八禁无遮无拦视频免费| 爆乳无码AV一区二区三区小说| 男人边吃奶边挵进去呻吟动态图 | 无码国产精品一区二区免费式芒果| 边摸边吃奶边做爽视频免费| 欧美疯狂做受XXXX高潮| 中文字幕不卡乱偷在线观看 | 野花香日本大全免费观看| 国语对白露脸XXXXXX| 性欧美VIDEOFREE高清成| 国产成人无码AV在线播放无广告| 日韩AV无码中文无码不卡电影| MM131美女大尺度私密照尤果 | 铜铜铜铜铜铜铜铜铜好大好深色| 村长趴在小雪身上耕耘视频| 区二区三区在线 | 欧洲| 9色国产深夜内射| 女人下边被添全过视频| 亚洲AV极品无码专区在线观看| 国产CHINESE中国HDXX| 少女たちよ在线观看动漫| 丁香花在线观看视频在线 | 美女脱精光手不挡图片| 孕妇滴着奶水做着爱A| 国产白嫩漂亮美女在线观看| 色综合久久久久无码专区| 第一次接黑人嫖客| 色悠久久久久综合网伊| 夫妇交换聚会群4P疯狂大战视频| 日产乱码一二三区别免费一| 成人无码区免费∨| 少妇兽交PWWW综合网| 国产SM重口调教在线观看| 天天躁日日躁狠狠躁欧美老妇| 国产盗摄XXXX视频XXXX| 午夜成人无码片在线观看影院| 国产三级多多影院| 亚洲精品偷拍无码不卡AV| 精品人妻一区二区三区四区在线| 亚洲婷婷五月激情综合APP| 久久亚洲熟妇熟女ⅩXXX| 2021亚洲无码| 人妻丰满熟妇aⅴ无码HD| 在线天堂中文最新版WWW| 妺妺窝人销魂体色www| BGMBGMBGM老太太XX一| 日本亚欧乱色视频免费观看| 国产AV成人一区二区三区| 无遮挡边吃摸边吃奶边做| 国内精品伊人久久久久AV影院| 亚洲男同帅GAY片在线观看| 老头边吃奶边弄进去呻吟| 99精品电影一区二区免费看| 人与畜禽CROPROATION| 国产AⅤ精品福利一区二区三区| 无人区码一码二码三码四码| 狠狠做五月深爱婷婷| 伊人久久五月丁香综合中文亚洲| 欧美XXXⅩ重口变态调教| 成熟丰满熟妇高潮XXXXX视频| 无码高潮爽到爆的喷水视频| 护士交换粗吟配乱大交| 在糖心VLOG唐伯虎女主角是谁 | 久久久久久久精品妇女99| 97夜夜澡人人爽人人| 色噜噜精品一区二区三区| 国产在线无码精品电影网| 一区二区三区欧美| 精品人妻无码一区二区色欲产成人 | 久久精品人人做人人爽老司机| 4HC88四虎WWW在线影院| 日韩精品人妻系列无码专区免费| 国产精品亚洲А∨天堂2021 | 久久久久精品精品6精品精品| 99精品热6080YY久久| 四虎成人永久在线精品免费| 交换人生俱乐部全文免费阅读 | 免费精品99久久国产综合精品| 艳妇乳肉豪妇荡乳ⅩXXO电影| 欧美疯狂性受XXXXX另类| 国产精品国产精品国产专区不卡| 亚洲人77777在线观看| 欧美性猛尖ⅩⅩⅩⅩ乱大交| 国产成人亚洲综合无码8| 亚洲自偷图片自拍图片| 日本XXXX18裸体XXXX| 国产精品自在欧美一区| 在线观看免费视频| 日日摸夜夜添夜夜添高潮喷水| 激情综合五月丁香五月激情| 99国精品午夜福利视频不卡| 无码人妻aⅴ一区二区三区99| 榴莲草莓视频黄丝瓜芭乐秋葵| 成人无码影片精品久久久 | 精品国产乱码久久久久久下载|