Node を使用してポスターを生成するにはどうすればよいですか?次の記事では、Node Puppeteer を使用してポスターを生成する方法を紹介します。
前回の記事で、数日前に html2canvas を使用しているときに多くの互換性の問題に遭遇し、バケツを持って逃げそうになったことを書きました。その後、コメント エリアの偉い人の指導により、操作が簡単で再利用性の高いポスター生成ソリューションである Node Puppeteer がポスターを生成するソリューションを発見しました。
主な設計アイデアは次のとおりです: ポスターを生成するためのインターフェイスにアクセスするこのインターフェイスは、Puppeteer を通じて受信アドレスにアクセスし、対応する要素のスクリーンショットを返します。1. 単純なインターフェイスを作成します
Express は Aシンプルで柔軟なnode.js Webアプリケーションフレームワーク。 Express を使用して単純なノード サービスを作成し、インターフェイスを定義し、スクリーンショットに必要な構成アイテムを受け取り、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 } };
#3. 最適化
1. リクエスト時間の最適化
page.goto(url[, options]) メソッドの設定項目 waitUntil は、実行時の状態を示します。デフォルトでは、load イベントがトリガーされたときです。await page.goto(url, { waitUntil: [ 'load', //页面“load” 事件触发 'domcontentloaded', //页面 “DOMcontentloaded” 事件触发 'networkidle0', //在 500ms 内没有任何网络连接 'networkidle2' //在 500ms 内网络连接个数不超过 2 个 ] });
const waitTime = (n) => new Promise((r) => setTimeout(r, n)); //省略部分代码 await page.goto(opt.url); await waitTime(opt.waitTime || 0);
await page.waitForSelector("#end")
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 教程!!
以上がPuppeteer ライブラリを使用して Node でポスターを生成する方法の簡単な分析 (実装計画の共有)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。