最初に
pythonでエクセルやらスクレーピングやらを簡単にやってみたいなと思い、
まずはエクセルで簡単なファイル操作をしてみました。
今回、lambda以外は全て触るのが初めてなので微妙な記述等あるかと思いますが、
ご容赦いただければ幸いです。またSAMやAWS CLIなどのインストール等については割愛しておりますので、同じように試される方は適宜検索してみてください。
今回実現したいのは、
以下のようにcurl
でエクセルファイルを添付して、APIGatewayを叩いて、lambdaで処理して、そのエクセルファイルから値を抽出して返却する、というシンプルなものです。
が、思った以上に大変でした。。。
SAM
では早速SAMから定義していきます。
sam init
を実行し、セットアップのために色々と聞かれるので、答えていく。
$ sam init
You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Choose an AWS Quick Start application template
1 - Hello World Example
2 - Data processing
3 - Hello World Example with Powertools for AWS Lambda
4 - Multi-step workflow
5 - Scheduled task
6 - Standalone function
7 - Serverless API
8 - Infrastructure event management
9 - Lambda Response Streaming
10 - GraphQLApi Hello World Example
11 - Full Stack
12 - Lambda EFS example
13 - Serverless Connector Hello World Example
14 - Multi-step workflow with Connectors
15 - DynamoDB Example
16 - Machine Learning
Template: 1
Use the most popular runtime and package type? (python3.13 and zip) [y/N]: N
Which runtime would you like to use?
1 - dotnet8
2 - dotnet6
3 - go (provided.al2)
4 - go (provided.al2023)
5 - graalvm.java11 (provided.al2)
6 - graalvm.java17 (provided.al2)
7 - java21
8 - java17
9 - java11
10 - java8.al2
11 - nodejs22.x
12 - nodejs20.x
13 - nodejs18.x
14 - python3.9
15 - python3.8
16 - python3.13
17 - python3.12
18 - python3.11
19 - python3.10
20 - ruby3.3
21 - ruby3.2
22 - rust (provided.al2)
23 - rust (provided.al2023)
Runtime: 15
# ここで今回はイメージを選択しました。
What package type would you like to use?
1 - Zip
2 - Image
Package type: 2
Based on your selections, the only dependency manager available is pip.
We will proceed copying the template using pip.
Would you like to enable X-Ray tracing on the function(s) in your application? [y/N]: n
Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: n
Would you like to set Structured Logging in JSON format on your Lambda functions? [y/N]: y
Structured Logging in JSON format might incur an additional cost. View https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-pricing for more details
Project name [sam-app]: excel-app
-----------------------
Generating application:
-----------------------
Name: excel-app
Base Image: amazon/python3.8-base
Architectures: x86_64
Dependency Manager: pip
Output Directory: .
Configuration file: excel-app/samconfig.toml
Next steps can be found in the README file at excel-app/README.md
Commands you can use next
=========================
[*] Create pipeline: cd excel-app && sam pipeline init --bootstrap
[*] Validate SAM template: cd excel-app && sam validate
[*] Test Function in the Cloud: cd excel-app && sam sync --stack-name {stack-name} --watch
すると以下のようなディレクトリとファイルが生成されます。
ここで2点追加操作します。
- 送信用のエクセルファイルを追加(何でもOK)
- hello_wordからexcel_manipulatorにディレクトリ名を変更
ここのポイントとしてはWhat package type would you like to use?
の質問でImage
を使用することです。
cd excel-app
tree
.
├── Book1.xlsx # 検証用に手動で後から追加しました。
├── README.md
├── __init__.py
├── events
│ └── event.json
├── excel_manipulator # hello_worldから変更
│ ├── Dockerfile
│ ├── __init__.py
│ ├── app.py
│ └── requirements.txt
├── samconfig.toml
├── template.yaml
└── tests
├── __init__.py
└── unit
├── __init__.py
└── test_handler.py
template.yaml
次にtemplate.yaml
を変更します。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
python3.8
Sample SAM Template for excel-app
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
Function:
# 時間がかかる処理をしたい場合は、ここを伸ばす必要があります。
Timeout: 3
MemorySize: 128
# You can add LoggingConfig parameters such as the Logformat, Log Group, and SystemLogLevel or ApplicationLogLevel. Learn more here https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-loggingconfig.
LoggingConfig:
LogFormat: JSON
Resources:
# RestAPIの定義
ExcelManipulatorApi:
Type: AWS::Serverless::Api
Properties:
StageName: default
BinaryMediaTypes:
- '*~1*'
ExcelManipulatorFunction:
Type: AWS::Serverless::Function # More info about Function Resource:
Properties:
PackageType: Image
Architectures:
- x86_64
Events:
ExcelManipulatorApi:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /excel_manipulator
Method: post
RestApiId: !Ref ExcelManipulatorApi
Metadata:
Dockerfile: Dockerfile
DockerContext: ./excel_manipulator # ここをhello_worldから変更する
DockerTag: python3.8-v1
Outputs:
# ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
# Find out more about other implicit resources you can reference within SAM
# https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
ExcelManipulatorApi:
Description: API Gateway endpoint URL for Prod stage for ExcelManipulator Function
Value: !Sub "https://${ExcelManipulatorApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/excel_manipulator/"
ExcelManipulatorFunction:
Description: ExcelManipulator Lambda Function ARN
Value: !GetAtt ExcelManipulatorFunction.Arn
ExcelManipulatorFunctionIamRole:
Description: Implicit IAM Role created for ExcelManipulator Function
Value: !GetAtt ExcelManipulatorFunctionRole.Arn
細かくは解説しませんが、ポイントが数点あります(めちゃくちゃ沼った。。。)
BinaryMediaTypesを指定する
これをAPIに指定しないといけません。
なぜなら、RestAPIでは、デフォルトではUTF-8のテキストのペイロードしかサポートしておらず、これを設定しないとめちゃくちゃ文字化けして500エラーが返却されます。
HTTPAPIにすれば問題ないのですが、今回はRESTAPIで進めるためにこの設定を付与しました。
また以下のドキュメントに書かれている通り/
は~1
と書く必要があるので注意が必要です。
https://docs.aws.amazon.com/apigateway/latest/api/API_RestApi.html#apigw-Type-RestApi-binaryMediaTypes:~:text=Required%3A%20No-,binaryMediaTypes,-The%20list%20of
app.py
次にコードを変更します。特に注意点はありません。
Base64でエンコードされているので、それをデコードする、くらいでしょうか。
import json
import pandas as pd
import base64
from io import BytesIO
def lambda_handler(event, context):
print("event", event)
# Base64エンコードされたエクセルデータを取得
body = event['body']
print("body_data", body)
# Base64デコードしてバイナリデータに変換
excel_data = base64.b64decode(body)
# BytesIOを使ってメモリ上でエクセルファイルを開く
excel_file = BytesIO(excel_data)
df = pd.read_excel(excel_file, engine='openpyxl')
output = df.loc[:,:]
print("output", output)
# DataFrame を JSON に変換
output_json = output.to_json(orient="records") # レコード形式で変換
print("output_json", output_json)
# Lambda のレスポンスとして返す
return {
"statusCode": 200,
"body": json.dumps({"output": json.loads(output_json)}) # JSON を辞書形式にして返す
}
requirements.txtの変更
railsでいうgemfile的な位置付けのファイルなんですかね。
ここに必要なパッケージを記述します。
requests
pandas
ビルドしてデプロイする
ここまでで準備は整ったので、ビルドとデプロイをしていきます。
ビルド
$ sam build
デプロイ
$ sam deploy -g --profile ~~~
また、色々と確認されるので答えていく。基本、全てY
で良いと思います。
ExcelManipulatorFunction has no authentication. Is this okay? [y/N]: y
については、本来は設定するべきですが、今回は割愛しています。
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Found
Reading default arguments : Success
Setting default arguments for 'sam deploy'
=========================================
Stack Name [image-sam-app]:
AWS Region [us-east-1]: ap-northeast1
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [Y/n]: Y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
#Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: y
ExcelManipulatorFunction has no authentication. Is this okay? [y/N]: y
Save arguments to configuration file [Y/n]: y
SAM configuration file [samconfig.toml]:
SAM configuration environment [default]:
しばらく待って以下のメッセージが表示されたらデプロイ成功です!
Successfully created/updated stack - excel-app in ap-northeast-1
AWSのコンソール画面に確認しにいくと
- lambda関数
- API Gateway
が存在することを確認できました。
試しにcurlで最初に用意したエクセルファイルを送ると、
$ curl -X POST \
-H "Content-Type: multipart/form-data" \
-F "file=@Book1.xlsx" \
https://~~~~~.execute-api.ap-northeast-1.amazonaws.com/Stage/excel_manipulator
こんな感じでエクセルから文字列を読み取って返却されました🎉
{"output": [{"A1": "A2", "B1": "B2", "C1": "C2"},
{"A1": "A3", "B1": "B3", "C1": "C3"}]}
まとめ
振り返ってみるとめちゃくちゃ簡単そう(という簡単)なのですが、
- Layerを作成してLambdaに設定したらいい
- dockerを起動して、そこでpip installしたらいい
などさまざまな情報にあたり、色々試した結果今回の方法に辿り着きました。
同じように詰まっている方の助けになれば幸いです。
参考