0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

boto3でS3オブジェクト一覧を1000件以上取得する方法

Posted at

きっかけ

AWS S3のバケットからオブジェクト一覧を取得しようと
boto3のlist_objects_v2を使っていましたが、
リストの上限(1000件)があるため、
一部のモジュールが取得漏れ
していることに気づきました。

小規模なバケットでは問題なかったため、
見逃しやすい落とし穴だと思います。


対応

調査の結果、boto3には
**Paginator(ページネーター)**機能があり、
ループ処理で全件取得できることが分かりました。

上限1000件を気にせず、すべてのオブジェクトを取得できます。


実装例

以下のように、Paginatorを使ってすべてのページをたどり、
さらにyieldを使ってスマートにオブジェクトを返すようにしています。

def list_objects_v2_paginator(self, prefix='', suffix=''):
    paginator = self.client.get_paginator('list_objects_v2')
    page_iterator = paginator.paginate(Bucket=self.bucket_name, Prefix=prefix)

    for page in page_iterator:
        if 'Contents' in page:
            for obj in page['Contents']:
                key = obj['Key']
                if suffix:
                    if key.endswith(suffix):
                        yield obj
                else:
                    yield obj

Paginatorを利用するとなぜできるのか?

通常のlist_objects_v2は1回の呼び出しで最大1000件しか返せませんが、
Paginatorを使うと、

  • API内部で**ContinuationToken(継続トークン)**を自動的に扱い
  • 次のページも順番にたどって
  • 最後のページまですべてのデータを取得してくれます。

つまり、プログラム側で何も意識せずに全件取得できるようになります!


【補足】yieldを使うと何が違うのか?【一括読み込みとの比較】

通常の一括読み込み(リストにためるパターン)

例えばS3のオブジェクトを全部リストにためてから返すと、こうなります。

def list_all_objects(self):
    objects = []
    paginator = self.client.get_paginator('list_objects_v2')
    page_iterator = paginator.paginate(Bucket=self.bucket_name)

    for page in page_iterator:
        if 'Contents' in page:
            for obj in page['Contents']:
                objects.append(obj)
    return objects

この場合、

  • 最後まで全部読み込んでから返す
  • オブジェクト数が多いとメモリを大量に消費する
  • 全部読み終わるまで次の処理に進めない

という特徴になります。


yieldを使った場合(毎回読み込み)

先ほどのlist_objects_v2_paginatoryieldを使っているので、

  • 1件読み込むたびに返す
  • メモリに大量データをため込まない
  • すぐに次の処理に進める(読みながら処理できる)

という動きになります。

def list_objects_v2_paginator(self, prefix='', suffix=''):
    paginator = self.client.get_paginator('list_objects_v2')
    page_iterator = paginator.paginate(Bucket=self.bucket_name, Prefix=prefix)

    for page in page_iterator:
        if 'Contents' in page:
            for obj in page['Contents']:
                key = obj['Key']
                if suffix:
                    if key.endswith(suffix):
                        yield obj
                else:
                    yield obj

比較まとめ

項目 一括読み込み (リスト) 毎回読み込み (yield)
メモリ使用量 多い(全部保持) 少ない(1件ずつ)
データ取得タイミング 最後まで読んでから 1件ずつすぐ使える
小規模データ(数百件)向き
大規模データ(数万件)向き △(メモリ注意) ◎(安定)
コードのわかりやすさ シンプル(初心者向き) 少し慣れが必要

まとめ

  • boto3のlist_objects_v2単体では1000件上限に注意!
  • Paginatorを使えば全件取得できる
  • yieldを使えばメモリ効率もよくなる

小規模なら一括取得でも問題ないですが、
数千~数万件以上のオブジェクトを扱うなら、
Paginator+yieldが必須テクニックです!


0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?