目次

目次

S3 から特定のファイルを持ってきて Zip に固めてダウンロードさせる

アバター画像
江藤 光
アバター画像
江藤 光
最終更新日2017/10/18 投稿日2017/10/18

大変なのかと思っていたのですが、ライブラリが揃っていて思っていたより簡単に実現できました。

環境

  • 言語
    • Python 3.5.2
  • ライブラリ
    • Flask 0.11
    • boto3 1.3.1

AWS 側の設定として、 Web サーバから 対象となる S3 のファイルへ読み込みアクセスの許可が必要な場合があります。

コード

(略)
from boto3 import resource
from flask import send_file
from os
from zipfile import ZipFile

(中略)

    def create_zip(filenames: list) -> str:
        '''
        受け取ったファイル名リストのファイルから成るZipを作成し、そのファイルパスを返す
        :param filenames: S3からダウンロードするファイル名のリスト
        :return: 作成された Zip ファイルの PATH
        '''
        bucket = 'BUKET_NAME_HERE' # S3 のbucket名をここに
        key_prefix = 'KEY_PREFIX_HERE' # ファイルが置かれている場所が bucket 直下でなければ key をここに
        tmp_dir = os.path.join(current_app.root_path, 'tmp') # 画像を一時保存するディレクトリを指定

        s3 = resource('s3')

        result_file_path = os.path.join(tmp_dir, 'result.zip')
        with ZipFile(result_file_path, 'w') as result_zip:
            for name in filenames:
                try:
                    filename = os.path.join(tmp_dir, name)  # 保存先
                    target_key = key_prefix + str(answer_id) + '/' + name  # ダウンロード元
                    s3.Bucket(bucket).download_file(target_key, filename)
                    result_zip.write(filename=filename, arcname=name)
                except Exception as e:
                    # エラー時の処理
        return result_file_path

    @app.route('/download/<int: file_id>')
    def download_by_id(file_id: int):
        files = get_file_names(id=file_id) # ID を元にファイル名の list を取得
        zip_filename = create_zip(image_names=images, survey_id=survey_id)
        return send_file(filename_or_fp=zip_filename, as_attachment=True)

解説

コントローラー

from flask import send_file

    @app.route('/download/<int: file_id>')
    def download_by_id(file_id: int):
        files = get_file_names(id=file_id) # ID を元にファイル名の list を取得
        zip_filename = create_zip(image_names=images, survey_id=survey_id)
        return send_file(filename_or_fp=zip_filename, as_attachment=True)

/downlaod/1 のように ID を指定してアクセスすると、ファイルがダウンロードされます。

Flask には send_file という API があり、 サーバ内のファイルへの PATH を渡すと、クライアントへダウンロードさせるレスポンスを簡単に返すことが出来ます。 mimetype などの設定も Flask が自動判断してくれるので、凝ったことしない限り便利に使えそうです。

一点ハマったこととして、ファイルをダウンロードさせたければ send_file のオプションで、 as_attachment=True と指定しなければいけません。 このオプションはレスポンスヘッダに Content-Disposition: attachment と追加し、 添付ファイルであると明示的に指定します。 これがないと、ブラウザでファイルの中身が表示されてしまったり、 ダウンロードファイル名が変になってしまったりします。

モデル

from boto3 import resource
from zipfile import ZipFile

    def create_zip(filenames: list) -> str:
        '''
        受け取ったファイル名リストのファイルから成るZipを作成し、そのファイルパスを返す
        :param filenames: S3からダウンロードするファイル名のリスト
        :return: 作成された Zip ファイルの PATH
        '''
        bucket = 'BUKET_NAME_HERE' # S3 のbucket名をここに
        key_prefix = 'KEY_PREFIX_HERE' # ファイルが置かれている場所が bucket 直下でなければ key をここに
        tmp_dir = os.path.join(current_app.root_path, 'tmp') # 画像を一時保存するディレクトリを指定

        s3 = resource('s3')

        result_file_path = os.path.join(tmp_dir, 'result.zip')
        with ZipFile(result_file_path, 'w') as result_zip:
            for name in filenames:
                try:
                    filename = os.path.join(tmp_dir, name)  # 保存先
                    target_key = key_prefix +  + name  # ダウンロード元
                    s3.Bucket(bucket).download_file(target_key, filename)
                    result_zip.write(filename=filename, arcname=name)
                except Exception as e:
                    # エラー時の処理
        return result_file_path

ここでは2つの処理を行っています。

  • ファイルをS3から取得する
  • 取得したファイルをZipアーカイブに入れる

ファイルをS3から取得する

S3 からの取得には Python 用 SDK boto3 を使用しています。

s3 = resource('s3')

resource というのは、公式によると

Boto 3 consists of the following major features: Resources: a high level, object oriented interface Collections: a tool to iterate and manipulate groups of resources Clients: low level service connections Paginators: automatic paging of responses Waiters: a way to block until a certain state has been reached

とのことで、 S3に関しての高度な操作まで揃ったインターフェイスを返してくれる…みたいです。

s3.Bucket(bucket).download_file(target_key, filename)

そして、リソース s3 中の download_file メソッドを呼び出してローカルにS3のファイルをダウンロードします。 第二引数で保存先のファイル名を指定出来ます。 ここで一定の規則に従ったファイルにしておけば、後でまとめて消すときに楽です。

取得したファイルをZipアーカイブに入れる

Python 標準ライブラリーの zipfileを使用しています。 (あんまり使われてないのか、まだドキュメントの翻訳が完了しておらず、一部が英語のままです)

with ZipFile(result_file_path, 'w') as result_zip:

これで Zip ファイルを作成し、同時に読み込みモード(w) で開いています。

result_zip という名前で作成した Zip ファイルにアクセスできるようになります。 本来は close を明示的に書く必要がありますが、 with を使っていれば句の終わりで自動的に close してくれるのでこちらを使いましょう。

result_zip.write(filename=filename, arcname=name)

先ほど開いた zip ファイルに S3 から取得したファイルを書き込みます。

filename に書き込むファイルのフルパスを、arcname に zip ファイルで実際に書き込まれる時の名前を指定します。 このとき、arcname を指定しないと、フルパスのディレクトリ構造そのままでZipファイルが作成されてしまいます。注意しましょう。

アバター画像

江藤 光

まだまだ気持ちは新人です。

目次