13章 - S3 (後半)

前回の続きを行う.
urhayataro.hatenablog.com

S3とメモリ間のファイル操作

前回は,ローカルのディスクにあるファイルをアップロードしたりして,S3とローカル間でファイル操作をしていた.今回は,高速化のためにS3とローカルのメモリ間でファイル操作を行う.

csvデータファイルのやり取り

まずは,pythonのpandasとioを使い,CSVデータを読み書きする.

import pandas as pd
import io

必要なライブラリをインポートし,dfに適当なオブジェクトを作成する.以下の例では出力で表が作成された.

次に,この表をS3にCSVで保存する.

with io.BytesIO() as stream:
      df.to_csv(stream, index_label="Car")
      resp = bucket.put_object(
            key="data.csv",
            Body=stream.getvalue()
      )

何をしているかというと,まずio.BytesIO()でメモリ上のストリームを用意している.次に,df.to_csv()でメモリ上のストリームに一時的にアップロードしている.最後に,bucket.put_objectでS3にアップロードしている.

この他にも,stream.seekを用いた方法や,upload_fileobjを用いた方法がある.


アップロードのやり方がわかれば,次は,S3にあるCSVデータをロードする

obj = bucket.Object("data.csv").get()
stream - io.BytesIO(obj.get("Body").read())
df2 = pd.read_csv(stream, index_col="Car")

これは,まず1行目でdata.csvというキーを持ったS3オブジェクトを作り,S3からそのオブジェクトを取得している.
次に,get("Body")でオブジェクトを取得し,read()でデータの中身をダウンロードする.これでデータのダウンロードが済んだ.
最後に取得したデータをread_csv()関数に渡している.


画像ファイルのやり取り

基本CSVと同じだが,画像ファイルの操作も行う.
まず,必要なライブラリをインポートする.

from PIL import Image
import numpy as np
from matprotlib import pyplot as plt

その後,clownfish.jpgを開き,表示する.

img = Image.open("clownfish.jpg")
plt.imshow(np.asarray(img)

すると,以下のような画面が表示される.


ではこのメモリにロードした画像をバケットにアップロードしてみる.

with io.BytesIO() as buffer:
      img.save(buffer, "PNG")
      resp = bucket.put_object(
            key="clownfish.png",
            body=buffer.getvalue()
      )

img.close()

saveメソッドでデータをPNGでメモリに書き込み,バッファーからS3にアップロードしている.最後にはimageオブジェクトを閉じている.

最後に,バケットからメモリにダウンロードしてみる.

stream = bucket.Object("clownfish.png").get().get("Body")
img2 = Image.open(stream)

plt.imshow(np.asarray(img2))
img2.close()

実行してみると,以下のようにクマノミの画像を出力するはずだ.これで画像ファイルもメモリとS3間でやり取りすることができた.


S3で署名付きURLの発行

S3のバケットにあるオブジェクトは,プライベートで,他人はアクセスできないようになっている.そのため,他人がアクセスする場合は,アクセスするオブジェクトに対してPresigned URLを発行する必要がある.そのURLを受け取った人はファイルのアップロードやダウンロードができる権限を付与される.という仕組みだ.

アップロードのためのProsigned URL

まずはライブラリのインポートと,S3のclientオブジェクトを作成する.

import requests
client = session.client("S3")

では,S3にアップロードするためのPresigned URLを発行する.

resp = client.generate_presigned_post(
      Bucket=bucket.name,
      key="upload.txt",
      ExpriesIn=600
)
print(resp)

client.generate_presigned_post()は,Presigned URLを発行するための関数で,2行目で使用するバケットを指定する.
3行目では,アップロードされるオブジェクトのキーをupload.txtにしている.
また, ExpriesIn=600で600秒後にURLが失効するように設定している.600秒をすぎると,発行したURLにはアクセスできない.

実行すると,次のような出力が出てくる.urlの部分がアップロードする先のURLになっている.


取得したPresigned URLを用いてファイルをアップロードしてみる.

resp2 = requests.post(
      resp["url"],
      data=resp["fields"],
      files={'file': ("dummy.text", "Hello world!")}
)
print(:Upload sucsess?", resp2.status_code ==204)

出力は以下のようになる.また,AWSのS3のバケットにはupload.txtがアップロードされている.


ダウンロードのためのProsigned URL

今度は,S3から特定のオブジェクトをダウンロードしてみる.

resp3 = client.generate_presigned_url(
      ClientMethod='get_object',
      params={
            'Bucket': bucket.name,
            'Key': "upload.txt",
      },
      ExpriesIn=600
)
print(resp3)

client.generate_presigned_url()は,Presigned URLを発行するための関数.
2行目ではダウンロードの許可を設定している.
後はバケット名とキーを設定している.

これで実行するとPresigned URLが発行され,アクセスするとdummy.txtの内容が入った,upload.txtがダウンロードされる.

他の方法として以下の方法もある.

resp4 = requests.get(resp3)
print("Download sucsess?", resq4.status_code == 200)
print("File content :". resp4.text)

出力は以下になる.


これで一通り終えたので,バケットを削除しておく.

Presigned URLの利点

他人が自分のS3にアクセスするときを想定する.
まず,Presigned URLを用いない場合,クライアントが,Lambdaにダウンロードのリクエストをし,LambdaはS3から取得したデータをクライアントに返す.


次に,Presigned URLを用いる場合,クライアントはLambdaにダウンロードのリクエストをし,LambdaはPresigned URLを返す.あとはクライアントがS3にアクセスする.つまり,クライアントとS3が直接やり取りすることができる.

これなら,Lambdaの利用を控えることで効率化が図れ,Presigned URLで有効期限の設定もできる.


まとめ

ディスクやメモリと,S3間でのデータのやり取りについておこないました.S3くらいはできないとと先生が言っていたので大事だと思い,長かったですが,しっかりめに書きました.なんとなくメモリとクラウド間のデータのやり取りがわかってきたような気がします.
これでS3は終わります.