Получение имен вложенных папок в корзине S3 из boto3

93

Используя 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)

но я не получаю папки на желаемом уровне.

Есть ли способ решить эту проблему?

мартини
источник
Так вы говорите, что это не работает? Не могли бы вы опубликовать, что происходит, когда вы это запускаете?
Джордон Филлипс,
1
@JordonPhillips Я пробовал первые строки этой ссылки, которую вы отправляете, которую я вставил сюда, и получаю текстовые файлы на самом первом уровне корзины и без папок.
marin
@mar tin Вы когда-нибудь решали эту проблему. Я столкнулся с аналогичной дилеммой, когда мне нужен первый элемент в каждой подпапке bucket'ов.
Тед Тейлор из Life,
1
@TedTaylorofLife Да, нет другого способа, кроме как получить все объекты и разделить их, /чтобы получить подпапки
мартин
1
@ mar tin Единственный способ, который я сделал, - это взять вывод, преобразовать его в текстовый формат и разделить запятыми "/", а затем скопировать и вставить первый элемент. Какая заноза в заднице.
Тед Тейлор из Life,

Ответы:

65

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 даст вам слой черного ящика над используемыми ресурсами.

спор
источник
2
Это дает мне тот же результат, который я получаю с моей попыткой в ​​вопросе. Я думаю, мне придется решить сложный путь, взяв все ключи из возвращенных объектов и разделив строку, чтобы получить имя папки.
marin
1
@martina: ленивый питон разделяет и выбирает последние данные из списка, например filename = keyname.split ("/") [- 1]
mootmoot 07
1
@martin directory_name = os.path.dirname(directory/path/and/filename.txt)andfile_name = os.path.basename(directory/path/and/filename.txt)
jkdev 03
109

Приведенный ниже фрагмент кода возвращает ТОЛЬКО «подпапки» в «папке» из корзины 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.

Дипанкар
источник
12
Что, если я хочу вывести список содержимого определенной подпапки?
azhar22k
1
@ azhar22k, я предполагаю, что вы можете просто запустить функцию рекурсивно для каждой «подпапки».
Сербан Цезарь
Что делать, если разных префиксов больше 1000?
Kostrahb
42

Краткий ответ :

  • Используйте 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())

Полученная структура выглядит так:

./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действительно эксклюзив. Given Marker=topdir + 'mixed/0500_foo_a'запустит листинг после этого ключа (в соответствии с API AmazonS3 ), то есть с .../mixed/0500_foo_b. Это причина __prev_str().

  • Используя Delimiterпри листинге mixed/, каждый ответ из paginatorсодержит 666 ключей и 334 общих префикса. Это довольно хорошо для того, чтобы не создавать огромных откликов.

  • Напротив, при перечислении dirs/каждый ответ от paginatorсодержит 1000 общих префиксов (и без ключей).

  • Передача лимита в виде PaginationConfig={'MaxItems': limit}ограничивает только количество ключей, а не общие префиксы. Мы справляемся с этим путем дальнейшего усечения потока нашего итератора.

Пьер Д
источник
@Mehdi: это действительно не так уж сложно для системы, которая предлагает такой невероятный масштаб и надежность. Если вы когда-нибудь будете иметь дело с более чем несколькими сотнями ТБ, вы получите признание за то, что они предлагают. Помните, диски всегда имеют MTBF> 0 ... Подумайте о последствиях для крупномасштабного хранилища данных. Отказ от ответственности: я активный и счастливый пользователь AWS, другого подключения нет, за исключением того, что я работал с данными петабайтного масштаба с 2007 года, и раньше это было намного сложнее.
Pierre D
39

Мне потребовалось много времени, чтобы понять, но, наконец, вот простой способ вывести список содержимого подпапки в корзине 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))
azhar22k
источник
3
что, если ваша папка содержит огромное количество объектов?
Pierre D
3
Я хочу сказать, что это ужасно неэффективное решение. S3 создан для работы с произвольными разделителями в ключах. Например, '/'. Это позволяет вам пропускать «папки», полные объектов, без необходимости перемещать их по страницам. И затем, даже если вы настаиваете на полном листинге (т. Е. «Рекурсивном» эквиваленте в aws cli), тогда вы должны использовать пагинаторы, иначе вы перечисляете только первые 1000 объектов.
Пьер Д.
Это отличный ответ. Для тех, кому это нужно, я применил limitк нему в своем производном ответе .
Acumenus
16

Большая реализация S3 заключается в том, что нет папок / каталогов, только ключи. Структура очевидной папки только предваряет имя файла , чтобы стать «Key», так чтобы перечислить содержимое myBucket«S some/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'])

что даст вам что-то вроде:

some/path/to/the/file/yo.jpg
some/path/to/the/file/meAndYou.gif
...
CpILL
источник
Это хороший ответ, но он вернет только 1000 объектов и не более. Я произвел производный ответ, который может получить большее количество объектов.
Acumenus
да, @Acumenus, я думаю, ваш ответ более сложный
CpILL
16

У меня была такая же проблема , но удалось решить его с помощью 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']

Результат вывода для приведенного выше кода будет отображать следующее:

firstlevelFolder/secondLevelFolder/item1
firstlevelFolder/secondLevelFolder/item2

Boto3 list_objects_v2 Документация

Чтобы вырезать только имя каталога, secondLevelFolderя просто использовал метод python split():

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]

Результат вывода для приведенного выше кода будет отображать следующее:

secondLevelFolder
secondLevelFolder

Документация Python split ()

Если вы хотите получить имя каталога И имя элемента содержимого, замените строку печати следующим:

print "{}/{}".format(fileName[1], fileName[2])

И будет выведено следующее:

secondLevelFolder/item2
secondLevelFolder/item2

Надеюсь это поможет

Софи Маспратт
источник
9

У меня работает ... Объекты S3:

s3://bucket/
    form1/
       section11/
          file111
          file112
       section12/
          file121
    form2/
       section21/
          file211
          file112
       section22/
          file221
          file222
          ...
      ...
   ...

С помощью:

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']] 

мы получили:

form1/
form2/
...

С участием:

resp = s3client.list_objects(Bucket=bucket, Prefix='form1/', Delimiter="/")
sections = [x['Prefix'] for x in resp['CommonPrefixes']] 

мы получили:

form1/section11/
form1/section12/
cem
источник
Это единственное решение, которое сработало для меня, потому что мне нужны «папки» в корне ведра, префикс должен быть «», в противном случае он должен заканчиваться на «/»
Оливер
7

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
Пол Зелински
источник
2

Вот возможное решение:

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']]
двусмысленность9
источник
2

Почему бы не использовать 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)
Acumenus
источник
0

Прежде всего, в 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)
Пиреас
источник
1
Боюсь, у меня нет метода get_all_keys для объекта bucket. Я использую boto3 версии 1.2.3.
marin
Только что проверил boto 1.2a: там у bucket есть метод listс prefixи delimiter. Полагаю, это должно сработать.
Pirheas 04
1
Объект Bucket, полученный при публикации в вопросе, не имеет этих методов. Я использую boto3 1.2.6, к какой версии относится ваша ссылка?
мартин
0

Я знаю, что здесь обсуждается тема boto3, но я считаю, что обычно быстрее и интуитивно понятнее просто использовать awscli для чего-то вроде этого - awscli сохраняет больше возможностей, чем boto3, за то, чего стоит.

Например, если у меня есть объекты, сохраненные во «подпапках», связанных с данным сегментом, я могу перечислить их все с помощью чего-то вроде этого:

1) 'mydata' = имя корзины

2) 'f1 / f2 / f3' = «путь», ведущий к «файлам» или объектам

3) 'foo2.csv, barfar.segy, gar.tar' = все объекты "внутри" f3

Итак, мы можем представить себе «абсолютный путь», ведущий к этим объектам: 'mydata / f1 / f2 / f3 / foo2.csv' ...

Используя команды awscli, мы можем легко перечислить все объекты внутри заданной «подпапки» с помощью:

aws s3 ls s3: // mydata / f1 / f2 / f3 / - рекурсивный

Натан Бентон
источник
0

Ниже приведен фрагмент кода, который может обрабатывать разбиение на страницы, если вы пытаетесь получить большое количество объектов корзины 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
Питер Дрисколл
источник
что, если первая страница заполнена «CommonPrefixes» и не предоставляет никакого ключа «Contents». Я думаю, что правильная реализация должна пропустить отсутствующий ключ Contents и перейти к следующей странице.
Ян Влчинский
0

Что касается 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']]
Виталий Котляренко
источник