Edited at

Boto 3 で Amazon S3 上の key を取得する方法、実装例、注意点

More than 3 years have passed since last update.

Boto 3 で、S3 Buckets 上にある key を取得するときには、list_objects() を使います。prefix を指定して、条件を絞ることもできます。S3 で key を取得するときにはよく使われるメソッドだと思います。


基本的な使いかた

たとえば、バケット名: hoge-bucket にある、プレフィックス: xx/yy の key を全て取得したい時は以下のようにします。


sample1.py

from boto3 import Session

s3client = Session().client('s3')

response = s3client.list_objects(
Bucket='hoge-bucket',
Prefix='xx/yy/'
)

if 'Contents' in response: # 該当する key がないと response に 'Contents' が含まれない
keys = [content['Key'] for content in response['Contents']]


これで、Prefix で指定した条件の key があれば、keys には

>>> keys

['xx/yy/a1', 'xx/yy/a2', 'xx/yy/a3', 'xx/yy/b1']

のように key 文字列の配列が代入されているはずです。

ちなみに、Prefix は'xx/yy/a' のように指定することもできます。その場合は以下のような結果が返ります。

>>> keys

['xx/yy/a1', 'xx/yy/a2', 'xx/yy/a3']


注意点

list_objects() には (正確には Amazon S3 API には)、一度に取得できるのは 1000 key までという制限があります。単純に、バケットの下にある、1000 件以上の key を全て取得したいのであれば、


sample2.py

from boto3 import Session

s3res = Session().resource('s3')

bucket = s3res.Bucket('hoge-bucket')
keys = [obj.key for obj in bucket.objects.all()]


で良いでしょう。


Prefix 条件をつけて 1000 件以上の key を取得するには

ただ、もし Prefix を指定したいときはどうなるでしょうか? 上述した sample1.py では、keys に代入されるのは多くても 1000 件です。引数の MaxKeys に 1000000 を指定しようとも、仕様により 1000 件以上は返ってきません。


方法1: 一度全件取得してからフィルタする

hoge-bucket の下にある key が少ないときには以下のようなやり方でも良いかもしれません。


sample3.py

from boto3 import Session

s3res = Session().resource('s3')

bucket = s3res.Bucket('hoge-bucket')
keys = [obj.key for obj in bucket.objects.all() if obj.key.startswith("xx/yy/")]


ただ、もし key が何万、何十万とあると、結果を得るためにかなりの時間がかかります。モデルで出来る部分は極力モデルに任せたい所です。


方法2: list_objects()Marker を使う

list_objects() の返り値は、以下のような形のハッシュになっています。

{

'IsTruncated': True|False, # 結果が寸断されたか。されていれば True
'Marker': 'string',
'NextMarker': 'string',
'Contents': [
{
'Key': 'string',
'LastModified': datetime(2015, 1, 1),
'ETag': 'string',
'Size': 123,
'StorageClass': 'STANDARD'|'REDUCED_REDUNDANCY'|'GLACIER',
'Owner': {
'DisplayName': 'string',
'ID': 'string'
}
},
],
'Name': 'string',
'Prefix': 'string',
'Delimiter': 'string',
'MaxKeys': 123,
'CommonPrefixes': [
{
'Prefix': 'string'
},
],
'EncodingType': 'url'
}

ここで重要なのは IsTruncated です。もし結果が 1000 件以上あるのに、1000 件しか返せなかった場合、この IsTruncatedTrue になります。ちなみに、'Contents' 配列は、常に 'Key' のアルファベット順を基準に昇順で並べられています。

また list_objects() には、Marker という引数があり、指定した key を 1 件目として、結果を出力することができます。これで役者は揃いました。以下は list_objects() をラップして作った、件数を問わず指定した全ての key を取得する関数です。


sample4.py

from boto3 import Session

s3client = Session().client('s3')

def get_all_keys(bucket: str='', prefix: str='', keys: []=[], marker: str='') -> [str]:
"""
指定した prefix のすべての key の配列を返す
"""

response = s3client.list_objects(Bucket=bucket, Prefix=prefix, Marker=marker)
if 'Contents' in response: # 該当する key がないと response に 'Contents' が含まれない
keys.extend([content['Key'] for content in response['Contents']])
if 'IsTruncated' in response:
return get_all_keys(bucket=bucket, prefix=prefix, keys=keys, marker=keys[-1])
return keys


if 'IsTruncated' in response: で、もし IsTruncated であれば、keys のケツ (keys[-1]) を marker にして自分自身を呼び出しています。response に Contents がなくなったら、あるいは IsTruncated でなくなれば一気に結果を返します。

これで、件数を気にせずに、S3 上の key を取得することができるようになりました!