সংক্ষিপ্ত উত্তর :
ব্যবহার Delimiter='/'
। এটি আপনার বালতির একটি পুনরাবৃত্ত তালিকা করা থেকে বিরত থাকে। এখানে কিছু উত্তর ভুলভাবে পুরো তালিকা তৈরি করার এবং ডিরেক্টরিগুলির নামগুলি পুনরুদ্ধার করতে কিছু স্ট্রিং ম্যানিপুলেশন ব্যবহার করার পরামর্শ দেয়। এটি মারাত্মকভাবে অদক্ষ হতে পারে। মনে রাখবেন যে বালতিতে থাকা বস্তুর সংখ্যার উপর এস 3 এর কার্যত কোনও সীমা নেই। সুতরাং, কল্পনা করুন যে এর মধ্যে bar/
এবং foo/
আপনার কাছে একটি ট্রিলিয়ন বস্তু রয়েছে: আপনি পেতে খুব দীর্ঘ সময় অপেক্ষা করবেন ['bar/', 'foo/']
।
ব্যবহার Paginators
। একই কারণে (এস 3 একটি ইঞ্জিনিয়ারের অনন্তের অনুমান), আপনাকে অবশ্যই পৃষ্ঠাগুলির মাধ্যমে তালিকা তৈরি করতে হবে এবং মেমরির সমস্ত তালিকা সঞ্চয় করা এড়াতে হবে। পরিবর্তে, আপনার "লিস্টার "টিকে পুনরুক্তি হিসাবে বিবেচনা করুন এবং এটি উত্পাদিত স্ট্রিমটি পরিচালনা করুন।
ব্যবহার boto3.client
, না boto3.resource
। resource
সংস্করণ ভাল হ্যান্ডেল বলে মনে হচ্ছে না Delimiter
বিকল্প। আপনি যদি একটি সংস্থান আছে, তাহলে একটি বলে bucket = boto3.resource('s3').Bucket(name)
আপনার সাথে সংশ্লিষ্ট ক্লায়েন্ট পেতে পারেন: bucket.meta.client
।
দীর্ঘ উত্তর :
নিম্নলিখিতটি একটি পুনরাবৃত্তিকারী যা আমি সাধারণ বালতিগুলির জন্য ব্যবহার করি (কোনও সংস্করণ হ্যান্ডলিং নেই)।
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
পরীক্ষা :
নিম্নলিখিত আচরণ পরীক্ষা করার জন্য সহায়ক paginator
এবং list_objects
। এটি অনেকগুলি ডায়ার এবং ফাইল তৈরি করে। যেহেতু পৃষ্ঠাগুলি 1000 এন্ট্রি পর্যন্ত, আমরা ডায়ার এবং ফাইলগুলির জন্য এর একাধিকটি ব্যবহার করি। dirs
কেবলমাত্র ডিরেক্টরি রয়েছে (প্রত্যেকটির একটি করে অবজেক্ট রয়েছে)। mixed
প্রতিটি ডিরের জন্য 2 টি বস্তুর অনুপাত সহ ডায়ার এবং অবজেক্টের সংমিশ্রণ রয়েছে (অবশ্যই ডির অধীনে একটি বস্তু অবশ্যই; এস 3 কেবলমাত্র বস্তু সঞ্চয় করে)।
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())
ফলস্বরূপ কাঠামোটি হ'ল:
./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
এর s3list
থেকে প্রাপ্ত প্রতিক্রিয়াগুলি পরীক্ষা করার জন্য উপরে দেওয়া কোডটির কিছুটা চিকিত্সা সহ paginator
আপনি কিছু মজাদার তথ্য পর্যবেক্ষণ করতে পারেন:
Marker
সত্যিই স্বতন্ত্র। প্রদত্ত Marker=topdir + 'mixed/0500_foo_a'
তালিকাটি সেই কী ( আমাজনএস 3 এপিআই অনুসারে ) এর পরে শুরু করবে , যার সাথে । এটাই কারণ ।.../mixed/0500_foo_b
__prev_str()
Delimiter
তালিকাবদ্ধ করার সময় ব্যবহার করে , mixed/
প্রতিটি প্রতিক্রিয়াতে paginator
666 কী এবং 334 সাধারণ উপসর্গ থাকে। প্রচন্ড প্রতিক্রিয়া না গড়ে এটি বেশ ভাল।
বিপরীতে, তালিকাবদ্ধ করার সময় dirs/
, এর প্রতিটি প্রতিক্রিয়াতে paginator
1000 টি সাধারণ উপসর্গ (এবং কোনও কী নেই) থাকে।
সীমাবদ্ধ আকারে PaginationConfig={'MaxItems': limit}
সীমা অতিক্রম করা কেবল কীগুলির সংখ্যা, সাধারণ উপসর্গ নয়। আমরা আমাদের পুনরুক্তকারীর স্ট্রিমটি আরও ছাঁটাই করে তা মোকাবিলা করি।