Boto 3 で、S3 Buckets 上にある key を取得するときには、list_objects()
を使います。prefix を指定して、条件を絞ることもできます。S3 で key を取得するときにはよく使われるメソッドだと思います。
基本的な使いかた
たとえば、バケット名: hoge-bucket
にある、プレフィックス: xx/yy
の key を全て取得したい時は以下のようにします。
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 を全て取得したいのであれば、
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 が少ないときには以下のようなやり方でも良いかもしれません。
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 件しか返せなかった場合、この IsTruncated
が True
になります。ちなみに、'Contents'
配列は、常に 'Key'
のアルファベット順を基準に昇順で並べられています。
また list_objects()
には、Marker
という引数があり、指定した key を 1 件目として、結果を出力することができます。これで役者は揃いました。以下は list_objects()
をラップして作った、件数を問わず指定した全ての key を取得する関数です。
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 を取得することができるようになりました!