概要
AzureFunctions + puppeteerでPDFを出力する。
生成したPDFはAzureStorageに保存する。
How to
「AzureFunctions + puppeteer」でスクショを撮る方法は以下を参照。
https://anthonychu.ca/post/azure-functions-headless-chromium-puppeteer-playwright/
コード
実際に動かしたコードをちょっと変えています(大人の事情)。
動作確認してないので、動かないかもです。参考程度にご覧ください。
ガーと書いたので、ツッコミポイントは多いですが、スルーしてください。
import * as puppeteer from "puppeteer";
import { BlobServiceClient, BlockBlobClient } from "@azure/storage-blob";
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import * as util from "util";
import { jsPDF } from "jspdf";
import { promises as fs } from "fs";
import { exec } from "child_process";
import imageSize from "image-size";
const execAsync = util.promisify(exec);
const AZURE_STORAGE_CONNECTION_STRING ="接続文字";
/**
* Azure Function上にフォントファイルを設置する
* https://github.com/Azure/azure-functions-host/issues/4883#issuecomment-683380515
*/
const fontUpdate = async () => {
const fontPath = process.env.FONT_PATH || "/home/.fonts";
try {
await fs.stat(fontPath);
return;
} catch {}
// ln -s /home/site/wwwroot/fonts $FONT_PATH
await execAsync(`ln -s /home/site/wwwroot/fonts ${fontPath}`);
};
const getBlockBlobClient = (name: string) => {
const blobServiceClient = BlobServiceClient.fromConnectionString(
AZURE_STORAGE_CONNECTION_STRING
);
const containerClient = blobServiceClient.getContainerClient("pdf");
return containerClient.getBlockBlobClient(`${name}.pdf`);
};
const gotoPdfPage = async (
page: puppeteer.Page
) => {
// スクショ撮るページ
const url = `https://www.google.com/?hl=ja`;
await Promise.all([
page.waitForNavigation({ waitUntil: ["load", "networkidle2"] }),
page.goto(url),
]);
};
const screenShotPdf = async (page: puppeteer.Page) => {
const image = (await page.screenshot({
type: "jpeg",
quality: 85,
fullPage: true,
})) as Buffer;
return image;
};
const createPdf = (image: Buffer, name: string) => {
const size = imageSize(image);
const pdf = new jsPDF({
format: "a4",
unit: "mm",
orientation: "p",
});
const ratio = pdf.internal.pageSize.height;
pdf.addImage(
image,
"JPEG",
0,
0,
size.width * ratio,
size.height * ratio,
"",
"FAST"
);
const pdfPath = `/tmp/${name}.pdf`;
pdf.save(pdfPath);
return pdfPath;
};
const httpTrigger: AzureFunction = async function (
context: Context,
req: HttpRequest
): Promise<void> {
const name = req.query?.name;
if (name) {
try {
const pdfBlob = getBlockBlobClient(name);
// 既に存在する場合は何もしない
if (await pdfBlob.exists()) {
context.res = {
status: 200,
body: "already exists",
};
return;
}
await fontUpdate();
const browser = await puppeteer.launch({
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-web-security",
],
});
const page = await browser.newPage();
await gotoPdfPage(page, name);
const image = await screenShotPdf(page);
const pdfPath = createPdf(image,name);
pdfBlob.uploadFile(pdfPath);
browser.close();
} catch (e) {
context.res = {
status: 400,
body: `error: ${e}`,
};
context.done();
return;
}
context.res = {
status: 200,
body: "success",
};
context.done();
return;
} else {
context.res = {
status: 400,
body: `name:${name}`,
};
context.done();
return;
}
};
export = httpTrigger;
大雑把な流れ
- AzureStorageに既に存在するかチェックする
- フォントを設定する
- ヘッドレスブラウザを起動
- ページに移動
- ページのスクリーンショットを取得
- スクショの画像をPDFのA4サイズにリサイズしてPDFに貼り付ける
- AzureStorageにPDFをアップロード
詰まりポイント
日本語のフォントを読み込む必要あり
postinstall
で postinstall.sh
を動かします。
スクショの画像サイズ取得
画像のサイズを取得してA4サイズとの比率を計算する必要がありました。
この画像サイズを取得するのに詰まりました。
便利なライブラリimage-sizeを使用しました。
使いやすいです。
browserのオプション
以下のオプションを設定しないと、出力できませんでした。
const browser = await puppeteer.launch({
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-web-security",
],
});
一時ファイルの保存場所
一時的にファイルを保存したい場合、/tmp/*
に保存する。
AzureFunctionsの仕様?