溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node中如何用Puppeteer庫(kù)生成海報(bào)

發(fā)布時(shí)間:2022-01-19 09:07:06 來(lái)源:億速云 閱讀:118 作者:iii 欄目:web開(kāi)發(fā)

這篇文章主要介紹“Node中如何用Puppeteer庫(kù)生成海報(bào)”,在日常操作中,相信很多人在Node中如何用Puppeteer庫(kù)生成海報(bào)問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Node中如何用Puppeteer庫(kù)生成海報(bào)”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

Node中如何用Puppeteer庫(kù)生成海報(bào)

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

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

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

  • 沒(méi)有瀏覽器兼容,平臺(tái)兼容等問(wèn)題。

  • 代碼復(fù)用性高,h6、小程序、app的生成海報(bào)服務(wù)都可以使用。

  • 優(yōu)化操作空間更大。因?yàn)楦某闪私涌谏珊?bào)的形式,可以使用各種服務(wù)端的方式去優(yōu)化響應(yīng)速度,比如:加服務(wù)器、加緩存

puppeteer介紹

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

  • 生成頁(yè)面 PDF或者截圖。

  • 抓取 SPA(單頁(yè)應(yīng)用)并生成預(yù)渲染內(nèi)容(即“SSR”(服務(wù)器端渲染))。

  • 自動(dòng)提交表單,進(jìn)行 UI 測(cè)試,鍵盤(pán)輸入等。

  • 創(chuàng)建一個(gè)時(shí)時(shí)更新的自動(dòng)化測(cè)試環(huán)境。 使用最新的 JavaScript 和瀏覽器功能直接在最新版本的Chrome中執(zhí)行測(cè)試。

  • 捕獲網(wǎng)站的 timeline trace,用來(lái)幫助分析性能問(wèn)題。

  • 測(cè)試瀏覽器擴(kuò)展。

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

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

Express 是一個(gè)簡(jiǎn)潔而靈活的 node.js Web應(yīng)用框架。使用express寫(xiě)一個(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)聽(tīng)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è)截圖模塊

打開(kāi)一個(gè)瀏覽器 => 打開(kāi)一個(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):制定打開(kāi)頁(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)沒(méi)有任何網(wǎng)絡(luò)連接
         'networkidle2' //在 500ms 內(nèi)網(wǎng)絡(luò)連接個(gè)數(shù)不超過(guò) 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跳過(guò)等待時(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ì)開(kāi)啟很多不需要的功能,可以通過(guò)參數(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)的瀏覽器過(guò)多,程序還會(huì)拋出異常。所以使用了連接池:?jiǎn)?dòng)多個(gè)瀏覽器,在其中一個(gè)瀏覽器下創(chuàng)建標(biāo)簽頁(yè)打開(kāi)頁(yè)面,截圖完成后只關(guān)閉標(biāo)簽頁(yè),保留瀏覽器。下一次請(qǐng)求過(guò)來(lái)時(shí)直接創(chuàng)建標(biāo)簽頁(yè),達(dá)到復(fù)用瀏覽器的目的。當(dāng)瀏覽器使用次數(shù)達(dá)到一定數(shù)目或者一段時(shí)間內(nèi)沒(méi)有被使用時(shí)就關(guān)閉這個(gè)瀏覽器。 有大佬已經(jīng)對(duì)generic-pool這個(gè)連接池進(jìn)行了處理,我就直接拿來(lái)用了。

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分鐘都沒(méi)訪問(wèn)就關(guān)掉他
   evictionRunIntervalMillis: 1000 * 60 * 3,//每3分鐘檢查一次實(shí)例的訪問(wèn)狀態(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)
 // 重寫(xiě)了原有池的消費(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ì)開(kāi)啟一個(gè)瀏覽器進(jìn)程去截圖,可以使用緩存機(jī)制優(yōu)化重復(fù)的請(qǐng)求??梢酝ㄟ^(guò)傳入唯一的key作為標(biāo)識(shí)位(比如用戶id+活動(dòng)id),將圖片base64存入redis或者寫(xiě)入內(nèi)存中。當(dāng)接口被請(qǐng)求時(shí)先查看緩存里是否已經(jīng)生成過(guò),如果生成過(guò)就直接從緩存取。否則就走生成海報(bào)的流程。

到此,關(guān)于“Node中如何用Puppeteer庫(kù)生成海報(bào)”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI