Kısa cevap :
Kullanım Delimiter='/'
. Bu, paketinizin yinelemeli bir listesini yapmaktan kaçınır. Buradaki bazı yanıtlar, yanlış bir şekilde tam bir liste yapmayı ve dizin adlarını almak için bir dizi düzenleme kullanmayı önerir. Bu korkunç derecede verimsiz olabilir. S3'ün, bir paketin içerebileceği nesne sayısı konusunda neredeyse hiçbir sınırı olmadığını unutmayın. Öyleyse, bar/
ve arasında foo/
bir trilyon nesneye sahip olduğunuzu hayal edin : elde etmek için çok uzun bir süre beklersiniz ['bar/', 'foo/']
.
Kullanım Paginators
. Aynı nedenle (S3 sonsuz bir mühendisin tahmindir), sen gerekir tüm bellekte listeleme depolamak sayfaları ve kaçınmak yoluyla sıralar. Bunun yerine, "dinleyicinizi" bir yineleyici olarak düşünün ve ürettiği akışı işleyin.
Kullanım boto3.client
, değil boto3.resource
. resource
Versiyon iyi işlemek için görünmüyor Delimiter
seçeneği. Bir kaynağınız varsa,bucket = boto3.resource('s3').Bucket(name)
, sen ile ilgili müşteri alabilirsiniz: bucket.meta.client
.
Uzun cevap :
Aşağıda, basit kovalar için kullandığım bir yineleyici gösterilmektedir (sürüm işleme yok).
import boto3
from collections import namedtuple
from operator import attrgetter
S3Obj = namedtuple('S3Obj', ['key', 'mtime', 'size', 'ETag'])
def s3list(bucket, path, start=None, end=None, recursive=True, list_dirs=True,
list_objs=True, limit=None):
"""
Iterator that lists a bucket's objects under path, (optionally) starting with
start and ending before end.
If recursive is False, then list only the "depth=0" items (dirs and objects).
If recursive is True, then list recursively all objects (no dirs).
Args:
bucket:
a boto3.resource('s3').Bucket().
path:
a directory in the bucket.
start:
optional: start key, inclusive (may be a relative path under path, or
absolute in the bucket)
end:
optional: stop key, exclusive (may be a relative path under path, or
absolute in the bucket)
recursive:
optional, default True. If True, lists only objects. If False, lists
only depth 0 "directories" and objects.
list_dirs:
optional, default True. Has no effect in recursive listing. On
non-recursive listing, if False, then directories are omitted.
list_objs:
optional, default True. If False, then directories are omitted.
limit:
optional. If specified, then lists at most this many items.
Returns:
an iterator of S3Obj.
Examples:
# set up
>>> s3 = boto3.resource('s3')
... bucket = s3.Bucket(name)
# iterate through all S3 objects under some dir
>>> for p in s3ls(bucket, 'some/dir'):
... print(p)
# iterate through up to 20 S3 objects under some dir, starting with foo_0010
>>> for p in s3ls(bucket, 'some/dir', limit=20, start='foo_0010'):
... print(p)
# non-recursive listing under some dir:
>>> for p in s3ls(bucket, 'some/dir', recursive=False):
... print(p)
# non-recursive listing under some dir, listing only dirs:
>>> for p in s3ls(bucket, 'some/dir', recursive=False, list_objs=False):
... print(p)
"""
kwargs = dict()
if start is not None:
if not start.startswith(path):
start = os.path.join(path, start)
kwargs.update(Marker=__prev_str(start))
if end is not None:
if not end.startswith(path):
end = os.path.join(path, end)
if not recursive:
kwargs.update(Delimiter='/')
if not path.endswith('/'):
path += '/'
kwargs.update(Prefix=path)
if limit is not None:
kwargs.update(PaginationConfig={'MaxItems': limit})
paginator = bucket.meta.client.get_paginator('list_objects')
for resp in paginator.paginate(Bucket=bucket.name, **kwargs):
q = []
if 'CommonPrefixes' in resp and list_dirs:
q = [S3Obj(f['Prefix'], None, None, None) for f in resp['CommonPrefixes']]
if 'Contents' in resp and list_objs:
q += [S3Obj(f['Key'], f['LastModified'], f['Size'], f['ETag']) for f in resp['Contents']]
q = sorted(q, key=attrgetter('key'))
if limit is not None:
q = q[:limit]
limit -= len(q)
for p in q:
if end is not None and p.key >= end:
return
yield p
def __prev_str(s):
if len(s) == 0:
return s
s, c = s[:-1], ord(s[-1])
if c > 0:
s += chr(c - 1)
s += ''.join(['\u7FFF' for _ in range(10)])
return s
Ölçek :
Aşağıdaki davranışını test etmek yararlıdır paginator
velist_objects
. Bir dizi dizin ve dosya oluşturur. Sayfalar 1000 girişe kadar olduğundan, dizinler ve dosyalar için bunun bir katını kullanırız. dirs
yalnızca dizinleri içerir (her biri bir nesneye sahiptir) mixed
her bir dizin için 2 nesne oranına sahip (ayrıca dir altında bir nesne; S3 yalnızca nesneleri depolar) bir dizi ve nesne karışımı içerir.
import concurrent
def genkeys(top='tmp/test', n=2000):
for k in range(n):
if k % 100 == 0:
print(k)
for name in [
os.path.join(top, 'dirs', f'{k:04d}_dir', 'foo'),
os.path.join(top, 'mixed', f'{k:04d}_dir', 'foo'),
os.path.join(top, 'mixed', f'{k:04d}_foo_a'),
os.path.join(top, 'mixed', f'{k:04d}_foo_b'),
]:
yield name
with concurrent.futures.ThreadPoolExecutor(max_workers=32) as executor:
executor.map(lambda name: bucket.put_object(Key=name, Body='hi\n'.encode()), genkeys())
Ortaya çıkan yapı:
./dirs/0000_dir/foo
./dirs/0001_dir/foo
./dirs/0002_dir/foo
...
./dirs/1999_dir/foo
./mixed/0000_dir/foo
./mixed/0000_foo_a
./mixed/0000_foo_b
./mixed/0001_dir/foo
./mixed/0001_foo_a
./mixed/0001_foo_b
./mixed/0002_dir/foo
./mixed/0002_foo_a
./mixed/0002_foo_b
...
./mixed/1999_dir/foo
./mixed/1999_foo_a
./mixed/1999_foo_b
Yukarıda verilen kodun biraz düzeltilmesiyle s3list
yaparak, yanıtları incelemek içinpaginator
, bazı eğlenceli gerçekleri gözlemleyebilirsiniz:
Marker
Gerçekten özeldir. Verilen Marker=topdir + 'mixed/0500_foo_a'
, listelemenin bu anahtardan sonra ( AmazonS3 API'ye göre ), yani.../mixed/0500_foo_b
. Nedeni bu__prev_str()
.
Delimiter
Listeleme sırasında kullanmamixed/
, gelen her yanıt paginator
666 tuşları ve 334 ortak önekleri içerir. Çok büyük tepkiler oluşturmamakta oldukça iyidir.
Buna karşılık, listeleme sırasında dirs/
, her yanıt paginator
1000 ortak ön ek içerir (ve anahtar içermez).
Biçiminde bir sınır geçmek PaginationConfig={'MaxItems': limit}
, ortak önekleri değil, yalnızca anahtar sayısını sınırlar. Yineleyicimizin akışını daha da keserek bunun üstesinden geliyoruz.