Heim  >  Artikel  >  Web-Frontend  >  Eine kurze Analyse der Verwendung der Puppeteer-Bibliothek zum Generieren von Postern in Node (Freigabe von Implementierungsplänen)

Eine kurze Analyse der Verwendung der Puppeteer-Bibliothek zum Generieren von Postern in Node (Freigabe von Implementierungsplänen)

青灯夜游
青灯夜游nach vorne
2022-01-18 19:26:444023Durchsuche

Wie verwende ich Node zum Generieren von Postern? Im folgenden Artikel erfahren Sie, wie Sie mit Node+Puppeteer Poster erstellen. Ich hoffe, er hilft Ihnen weiter!

Eine kurze Analyse der Verwendung der Puppeteer-Bibliothek zum Generieren von Postern in Node (Freigabe von Implementierungsplänen)

Ich habe im vorherigen Artikel geschrieben, dass ich vor ein paar Tagen bei der Verwendung von html2canvas auf viele Kompatibilitätsprobleme gestoßen bin und fast mit dem Eimer davongelaufen bin. Dann entdeckte ich unter Anleitung der Experten im Kommentarbereich eine Lösung zur Postererstellung, die einfach zu bedienen und hochgradig wiederverwendbar ist – Node+Puppeteer generiert Poster.

Die Hauptidee des Designs ist: Zugriff auf die Schnittstelle zum Generieren von Postern. Die Schnittstelle greift über Puppeteer auf die eingehende Adresse zu und gibt einen Screenshot des entsprechenden Elements zurück.

Was sind die Vorteile der Postererstellung mit Puppeteer gegenüber der Canvas-Generierung:

  • Es gibt keine Browserkompatibilität, Plattformkompatibilität und andere Probleme.
  • Der Code ist in hohem Maße wiederverwendbar und kann zum Generieren von Postern für h5, Miniprogramme und Apps verwendet werden.
  • Der Optimierungsbetriebsraum ist größer. Da die Schnittstelle zum Generieren von Postern geändert wurde, können verschiedene serverseitige Methoden verwendet werden, um die Antwortgeschwindigkeit zu optimieren, z. B.: Hinzufügen von Servern, Hinzufügen von Caches

Einführung in Puppeteer

Puppeteer ist ein Nodejs Bibliothek, die eine High-Level-API zur Steuerung von Chromium oder Chrome über das DevTools-Protokoll bereitstellt. Puppeteer läuft standardmäßig im Headless-Modus, also im „Headless“-Modus, Sie können den „Headed“-Modus jedoch ausführen, indem Sie die Konfiguration headless:false ändern. Die meisten Dinge, die Sie manuell in einem Browser tun würden, können Sie mit Puppeteer erledigen! Hier einige Beispiele:

  • Seiten-PDF oder Screenshot erstellen.
  • Erfassen Sie SPA (Single Page Application) und generieren Sie vorgerenderten Inhalt (d. h. „SSR“ (Server Side Rendering)).
  • Formulare automatisch senden, UI-Tests durchführen, Tastatureingaben durchführen usw.
  • Erstellen Sie eine automatisierte Testumgebung, die ständig aktualisiert wird. Führen Sie Tests direkt in der neuesten Version von Chrome mit den neuesten JavaScript- und Browserfunktionen durch.
  • Erfassen Sie den Timeline-Trace der Website, um Leistungsprobleme zu analysieren.
  • Testen Sie Browsererweiterungen.

Lösungsimplementierung

1. Schreiben Sie eine einfache Schnittstelle

Express ist ein einfaches und flexibles Node.js-Webanwendungsframework. Verwenden Sie Express, um einen einfachen Knotendienst zu schreiben, eine Schnittstelle zu definieren, die für Screenshots erforderlichen Konfigurationselemente zu empfangen und diese an Puppenspieler weiterzuleiten.

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. Erstellen Sie ein Screenshot-Modul.

Einen Browser öffnen

    browser.newPage(): Eine Registerkarte erstellen
  • page.goto(url[, Optionen]): Zu einer Seite navigieren
  • page.setViewport(viewport): Geben Sie das Fenster zum Öffnen der Seite an
  • Seite .$ (Selektor): Elementauswahl
  • elementHandle.screenshot([Optionen]): Screenshot. Das Codierungsattribut kann angeben, dass der Rückgabewert Base64 oder Buffer
  • browser.close() ist: Schließen Sie den Browser und die Registerkarte „Optimierung“1. goto( Das Konfigurationselement „waitUntil“ der Methode url[, Optionen]) gibt den Status an, in dem die Ausführung abgeschlossen ist. Der Standardwert ist, wenn das Ladeereignis ausgelöst wird. Zu den Ereignissen gehören:
  • 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
        }
    };
  • Wenn Sie die Networkidle0-Lösung verwenden, um auf den Abschluss der Seite zu warten, werden Sie feststellen, dass die Antwortzeit der Schnittstelle länger ist, da Networkidle0 in realen Geschäftsszenarien 500 ms warten muss In vielen Fällen ist keine Wartezeit erforderlich, sodass Sie einen Verzögerer einkapseln können. Die Wartezeit kann individuell angepasst werden. Beispielsweise rendert unsere Posterseite nur ein Hintergrundbild und ein QR-Code-Bild. Wenn die Seite geladen wird, ist sie bereits geladen. Sie können keine Wartezeit angeben.
  •  await page.goto(url, {
         waitUntil: [
             'load', //页面“load” 事件触发
             'domcontentloaded', //页面 “DOMcontentloaded” 事件触发
             'networkidle0', //在 500ms 内没有任何网络连接
             'networkidle2' //在 500ms 内网络连接个数不超过 2 个
         ]
     });
  • Wenn diese Methode nicht zufriedenstellend ist und die Seite den Puppenspieler zu einem bestimmten Zeitpunkt über das Ende informieren muss, können Sie auch page.waitForSelector(selector[, Optionen]) verwenden, um auf das Erscheinen eines bestimmten Elements auf der Seite zu warten . Beispiel: Wenn die Seite einen Vorgang abschließt, fügen Sie ein Element mit der ID = „Ende“ ein und Puppereer wartet darauf, dass dieses Element angezeigt wird.
  •  const waitTime = (n) => new Promise((r) => setTimeout(r, n));
     //省略部分代码
     await page.goto(opt.url);
     await waitTime(opt.waitTime || 0);
Ähnliche Methoden umfassen:

  • 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

更多node相关知识,请访问:nodejs 教程!!

Das obige ist der detaillierte Inhalt vonEine kurze Analyse der Verwendung der Puppeteer-Bibliothek zum Generieren von Postern in Node (Freigabe von Implementierungsplänen). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen