boto3でS3上のExcelファイルを強制ダウンロードさせる方法
結論:boto3でS3上のExcelファイルを強制ダウンロードさせる方法
ContentType='binary/octet-stream'とMetadataDirective='REPLACE'を指定する。
s3_client.copy_object(
Bucket=destination_bucket,
CopySource={'Bucket': source_bucket, 'Key': source_key},
Key=destination_key,
ContentType='binary/octet-stream',
MetadataDirective='REPLACE'
)
前提
特定のS3バケットに既にExcelファイルが配置されており、そのファイルを AWS Lambda を使用して、別のバケットにコピーするユースケースを想定します。
以下のようなコードを AWS Lambda 側で実装します。
単純に元のバケットからコピー先のバケットに、元のままExcelファイルをコピーします。
このとき、コピー先ではExcelファイルをダウンロードできる必要があります。
import boto3
s3_client = boto3.client('s3')
def handler(event, context):
source_bucket = event['sourceBucket']
source_key = event['sourceKey']
destination_bucket = event['destinationBucket']
destination_key = event['destinationKey']
s3_client.copy_object(
Bucket=destination_bucket,
CopySource={'Bucket': source_bucket, 'Key': source_key},
Key=destination_key,
)
落とし穴
Excelファイルをダウンロードする際に、Chromeであればダウンロードされますが、Microsoft EdgeなどではダウンロードされずにExcelファイルがブラウザで開かれてしまいます。
普通に登録するとアップロードされたExcelファイルの Content-Type は application/vnd.openxmlformats-officedocument.spreadsheetml.sheet が設定されます。


この状態で署名済みURLを作成し、ダウンロードしてみます。

ダウンロードされず、Excelファイルが開かれました。

解決策
これをファイルを開くのではなく、ダウンロードを強制するには Content-Type をバイナリファイル (binary/octet-stream) に設定することでブラウザ側にダウンロードを実行させます。
またS3オブジェクトのコピーでは、デフォルトで元のファイルのメタデータが引き継がれます。そのためMetadataDirectiveをREPLACEにすることで、元のファイルのContent-Typeを上書きして登録されます。
import boto3
s3_client = boto3.client('s3')
def handler(event, context):
source_bucket = event['sourceBucket']
source_key = event['sourceKey']
destination_bucket = event['destinationBucket']
destination_key = event['destinationKey']
s3_client.copy_object(
Bucket=destination_bucket,
CopySource={'Bucket': source_bucket, 'Key': source_key},
Key=destination_key,
ContentType='binary/octet-stream',
MetadataDirective='REPLACE'
)
ちなみにAWSコンソール上ではメタデータにContent-Typeがあり、boto3のcopy_objectのドキュメントにはMetadataパラメーターがありますが、MetadataにContent-Typeを入れても以下のようにユーザー定義になります。
ユーザー定義の方は単純なラベルのため、ブラウザが判定することには使われませんので注意が必要です。

s3_client.copy_object(
Bucket=destination_bucket,
CopySource={'Bucket': source_bucket, 'Key': source_key},
Key=destination_key,
MetadataDirective='REPLACE',
Metadata={
'Content-Type': 'binary/octet-stream' #これではない
}
)
参考
Discussion