Maison > Article > interface Web > Une brève analyse de la façon d'utiliser la bibliothèque Puppeteer pour générer des affiches dans Node (partage du plan de mise en œuvre)
Comment utiliser Node pour générer des affiches ? L'article suivant vous présentera comment utiliser Node+Puppeteer pour générer des affiches. J'espère qu'il vous sera utile !
J'ai écrit dans l'article précédent que j'avais rencontré de nombreux problèmes de compatibilité lors de l'utilisation de html2canvas il y a quelques jours, et que j'avais failli m'enfuir avec le bucket. Puis, grâce aux conseils des experts de la zone de commentaires, j'ai découvert une solution de génération d'affiches simple à utiliser et hautement réutilisable : Node+Puppeteer génère des affiches.
L'idée principale de conception est la suivante : accéder à l'interface de génération d'affiches. L'interface accède à l'adresse entrante via Puppeteer et renvoie une capture d'écran de l'élément correspondant.
Quels sont les avantages de la génération d'affiches avec Puppeteer par rapport à la génération Canvas :
Puppeteer est un Nodejs bibliothèque, qui fournit une API de haut niveau pour contrôler Chromium ou Chrome via le protocole DevTools. Puppeteer fonctionne en mode sans tête par défaut, c'est-à-dire en mode "sans tête", mais vous pouvez exécuter le mode "avec tête" en modifiant la configuration headless:false. La plupart des choses que vous feriez manuellement dans un navigateur peuvent être réalisées avec Puppeteer ! Voici quelques exemples :
1. Écrivez une interface simple
Express est un framework d'application Web node.js simple et flexible. Utilisez express pour écrire un service de nœud simple, définir une interface, recevoir les éléments de configuration requis pour les captures d'écran et les transmettre au marionnettiste.
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. Créer un module de capture d'écran
Ouvrir un navigateur=> Ouvrir un onglet=> Fermer le navigateur
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 } };
3. Optimisation
1. goto( L'élément de configuration waitUntil de la méthode url[, options]) indique l'état dans lequel l'exécution est terminée. La valeur par défaut est lorsque l'événement de chargement est déclenché. Les événements incluent :
await page.goto(url, { waitUntil: [ 'load', //页面“load” 事件触发 'domcontentloaded', //页面 “DOMcontentloaded” 事件触发 'networkidle0', //在 500ms 内没有任何网络连接 'networkidle2' //在 500ms 内网络连接个数不超过 2 个 ] });Si vous utilisez la solution networkidle0 pour attendre que la page soit terminée, vous constaterez que le temps de réponse de l'interface sera plus long, car networkidle0 doit attendre 500 ms, dans des scénarios commerciaux réels. pas besoin d'attendre dans de nombreux cas, vous pouvez donc encapsuler un retardateur. Le temps d'attente peut être personnalisé. Par exemple, notre page d'affiche ne restitue qu'une image de fond et une image de code QR. Lorsque la page déclenche le chargement, elle a déjà été chargée. Vous pouvez passer 0 pour ignorer le temps d'attente.
const waitTime = (n) => new Promise((r) => setTimeout(r, n)); //省略部分代码 await page.goto(opt.url); await waitTime(opt.waitTime || 0);Si cette méthode n'est pas satisfaisante et que la page doit avertir le marionnettiste de la fin à une certaine heure, vous pouvez également utiliser page.waitForSelector(selector[, options]) pour attendre qu'un élément spécifié sur la page apparaisse . Par exemple : lorsque la page termine une opération, insérez un élément avec id="end", et puppereer attend que cet élément apparaisse.
await page.waitForSelector("#end")Les méthodes similaires incluent :
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 教程!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!