Используя boto3, я могу получить доступ к своей корзине AWS S3:
s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket-name')
Теперь корзина содержит папку first-level
, которая, например, содержит несколько подпапок , названных с меткой времени 1456753904534
. Мне нужно знать названия этих подпапок для другой работы, которую я выполняю, и мне интересно, мог бы я получить их за меня с помощью boto3.
Итак, я попробовал:
objs = bucket.meta.client.list_objects(Bucket='my-bucket-name')
который дает словарь, ключ которого 'Contents' дает мне все файлы третьего уровня вместо каталогов временных меток второго уровня, фактически я получаю список, содержащий вещи как
{u'ETag ':' "etag" ', u'Key': first-level / 1456753904534 / part-00014 ', u'LastModified': datetime.datetime (2016, 2, 29, 13, 52, 24, tzinfo = tzutc ()),
u'Owner ': {u'DisplayName': 'owner', u'ID ':' id '},
u'Size': size, u'StorageClass ':' storageclass '}
вы можете видеть, что в этом случае part-00014
извлекаются определенные файлы, а я хотел бы получить только имя каталога. В принципе, я мог бы исключить имя каталога из всех путей, но некрасиво и дорого извлекать все на третьем уровне, чтобы получить второй уровень!
Я также пробовал что-то здесь :
for o in bucket.objects.filter(Delimiter='/'):
print(o.key)
но я не получаю папки на желаемом уровне.
Есть ли способ решить эту проблему?
источник
/
чтобы получить подпапкиОтветы:
S3 - это объектное хранилище, у него нет реальной структуры каталогов. Знак "/" скорее косметический. Одна из причин, по которой люди хотят иметь структуру каталогов, потому что они могут поддерживать / сокращать / добавлять дерево в приложение. Для S3 такая структура рассматривается как вид индекса или поискового тега.
Чтобы управлять объектом в S3, вам понадобится boto3.client или boto3.resource, например, чтобы вывести список всех объектов
import boto3 s3 = boto3.client("s3") all_objects = s3.list_objects(Bucket = 'bucket-name')
http://boto3.readthedocs.org/en/latest/reference/services/s3.html#S3.Client.list_objects
Фактически, если имя объекта s3 хранится с использованием разделителя '/'. Более новая версия list_objects (list_objects_v2) позволяет ограничить ответ ключами, которые начинаются с указанного префикса.
Чтобы ограничить элементы элементами в определенных подпапках:
import boto3 s3 = boto3.client("s3") response = s3.list_objects_v2( Bucket=BUCKET, Prefix ='DIR1/DIR2', MaxKeys=100 )
Документация
Другой вариант - использовать функцию python os.path для извлечения префикса папки. Проблема в том, что для этого потребуется перечислить объекты из нежелательных каталогов.
import os s3_key = 'first-level/1456753904534/part-00014' filename = os.path.basename(s3_key) foldername = os.path.dirname(s3_key) # if you are not using conventional delimiter like '#' s3_key = 'first-level#1456753904534#part-00014 filename = s3_key.split("#")[-1]
Напоминание о boto3: boto3.resource - хороший высокоуровневый API. Есть плюсы и минусы использования boto3.client vs boto3.resource. Если вы разрабатываете внутреннюю общую библиотеку, использование boto3.resource даст вам слой черного ящика над используемыми ресурсами.
источник
directory_name = os.path.dirname(directory/path/and/filename.txt)
andfile_name = os.path.basename(directory/path/and/filename.txt)
Приведенный ниже фрагмент кода возвращает ТОЛЬКО «подпапки» в «папке» из корзины s3.
import boto3 bucket = 'my-bucket' #Make sure you provide / in the end prefix = 'prefix-name-with-slash/' client = boto3.client('s3') result = client.list_objects(Bucket=bucket, Prefix=prefix, Delimiter='/') for o in result.get('CommonPrefixes'): print 'sub folder : ', o.get('Prefix')
Для получения дополнительной информации вы можете обратиться к https://github.com/boto/boto3/issues/134.
источник
Краткий ответ :
Используйте
Delimiter='/'
. Это позволяет избежать рекурсивного перечисления вашего ведра. Некоторые ответы здесь ошибочно предлагают сделать полный список и использовать некоторые строковые манипуляции для получения имен каталогов. Это могло быть ужасно неэффективным. Помните, что S3 практически не имеет ограничений на количество объектов, которые может содержать корзина. Итак, представьте, что междуbar/
и уfoo/
вас есть триллион объектов: вы очень долго ждете, чтобы получить['bar/', 'foo/']
.Используйте
Paginators
. По той же причине (S3 - инженерное приближение бесконечности) вы должны листать страницы и избегать сохранения всего списка в памяти. Вместо этого рассмотрите свой «листер» как итератор и обработайте создаваемый им поток.Используйте
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) # note: need to use a string just smaller than start, because # the list_object API specifies that start is excluded (the first # result is *after* 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']] # note: even with sorted lists, it is faster to sort(a+b) # than heapq.merge(a, b) at least up to 10K elements in each list 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 объекта для каждого каталога (плюс, конечно, один объект в каталоге; S3 хранит только объекты).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())
Полученная структура выглядит так:
Немного подправив приведенный выше код для
s3list
проверки ответов отpaginator
, вы можете увидеть некоторые забавные факты:Это
Marker
действительно эксклюзив. GivenMarker=topdir + 'mixed/0500_foo_a'
запустит листинг после этого ключа (в соответствии с API AmazonS3 ), то есть с.../mixed/0500_foo_b
. Это причина__prev_str()
.Используя
Delimiter
при листингеmixed/
, каждый ответ изpaginator
содержит 666 ключей и 334 общих префикса. Это довольно хорошо для того, чтобы не создавать огромных откликов.Напротив, при перечислении
dirs/
каждый ответ отpaginator
содержит 1000 общих префиксов (и без ключей).Передача лимита в виде
PaginationConfig={'MaxItems': limit}
ограничивает только количество ключей, а не общие префиксы. Мы справляемся с этим путем дальнейшего усечения потока нашего итератора.источник
Мне потребовалось много времени, чтобы понять, но, наконец, вот простой способ вывести список содержимого подпапки в корзине S3 с помощью boto3. Надеюсь, поможет
prefix = "folderone/foldertwo/" s3 = boto3.resource('s3') bucket = s3.Bucket(name="bucket_name_here") FilesNotFound = True for obj in bucket.objects.filter(Prefix=prefix): print('{0}:{1}'.format(bucket.name, obj.key)) FilesNotFound = False if FilesNotFound: print("ALERT", "No file in {0}/{1}".format(bucket, prefix))
источник
'/'
. Это позволяет вам пропускать «папки», полные объектов, без необходимости перемещать их по страницам. И затем, даже если вы настаиваете на полном листинге (т. Е. «Рекурсивном» эквиваленте в aws cli), тогда вы должны использовать пагинаторы, иначе вы перечисляете только первые 1000 объектов.limit
к нему в своем производном ответе .Большая реализация S3 заключается в том, что нет папок / каталогов, только ключи. Структура очевидной папки только предваряет имя файла , чтобы стать «Key», так чтобы перечислить содержимое
myBucket
«Ssome/path/to/the/file/
вы можете попробовать:s3 = boto3.client('s3') for obj in s3.list_objects_v2(Bucket="myBucket", Prefix="some/path/to/the/file/")['Contents']: print(obj['Key'])
что даст вам что-то вроде:
источник
У меня была такая же проблема , но удалось решить его с помощью
boto3.client
иlist_objects_v2
сBucket
иStartAfter
параметрами.s3client = boto3.client('s3') bucket = 'my-bucket-name' startAfter = 'firstlevelFolder/secondLevelFolder' theobjects = s3client.list_objects_v2(Bucket=bucket, StartAfter=startAfter ) for object in theobjects['Contents']: print object['Key']
Результат вывода для приведенного выше кода будет отображать следующее:
Boto3 list_objects_v2 Документация
Чтобы вырезать только имя каталога,
secondLevelFolder
я просто использовал метод pythonsplit()
:s3client = boto3.client('s3') bucket = 'my-bucket-name' startAfter = 'firstlevelFolder/secondLevelFolder' theobjects = s3client.list_objects_v2(Bucket=bucket, StartAfter=startAfter ) for object in theobjects['Contents']: direcoryName = object['Key'].encode("string_escape").split('/') print direcoryName[1]
Результат вывода для приведенного выше кода будет отображать следующее:
Документация Python split ()
Если вы хотите получить имя каталога И имя элемента содержимого, замените строку печати следующим:
print "{}/{}".format(fileName[1], fileName[2])
И будет выведено следующее:
Надеюсь это поможет
источник
У меня работает ... Объекты S3:
С помощью:
from boto3.session import Session s3client = session.client('s3') resp = s3client.list_objects(Bucket=bucket, Prefix='', Delimiter="/") forms = [x['Prefix'] for x in resp['CommonPrefixes']]
мы получили:
С участием:
resp = s3client.list_objects(Bucket=bucket, Prefix='form1/', Delimiter="/") sections = [x['Prefix'] for x in resp['CommonPrefixes']]
мы получили:
источник
AWS cli делает это (предположительно, без выборки и повторения всех ключей в корзине) при запуске
aws s3 ls s3://my-bucket/
, поэтому я решил, что должен быть способ использовать boto3.https://github.com/aws/aws-cli/blob/0fedc4c1b6a7aee13e2ed10c3ada778c702c22c3/awscli/customizations/s3/subcommands.py#L499
Похоже, что они действительно используют префикс и разделитель - я смог написать функцию, которая будет получать все каталоги на корневом уровне корзины, немного изменив этот код:
def list_folders_in_bucket(bucket): paginator = boto3.client('s3').get_paginator('list_objects') folders = [] iterator = paginator.paginate(Bucket=bucket, Prefix='', Delimiter='/', PaginationConfig={'PageSize': None}) for response_data in iterator: prefixes = response_data.get('CommonPrefixes', []) for prefix in prefixes: prefix_name = prefix['Prefix'] if prefix_name.endswith('/'): folders.append(prefix_name.rstrip('/')) return folders
источник
Вот возможное решение:
def download_list_s3_folder(my_bucket,my_folder): import boto3 s3 = boto3.client('s3') response = s3.list_objects_v2( Bucket=my_bucket, Prefix=my_folder, MaxKeys=1000) return [item["Key"] for item in response['Contents']]
источник
Почему бы не использовать
s3path
пакет, с которым так же удобно, как и работатьpathlib
? Однако если вы должны использоватьboto3
:С помощью
boto3.resource
Это основано на ответе itz-azhar о применении необязательного
limit
. Очевидно, что пользоваться им существенно проще, чемboto3.client
версией.import logging from typing import List, Optional import boto3 from boto3_type_annotations.s3 import ObjectSummary # pip install boto3_type_annotations log = logging.getLogger(__name__) _S3_RESOURCE = boto3.resource("s3") def s3_list(bucket_name: str, prefix: str, *, limit: Optional[int] = None) -> List[ObjectSummary]: """Return a list of S3 object summaries.""" # Ref: https://stackoverflow.com/a/57718002/ return list(_S3_RESOURCE.Bucket(bucket_name).objects.limit(count=limit).filter(Prefix=prefix)) if __name__ == "__main__": s3_list("noaa-gefs-pds", "gefs.20190828/12/pgrb2a", limit=10_000)
С помощью
boto3.client
Это использует
list_objects_v2
и основывается на ответе CpILL, позволяя получить более 1000 объектов.import logging from typing import cast, List import boto3 log = logging.getLogger(__name__) _S3_CLIENT = boto3.client("s3") def s3_list(bucket_name: str, prefix: str, *, limit: int = cast(int, float("inf"))) -> List[dict]: """Return a list of S3 object summaries.""" # Ref: https://stackoverflow.com/a/57718002/ contents: List[dict] = [] continuation_token = None if limit <= 0: return contents while True: max_keys = min(1000, limit - len(contents)) request_kwargs = {"Bucket": bucket_name, "Prefix": prefix, "MaxKeys": max_keys} if continuation_token: log.info( # type: ignore "Listing %s objects in s3://%s/%s using continuation token ending with %s with %s objects listed thus far.", max_keys, bucket_name, prefix, continuation_token[-6:], len(contents)) # pylint: disable=unsubscriptable-object response = _S3_CLIENT.list_objects_v2(**request_kwargs, ContinuationToken=continuation_token) else: log.info("Listing %s objects in s3://%s/%s with %s objects listed thus far.", max_keys, bucket_name, prefix, len(contents)) response = _S3_CLIENT.list_objects_v2(**request_kwargs) assert response["ResponseMetadata"]["HTTPStatusCode"] == 200 contents.extend(response["Contents"]) is_truncated = response["IsTruncated"] if (not is_truncated) or (len(contents) >= limit): break continuation_token = response["NextContinuationToken"] assert len(contents) <= limit log.info("Returning %s objects from s3://%s/%s.", len(contents), bucket_name, prefix) return contents if __name__ == "__main__": s3_list("noaa-gefs-pds", "gefs.20190828/12/pgrb2a", limit=10_000)
источник
Прежде всего, в S3 нет настоящей концепции папок. Вы определенно можете иметь файл @
'/folder/subfolder/myfile.txt'
а не папка или подпапка.Чтобы «имитировать» папку в S3, вы должны создать пустой файл со знаком «/» в конце его имени (см. Amazon S3 Boto - как создать папку? )
Для вашей проблемы вам, вероятно, следует использовать метод
get_all_keys
с двумя параметрами:prefix
иdelimiter
https://github.com/boto/boto/blob/develop/boto/s3/bucket.py#L427
for key in bucket.get_all_keys(prefix='first-level/', delimiter='/'): print(key.name)
источник
list
сprefix
иdelimiter
. Полагаю, это должно сработать.Я знаю, что здесь обсуждается тема boto3, но я считаю, что обычно быстрее и интуитивно понятнее просто использовать awscli для чего-то вроде этого - awscli сохраняет больше возможностей, чем boto3, за то, чего стоит.
Например, если у меня есть объекты, сохраненные во «подпапках», связанных с данным сегментом, я могу перечислить их все с помощью чего-то вроде этого:
Итак, мы можем представить себе «абсолютный путь», ведущий к этим объектам: 'mydata / f1 / f2 / f3 / foo2.csv' ...
Используя команды awscli, мы можем легко перечислить все объекты внутри заданной «подпапки» с помощью:
источник
Ниже приведен фрагмент кода, который может обрабатывать разбиение на страницы, если вы пытаетесь получить большое количество объектов корзины S3:
def get_matching_s3_objects(bucket, prefix="", suffix=""): s3 = boto3.client("s3") paginator = s3.get_paginator("list_objects_v2") kwargs = {'Bucket': bucket} # We can pass the prefix directly to the S3 API. If the user has passed # a tuple or list of prefixes, we go through them one by one. if isinstance(prefix, str): prefixes = (prefix, ) else: prefixes = prefix for key_prefix in prefixes: kwargs["Prefix"] = key_prefix for page in paginator.paginate(**kwargs): try: contents = page["Contents"] except KeyError: return for obj in contents: key = obj["Key"] if key.endswith(suffix): yield obj
источник
Что касается Boto 1.13.3, он оказывается таким простым (если вы пропустите все соображения по разбивке на страницы, которые были рассмотрены в других ответах):
def get_sub_paths(bucket, prefix): s3 = boto3.client('s3') response = s3.list_objects_v2( Bucket=bucket, Prefix=prefix, MaxKeys=1000) return [item["Prefix"] for item in response['CommonPrefixes']]
источник