puppeteerを使ってスクリーンキャプチャをFirebase Storageに保存する


はじめに

Nodejs環境にてFirebaseAdminpuppeteerを用いてWEBページのスクリーンショットを作成し、Firebase Storageに保存するまでの手順と注意点の覚書となります

環境

  • Nodejs

使用するパッケージ

以下をnpmまたはyarnでインストール

  • firebase-admin : Firebase Storageへのアップロードに使用
  • puppeteer : ヘッドレスChrome環境でスクリーンキャプチャを取得するために使用
  • uuid : 画像をダウンロードするためのアクセストークン付与に使用

手順

1. 初期化処理

firebaseAdminの初期化を行います(参考:スタートガイド

firebaseAdmin.ts
import * as firebaseAdmin from 'firebase-admin';
firebaseAdmin.initializeApp({
  credential: firebaseAdmin.credential.cert({
    privateKey: '**************',
    clientEmail: '**************',
    projectId: '**************',
  }),
  storageBucket: '<BUCKET_NAME>.appspot.com'
});
export default firebaseAdmin

2. スクリーンキャプチャの作成

puppeteerを用いてスクリーンキャプチャをメモリ上に作成します

capture.ts
import puppeteer from 'puppeteer'

const capture = async ()=>{
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
   await page.goto('対象のURL', {
       waitUntil : ['load', 'networkidle2']
   });
   // メモリ上に作成
   const buffer = await page.screenshot();

   await browser.close();
}

ここでの注意点としてはpage.gotoのoptionにwaitUntil : ['load', 'networkidle2']を指定しています。この指定がなかった場合、ページによっては真っ白の状態(JS等による構築が未完)でキャプチャを作成してしまう場合があります。そのため、networkidle2を指定することで通信処理等がアイドルになっていることを完了の目安としています。こちらの詳細についてはAPIドキュメントが詳しいです。

3. Firebase Storageへのデータ登録

メモリ上に作成したスクリーンキャプチャをFirebase Storageに登録します。

capture.ts
import puppeteer from 'puppeteer'
+ import firebaseAdmin from './firebaseAdmin' // 1で初期化したもの
+ import { v4 } from 'uuid';

const capture = async ()=>{
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
   await page.goto('対象のURL', {
       waitUntil : ['load', 'networkidle2']
   });
   const buffer = await page.screenshot();

+  const bucket = firebaseAdmin.storage().bucket('バケット名');
+  const savePath = 'Firebase Storageの保存先パス'
+  const bucketFile = bucket.file(savePath)
+  // firebaseStorageDownloadTokensはダウンロードURLを得るために必要 
+  const firebaseStorageDownloadTokens = v4()
+  await bucketFile.save(buffer, {
+     metadata: { 
+          contentType: 'image/png',
+          metadata: {
+              firebaseStorageDownloadTokens
+            }
+      },
+  const downloadUrl = `https://firebasestorage.googleapis.com/v0/b/バケット名/o/${encodeURIComponent(savePath)}?alt=media&token=${firebaseStorageDownloadTokens}`;
+  await browser.close();
}

ここでの注意点ですが、

metadata: {
   firebaseStorageDownloadTokens
}

の記述がない場合、ファイルが正しくダウンロードできなくなります。Firebase condole上では次のように画像が延々とローディングが表示される状態となります
(参考:Firebase Storage - Image preview is permenantly loading)

また、firebaseStorageDownloadTokensuuid形式の必要があるため、uuidのライブラリを使用してトークンを作成しています
Web版のfirebaseを用いてStorageにアップロードする場合には、このアクセストークンは自動的に付与されるようです)

その他注意事項

puppeteerはChroniumをダウンロードする点や使用メモリを結構食う点、LambdaやCloud Functionなどの環境で実行する際には注意が必要になります

最後に

capture.tsの全体のコードは以下のようになります

capture.ts
import puppeteer from 'puppeteer'
import { v4 } from 'uuid';
import firebaseAdmin from './firebaseAdmin'

const capture = async ()=>{
   const browser = await puppeteer.launch();
   const page = await browser.newPage();
   await page.goto('対象のURL', {
       waitUntil : ['load', 'networkidle2']
   });
   const buffer = await page.screenshot();
   const bucket = firebaseAdmin.storage().bucket('バケット名')
   const savePath = 'Firebase Storageの保存先パス'
   const bucketFile = bucket.file(savePath)
   const firebaseStorageDownloadTokens = v4()
   await bucketFile.save(buffer, {
       metadata: { 
           contentType: 'image/png',
           metadata: {
               firebaseStorageDownloadTokens
             }
       },
   })
   const downloadUrl = `https://firebasestorage.googleapis.com/v0/b/バケット名/o/${encodeURIComponent(savePath)}?alt=media&token=${firebaseStorageDownloadTokens}`;
   await browser.close();
}