やりたいこと
Lambda上でPuppeteerを動かしてキャプチャを取るとき、日本語フォントを正しく表示させる。
(デフォルトではLambdaに日本語フォントが入っていないため、何もしないと日本語がすべて豆腐になってしまうのだ。)
環境
- AWS Lambda
- Node.js 12.x
- ローカル
- Windows10
- Node.js v12.16.2
- npm 6.14.4
やり方(ざっくり)
- Lambda Layerに日本語フォントをzip化してアップロードする。
- Lambda関数にそのLayerを追加する。
- Lambda関数内でLayerのフォントファイルを参照できるようにindex.jsで
process.env['HOME'] = "/opt";
を定義する。
やり方(詳細)
puppeteer_sample
├─ modules # Layerに登録するnpmモジュール群
│ ├─ node_modules
│ └─ package-lock.json
├─ .fonts # Layerに登録する日本語フォント置き場
│ ├─ ipag.ttf
│ ├─ ipagp.ttf
│ ├─ NotoSansCJKjp-Bold.otf
│ └─ NotoSansCJKjp-Regular.otf
└─ lambda # Lambda本体のコード群
└─ index.js
Lambda上でpuppeteerを動作させる
こちらの記事が非常に分かりやすく、参考になりました。
参考記事は、LambdaのランタイムにNode.js 8.10を選択できる時代の記事ですが、Node.js 12.xと読み替えても通用します。
参考記事とほぼ同じですが、僕のやった手順を簡単にメモしておきます。
Lambda Layerに登録するモジュールの作成
$ cd modules
$ npm i chrome-aws-lambda puppeteer-core
modulesを丸ごとzipしてmodules.zipを作成
Lambda Layer作成
このステップはほぼ参考記事のまんまです。
AWSコンソールからLambda Layerを開く
レイヤー作成(npmモジュール群)
- 名前:任意
- .zipファイルをアップロード:先ほど作成したmodules.zipを選択
- 互換性のあるランタイム:Node.js 12.x
- 作成ボタン押下
日本語フォントを登録する
Lambda Layerに登録するための日本語フォントをローカルにダウンロードします。
僕の場合は、以前取得していた日本語フォントファイルが手元にあったので、詳細な手順は割愛します。
IPAのサイトからダウンロードしたり、適当な記事を参考に各自調達してください。
取得したフォントファイルを.fonts配下に格納し、エクスプローラから.fontsフォルダごとzipして.fonts.zipを作成します。
レイヤー作成(日本語フォント)
- 名前:任意
- zipファイルをアップロード:先ほど作成した.fonts.zipを選択
- 互換性のあるランタイム:Node.js 12.x
Lambda関数作成
このステップもほぼ参考記事のまんまです。
AWSコンソールからLambda 関数を開く
関数作成
- 関数名:任意
- ランタイム:Node.js 12.x
- 実行ロール:後で編集するので、作成時は適当に。
- 「関数の作成」を押下
レイヤー登録
追加したいレイヤーとバージョンを選択し、「追加」ボタンを押下する。
この手順を繰り返して、先ほど作成した2つのレイヤーを追加する。
index.jsの編集
デザイナービューでLambda関数を選択し、メイン処理を記述する。
/* 日本語フォントが入っている.fontsを読み込ませるためにHOMEを設定する */
process.env['HOME'] = "/opt"; // Layerの内容は/optにある
const AWS = require('aws-sdk');
const chromium = require('chrome-aws-lambda');
const puppeteer = require('puppeteer-core');
// パラメータ定義
const SAVE_BUCKET_NAME = 'キャプチャ保存先のバケット名'
const operations = [
{ method: 'goto', args: ['https://www.yahoo.co.jp/'] },
{ method: 'waitFor', args: [1000] }
]
/* ユーティリティ関数 */
// operationsに従ってブラウザ操作。clickやwaitFor時にキャプチャを取得
const execOperations = async function (page, operations, result, jpgBuf) {
for (const op of operations) {
console.log(`${op.method} (${op.args[0]} ${op.args.length > 1 ? " ," + op.args[1] : ""})`)
if (op.method === 'click') {
jpgBuf.push(await page.screenshot({ fullPage: true, type: 'jpeg' }));
}
await page[op.method](...op.args)
if (op.method === 'waitFor') {
jpgBuf.push(await page.screenshot({ fullPage: true, type: 'jpeg' }));
}
}
}
// S3にキャプチャを保存
const saveJpg = async (jpgBuf) => {
const s3 = new AWS.S3();
const now = new Date();
now.setHours(now.getHours() + 9);
const nowYMD = now.getFullYear() +
(now.getMonth() + 1 + '').padStart(2, '0') +
(now.getDate() + '').padStart(2, '0')
const nowHMS = (now.getHours() + '').padStart(2, '0') +
(now.getMinutes() + '').padStart(2, '0') +
(now.getSeconds() + '').padStart(2, '0');
for (const idx in jpgBuf) {
const fileName = 'screenshots/' + nowYMD + '/' + nowHMS + '_' + ('0000' + idx).slice(-4) + '.jpg';
let s3Param = {
Bucket: SAVE_BUCKET_NAME,
Key: fileName,
Body: jpgBuf[idx]
};
await s3.putObject(s3Param).promise();
}
}
// メイン処理
exports.handler = async (event, context) => {
let browser = null;
const result = {}
const jpgBuf = []
try {
// 初期処理
browser = await puppeteer.launch({
args: chromium.args.concat(['--lang=ja']),
defaultViewport: chromium.defaultViewport,
executablePath: await chromium.executablePath,
headless: chromium.headless,
});
let page = await browser.newPage();
await page.setExtraHTTPHeaders({
'Accept-Language': 'ja-JP'
});
// ブラウザ操作
await execOperations(page, operations, result, jpgBuf)
// 取得したキャプチャをS3に保存
await saveJpg(jpgBuf)
} catch (error) {
return context.fail(error);
} finally {
if (browser !== null) {
await browser.close();
}
}
return context.succeed(result);
};
Lambdaの設定を編集する
メモリとタイムアウトはデフォルトだと少なすぎる/短すぎるので、適当に増やす。
Lambda関数にアタッチされているIAMロールにS3書き込み権限を付与する
Lambda関数の基本設定の「編集」ボタンを押下してLambdaにアタッチされているIAMロールを確認する。
「IAMコンソールでxxxxxロールを表示します」のリンクからIAMロールを編集し、S3書き込み権限を付与する。
S3バケットを作成する
キャプチャ保存用のS3バケットを作成する。
Lambda関数のindex.js の SAVE_BUCKET_NAME に作成したバケット名を設定する。
Lambda関数の実行
Lambda関数の「テスト」を押下する。(初回はテストオブジェクトの定義が必要だが、適当でOK。)