Puppteerでキャプチャ画像を取得 part2


概要

エンジニアもどき2年目に入りました。色々あって以下を行うLambda関数を作成することになりました。
色々学びがあったので備忘録がわり兼アウトプットのために書くことにしました。

  1. 特定のページのキャプチャ取得
  2. 取得したキャプチャをS3に保存
  3. S3に保存したURLをDyanmoに記録

part2では2. 取得したキャプチャをS3に保存の実装とデプロイまで行います。
part2での実装内容はこちら

前提

前回(part1)の続きです。part1に引き続き以下の準備は完了している前提です。
前回(part1)の実装内容はこちら

  • nodejs
  • ServerlessFramework
  • yarn
  • webpack
  • iamのユーザ作成済み

実装作業

1. 準備

ローカルでの動作確認のためにローカルにS3のバケットを作成できるserverless-s3-localを使用します。

# 追加
yarn add -D serverless-s3-local

続いてserveress.ymlにS3の設定を追加します。

serverless.yml
service: puppeteer-capture
provider:
  # ・・・ 省略 ・・・
  # テーブル名やバケット名など環境変数
  environment:
    # ・・・ 省略 ・・・
    # 追加1 : バケット名
    CAPTURE_BUCKET: ${self:provider.environment.PREFIX}-capture-bucket
  iamRoleStatements:
    # 追加2 : S3の設定
    - Effect: Allow
      Action:
        - 's3:PutObject'
        - 's3:PutObjectAcl'
      Resource: 'arn:aws:s3:::${self:provider.environment.S3_BUCKET}*'
# 追加3 : ローカルでの動作用設定
custom:
  defaultStage: dev
  s3:
    port: 8081
    directory: .s3
    cors: false
# 追加3 : プラグイン
plugins:
  - serverless-webpack
  - serverless-s3-local
# ・・・ 省略 ・・・
# 追加4 : リソースの設定
resources:
  Resources:
    CaptureBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:provider.environment.CAPTURE_BUCKET}

ローカルで起動できるか確認します。

sls s3 start -c ./serverless.yml

以下のように起動していれば完了です。

ローカルにS3のバケットが作成されています。

また、AWSのリソース(S3やDynamoDB)を操作する必要があるので、aws-sdkを使用します。

# 追加
yarn add -D aws-sdk

Lambdaの実行環境には AWS SDK for JavaScriptが含まれているためdevDependenciesにのみ追加しています。
webpack.config.jsの外部依存にaws-sdkを追加して準備は完了です。

webpack.config.js
  // ・・・ 省略 ・・・
  target: 'node',
  // 追加
  externals: ['chrome-aws-lambda', 'aws-sdk'],
  // ・・・ 省略 ・・・
};

2. 実装

前回は取得したキャプチャ画像をローカルに書き出して保存していましたが、Lambdaは実行後、使用されたすべてのリソース(が破棄されてしまうので、S3に保存するように修正します。

handler.js
// ・・・ 省略 ・・・
// 追加1 : S3のインポート
import { config, S3 } from 'aws-sdk';

// 追加2 : S3の設定とオプション(LOCALで実行した際の設定)
config.update({ region: process.env.AWS_REGION });
const s3Options = process.env.LOCAL
  ? {
      s3ForcePathStyle: true,
      accessKeyId: 'S3RVER',
      secretAccessKey: 'S3RVER',
      endpoint: new AWS.Endpoint('http://localhost:8081'),
    }
  : {};

// 追加3 : s3のクライアント/バケット名/アップロード関数
const s3 = new S3(options);
const bucket = process.env.CAPTURE_BUCKET;
const putObject = ({ key, body, contentType, acl }) => {
  const params = {
    Bucket: bucket,
    Key: key,
    Body: body,
    ContentType: contentType,
    ACL: acl,
  };
  console.log(params);
  return s3.putObject(params).promise();
};

const getCapture = async (url) => {
  // ・・・ 省略 ・・・
};

export const captureFunction = async event => {
  // ・・・ 省略 ・・・
  // 修正 : ファイルに書き出しからS3へアップロードするように変更
  // Before : ファイル書き出し
  // writeFileSync('/tmp/hoge.jpg', jpgBuf);
  // After : S3へアップロード
  try {
    await putObject({
      key: 'hoge.txt',
      body: jpgBuf,
      contentType: 'image/jpeg',
      acl: 'public-read',
    });
  } catch (error) {
    console.log(error);
    return { statusCode: 500, body: error.message };
  }

  return { statusCode: 200, body: 'キャプチャの取得に成功しました。'};
};

注意する点はacl、つまりアクセスコントロールリストにpublic-readの設定を忘れるとS3にアップロードはできるもののAccess Deniedとなりリソースにアクセスができなくなってしまいます。

3. 動作の確認

まずはローカル環境での動作の確認です。

LOCAL=true sls invoke local --function captureFunction -c ./serverless.yml
LOCAL=true sls invoke local --function captureFunction --data '{"url":"https://qiita.com/"}' -c ./serverless.yml

実行して 200でレスポンスが帰ってくること、ローカルのバケットにキャプチャ画像が保存されていることを確認します。

バケットに保存されているhode.jpg._S3rver_objecthoge.jpgにリネームして開くと確認することができます。

4. デプロイと動作確認

デプロイします。

serverless deploy

実行し200でレスポンスが返ってくることを確認します。
また、AWSのコンソールからS3にアクセスしてバケットの中を確認します。

sls invoke --function captureFunction -c ./serverless.yml
sls invoke --function captureFunction --data '{"url":"https://qiita.com/"}' -c ./serverless.yml

以上でpart2の作業は完了です。
次はS3に保存した画像のURLをDynamoDBに書き出す処理までの実装を行います。

おわりに

  • ACLのところで結構引っかかった(Bucketに保存できるもののアクセスできない)
  • ローカルでのS3の使い方、ServerlessFrameworkでS3のバケットの作成など色々勉強になった
  • 今回は日本語のページのキャプチャは必要なかったので対応していませんが、Lambdaのインスタンスに日本語フォントが含まれておらず、日本語を含むページのキャプチャした際に文字化けが発生します。(フォントを用意して設定する必要がある)

参考