104
83

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-07-02

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 を取得することができるようになりました!

104
83
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
104
83

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?