AWS LambdaでPlayWright(chromium)を動かした

起こったこと

特定のサイトのスクショを取りたい。WebAPIで叩いて動くAWS Lambda上でPlayWrightを起動し、スクショを取って返却するようにした。

これを実装するには結構な時間がかかったので共有したくなった。

ただし、実行速度が遅い。

正しくは値を返却するまでの時間がものすごくかかる。およそ15秒待たないと画像が表示されない。LambdaLayerなどを使うと早くなるのだろうか。誰か教えてくれ。

AWS CDKでの書き方

NodejsFunctionを使いたい場合、bundlingプロパティに依存するモジュールを記載すると動くようだ。

import { Duration, Size, Cors } from "aws-cdk-lib";
import { Construct } from "constructs";
import { Runtime } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";

export class LambdaWithCdkStack extends Stack {
  constructor(scope: Construct, id: string, props: CustomizedProps) {
    super(scope, id, props);

    const nodeFunction = new NodejsFunction(this, `node_function`, {
      entry: "./lambda/handler.ts",
      handler: "handler",
      runtime: Runtime.NODEJS_16_X,
      timeout: Duration.seconds(60),
      memorySize: 2048,
      ephemeralStorageSize: Size.gibibytes(1),
      bundling: { // ①このプロパティを記載する
        nodeModules: [
          "playwright-core",
          "@sparticuz/chromium",
        ]
      }
    });

    const api = new aws_apigateway.RestApi(this, `apigateway`, {
      restApiName: `apigateway`,
      binaryMediaTypes: ["image/png"], // ②バイナリを返却する場合はここをきちんと記載
      defaultCorsPreflightOptions: {
        allowOrigins: Cors.ALL_ORIGINS,
        allowMethods: Cors.ALL_METHODS,
        allowHeaders: Cors.DEFAULT_HEADERS,
        statusCode: 200,
      },
    });

    const nodeFunctionApi = api.root.addResource(`ss`);
    nodeFunctionApi.addMethod("POST", new aws_apigateway.LambdaIntegration(
      nodeFunction
    ));
  }
}

①に依存するモジュールをを書かないとうまくいかない。/tmp/chromiumchromeが存在しないというエラーが発生する。

②バイナリデータを返したい場合はbinaryMediaTypesMIMEを記述する。

バイナリデータ返却についてはこちらの記事も参考にしてみてほしい。

extab-az.hatenablog.com

handlerの書き方

import type { APIGatewayProxyEvent } from "aws-lambda";
import { chromium } from "playwright-core";
import awsChromium from "@sparticuz/chromium";

let browser: Browser | null = null;

export const handler = async (event: APIGatewayProxyEvent) => {
  if (!browser) {
    browser = await chromium.launch({
      headless: true,
      chromiumSandbox: false,
      executablePath: await awsChromium.executablePath(),
      args: awsChromium.args,
    });
   // 以下の処理
  }
});

@sparticuz/chromiumはnode_modulesディレクトリにchromiumを置くモジュールで、そのPathをPromiseで持っている。Playwrightはchromiumのパスを指定することが出来るので、そのPromiseで受け取って指定してあげればよい。

www.npmjs.com