S3のバケット内のオブジェクトをすべて取得する

今回はS3のすべてのオブジェクトのボディを含まない情報を取得するサンプルコードを書いてみました。 プレフィックス以外でのオブジェクトの検索やより高頻度でのオブジェクト数の監視などに有用かとおもいます。
2022.09.30

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

サンプルコード

Boto3には高レベルAPIと低レベルAPIがあります。 高レベルAPIはクラスベースで実装されており、より少ないコード量で記述が可能です。 低レベルAPIではより細かい制御が可能です。 基本的には高レベルAPIの仕様をおすすめします。

高レベルAPI

高レベルAPI

import boto3
s3 = boto3.resource('s3')
def get_all_objects_high(bucket):
    bucket = s3.Bucket(bucket)
    return bucket.objects.all()

objs = get_all_objects_high(TARGET_BUCKET)
for i,obj in enumerate(iter(objs)):
    logger.info(f'{i}: {obj.key}')
=> 0: xxx
   1: yyy
   2: zzz

bucket.objectsの返り値はObjectSummaryCollectionです。 今回はall()メソッドを使用してすべてのオブジェクトを取得しています。

ObjectSummaryの仕様は以下の通りです。 ↑のサンプルコードではkeyを使用しています。

Collectionは以下のようなものです。 プレフィックスによるフィルタリングやページネーションがメソッドのチェーンで行えるので便利です。

オブジェクトのBodyは含まれていないので、ObjectSummarygetを使用して取得する必要があります。

低レベルAPI

低レベルAPI

import boto3

s3 = boto3.client('s3')

def get_all_objects_low(bucket):
    continuation_token = None
    while True:
        if continuation_token is None:
            res = s3.list_objects_v2(
                Bucket=bucket,
                MaxKeys=2
            )
        else:
            res = s3.list_objects_v2(
                    Bucket=bucket,
                    ContinuationToken=continuation_token
                )

        # バケットが空の場合Contentsフィールドがなくなる
        if res['KeyCount'] == 0:
            break

        for content in res['Contents']:
            yield content

        # ContinuationTokenが渡されなかったらそこで終わり
        continuation_token = res.get('NextContinuationToken')
        if continuation_token is None:
            break
objs = get_all_objects_low(TARGET_BUCKET)
for i,obj in enumerate(iter(objs)):
    logger.info(f'{i}: {obj["Key"]}')
=> 0: xxx
   1: yyy
   2: zzz

コードとしては上記のget_all_objectsがそれに該当します。 バケット名を引数に渡すことで、そのバケット内のオブジェクトの情報(ボディ含まない)を取得可能です。

今回はS3のlist_objcets_v2を使用しています。

関数自体はジェネレータになっているので、1000個単位でS3にリクエストを送り、情報を取得しています。 関数の返り値としては以下のようなものが返されます。 入っている内容はObjectSummaryと同じです。

Contents

{
    'Key': 'string',
    'LastModified': datetime(2015, 1, 1),
    'ETag': 'string',
    'ChecksumAlgorithm': [
        'CRC32'|'CRC32C'|'SHA1'|'SHA256',
    ],
    'Size': 123,
    'StorageClass': 'STANDARD'|'REDUCED_REDUNDANCY'|'GLACIER'|'STANDARD_IA'|'ONEZONE_IA'|'INTELLIGENT_TIERING'|'DEEP_ARCHIVE'|'OUTPOSTS'|'GLACIER_IR',
    'Owner': {
        'DisplayName': 'string',
        'ID': 'string'
    }
}

BodyやContentTypeなどは含まれていないので必要な場合は別途、get_objectなどの関数で取得する必要があります。

最後に

今回は2つのサンプルコードを書いてみました。 改めて見ると、低レベルAPIはコード量が多いです。(高レベルAPIに感謝) ただ、やっぱり細かい制御を行いたい場合もあるので両方あると便利ですね。