Boto3でS3にオブジェクトをGet・Putする方法まとめ

AWS SDKのPython版であるBoto3を用いてS3にファイルをダウンロード(Get)・アップロード(Put)する方法について整理しました。どのようなインターフェイスが用意されているかや、どういった値ならば引数として適切なのかについてまとめました。
2021.09.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

簡単まとめ

  • Binary形式でデータはやり取りする
  • BytesIOを使えばオンメモリで使える

Boto3のS3オブジェクトの読み書き

AWS SDKのPython版であるBoto3ではいくつかの方法でオブジェクトのアップロード・ダウンロードが可能です。 大きく分けて次の2つの方式があります。

  • ファイルの様な操作(cpコマンドに近い感じ)
  • HTTPのような操作

ファイルのような操作は次のように書きます。

file like

bucket.upload_file('/local/path/to/target', 'object/key')
bucket.download_file('object/key', '/local/path/to/target')

ファイル操作のcpコマンドのように引数が[SRC] [DIST]の順になっています。 この方式のAPIを便宜的にファイル版と呼びます。

HTTPのような操作の場合は次のように書きます。

http like

bucket.Object('object/key').get()['Body']
bucket.Object('object/key').put(Body=b'Lorem ipsum dolor sit amet')

HTTPクライアントのようにオブジェクトはBodyを通してやり取りします。 この方式のAPIを便宜的にHTTP版と呼びます。(ファイル版でもHTTP経由で通信します)

以下ではファイル版とHTTP版についてそれぞれ使い方を見ていきます。

準備

以下では次のようなパッケージがインポートされ、変数が定義されているとして話を進めます。

setup

from pathlib import Path

import boto3
import io

s3 = boto3.resource('s3')
bucket = s3.Bucket('boto-io')

KEY = 'ipsum.txt'
ORIGIN_PATH = Path('ipsum.txt')
SAVE_PATH = Path('saved.txt')

ファイル版

ファイル版ではディスクを使用する場合とオンメモリで処理する場合の2パターンを説明します。

直接ディスクから読み込む

ファイルのパスを指定してアップロードする方法です。

disk

bucket.upload_file(str(ORIGIN_PATH), KEY)
bucket.download_file(KEY, str(SAVE_PATH))

file objectを使用する

open関数を利用して、ファイルを読み書き可能です。 注意点はバイナリモードでファイルを開くことです。 バイナリのfile objectしか受け付けないようです。

file object

with ORIGIN_PATH.open('rb') as f:
    bucket.upload_fileobj(f, KEY)

以下はダメな例です。md5ハッシュが計算できないとのエラーが発生します。

fail

with PATH.open('r') as f:
    bucket.upload_fileobj(f, KEY)

Pythonではbオプションをつけてファイルを開くとテキストではなくバイナリとして読み込まれます。

以下はモードによるfile objectの違いです。 BufferedReaderはバイナリのfile objectです。

open

with ORIGIN_PATH.open('r') as f:
    print(f'mode=r: {type(f)}')
# -> "mode=r: <class '_io.TextIOWrapper'>"

with ORIGIN_PATH.open('rb') as f:
    print(f'mode=rb: {type(f)}')

# -> "mode=rb: <class '_io.BufferedReader'>"

BytesIOを使用してオンメモリで処理する

バイナリのfile objectであれば良いので、適切な物を使えばオンメモリでファイルのやりとりが可能です。 以下ではBytesIOを使用してファイルのやりとりをしています。

BytesIO

with SAVE_PATH.open('wb') as f:
    bucket.download_fileobj(KEY, f)

with io.BytesIO(b'Lorem ipsum dolor sit amet') as f:
    bucket.upload_fileobj(f, KEY)

readで読み出す場合は注意が必要です。書き込んだあとはオフセットが進んでいるので0に戻す必要があります。 getvaluesを使用した場合は不要です。

read

with io.BytesIO() as f:
    print(f.read())
    # -> b''

    f.seek(0)
    print(f.read())
    # -> b'Lorem ipsum dolor sit amet'

with io.BytesIO() as f:
    bucket.download_fileobj(KEY, f)
    print(f.getvalue())
    # -> b'Lorem ipsum dolor sit amet'

HTTP版

読み込み

read

obj = bucket.Object(KEY)

print(obj.get()['Body'].read())

getはdictを返し、その中にBodyというプロパティが存在します。 このBodyStreamingBodyです。これはReadOnlyのfile objectのようなものです。

書き込み

書き込む場合はバイナリのfile objectかバイナリそのものが利用可能です。

write

with ORIGIN_PATH.open('rb') as f:
    obj.put(Body=f)

with io.BytesIO(b'Lorem ipsum dolor sit amet') as f:
    obj.put(Body=f)

obj.put(Body=b'Lorem ipsum dolor sit amet')

感想

S3にファイルを置いてPythonで読み書きしたい場合の整理ができたと思います。 オンメモリで取り扱う方法も整理できました。