大変なのかと思っていたのですが、ライブラリが揃っていて思っていたより簡単に実現できました。
環境
- 言語
- 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ファイルが作成されてしまいます。注意しましょう。
この記事を書いた人
- まだまだ気持ちは新人です。
最近書いた記事
- 2018.03.23Windows のコンソールを使いやすくしよう
- 2018.02.23GitHubでPullRequestが出ると、Jenkinsでテストした後でEC2に自動デプロイする設定を行った
- 2018.02.21Jenkins にパラメータを渡して、Packer で引数付きビルドを行う
- 2018.01.10それ、キーボードマクロで出来ますよ(Emacs)