ホームページ  >  記事  >  ウェブフロントエンド  >  Puppeteer ライブラリを使用して Node でポスターを生成する方法の簡単な分析 (実装計画の共有)

Puppeteer ライブラリを使用して Node でポスターを生成する方法の簡単な分析 (実装計画の共有)

青灯夜游
青灯夜游転載
2022-01-18 19:26:444024ブラウズ

Node を使用してポスターを生成するにはどうすればよいですか?次の記事では、Node Puppeteer を使用してポスターを生成する方法を紹介します。

Puppeteer ライブラリを使用して Node でポスターを生成する方法の簡単な分析 (実装計画の共有)

前回の記事で、数日前に html2canvas を使用しているときに多くの互換性の問題に遭遇し、バケツを持って逃げそうになったことを書きました。その後、コメント エリアの偉い人の指導により、操作が簡単で再利用性の高いポスター生成ソリューションである Node Puppeteer がポスターを生成するソリューションを発見しました。

主な設計アイデアは次のとおりです: ポスターを生成するためのインターフェイスにアクセスするこのインターフェイスは、Puppeteer を通じて受信アドレスにアクセスし、対応する要素のスクリーンショットを返します。


Puppeteer によるポスター生成の Canvas 生成と比較した利点は何ですか:

    ブラウザの互換性、プラットフォームの互換性、その他の問題はありません。
  • コードは再利用性が高く、h5、ミニ プログラム、アプリのポスターの生成に使用できます。
  • 最適化演算領域が大きくなります。ポスターを生成するインターフェースに変更されたため、サーバーの追加やキャッシュの追加など、サーバー側のさまざまな方法を使用して応答速度を最適化できます

puppeteer Introduction

Puppeteer は、DevTools プロトコルを通じて Chromium または Chrome を制御するための高レベル API を提供する

Nodejs ライブラリです。 Puppeteer はデフォルトではヘッドレス モード、つまり「ヘッドレス」モードで実行されますが、設定 headless:false を変更することで「ヘッド」モードを実行できます。 ブラウザで手動で行うことのほとんどは、Puppeteer を使用して行うことができます。例をいくつか示します。

    ページ PDF またはスクリーンショットを生成します。
  • SPA (シングル ページ アプリケーション) をクロールし、事前レンダリングされたコンテンツ (つまり、「SSR」 (サーバー サイド レンダリング)) を生成します。
  • フォームの自動送信、UI テスト、キーボード入力などを実行します。
  • 常に更新される自動テスト環境を作成します。最新の JavaScript とブラウザ機能を使用して、最新バージョンの Chrome でテストを直接実行します。
  • Web サイトのタイムライン トレースをキャプチャして、パフォーマンスの問題の分析に役立てます。
  • ブラウザ拡張機能をテストします。

ソリューションの実装

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
    }
};

    puppeteer.launch([options]): ブラウザを起動します
  • browser.newPage(): タブ ページを作成します
  • page.goto (url[, options]): ページに移動します
  • page.setViewport(viewport): ページを開くウィンドウを指定します
  • page.$(selector): 要素の選択
  • elementHandle .screenshot([オプション]): スクリーンショット。エンコード属性では、戻り値が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 は 500 ミリ秒待機する必要があるため、インターフェイスの応答時間が長くなることがわかります。シナリオでは、多くの場合待つ必要がないため、Delay をカプセル化し、待ち時間をカスタマイズできます。たとえば、ポスター ページは背景画像と QR コード画像のみをレンダリングします。ページが読み込みをトリガーするとき、ページはすでに読み込まれています。待ち時間はありません。待ち時間をスキップするには、0 を渡すことができます。

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

このメソッドでは不十分で、ページが特定の時間に終了するようにパペッティアに通知する必要がある場合は、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

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

以上がPuppeteer ライブラリを使用して Node でポスターを生成する方法の簡単な分析 (実装計画の共有)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。