Отримання імен підпапок у сегменті S3 з boto3


85

Використовуючи boto3, я можу отримати доступ до мого сегмента AWS S3:

s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket-name')

Тепер сегмент містить папку first-level, яка сама містить кілька підпапок, названих, наприклад, із позначкою часу 1456753904534. Мені потрібно знати назву цих вкладених папок для іншої роботи, яку я виконую, і мені цікаво, чи міг я, щоб бото3 отримав їх для мене.

Тому я спробував:

objs = bucket.meta.client.list_objects(Bucket='my-bucket-name')

що дає словник, ключ якого "Зміст" дає мені всі файли третього рівня замість каталогів часових позначок другого рівня, насправді я отримую список, що містить речі як

{u'ETag ':' "etag" ', u'Key': перший рівень / 1456753904534 / part-00014 ', u'LastModified': datetime.datetime (2016, 2, 29, 13, 52, 24, tzinfo = tzutc ()),
u'Власник ': {u'DisplayName': 'власник', u'ID ':' id '},
u'Size': розмір, u'StorageClass ':' клас зберігання '}

Ви можете бачити, що конкретні файли в цьому випадку part-00014отримуються, тоді як я хотів би отримати лише назву каталогу. В принципі, я міг би викреслити ім'я каталогу з усіх шляхів, але негарно і дорого отримувати все на третьому рівні, щоб отримати другий рівень!

Я також спробував щось, про що повідомлялося тут :

for o in bucket.objects.filter(Delimiter='/'):
    print(o.key)

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

Чи є спосіб це вирішити?


Отже, ви говорите, що це не працює? Не могли б ви опублікувати, що відбувається, коли ви запускаєте це?
— Джордон Філліпс

1
@JordonPhillips Я спробував перші рядки посилання, яке ви надсилаєте, яке я вставив сюди, і текстові файли я отримую на самому першому рівні сегмента і без папок.
— mar tin

@mar tin Ви коли-небудь вирішували цю проблему. Я стикаюся з подібною дилемою, коли мені потрібен перший елемент у кожній підпапці сегментів.
— Тед Тейлор із життя

1
@TedTaylorofLife Так, не інакше, як отримати всі об’єкти та розділити, /щоб отримати підпапки
— mar tin

1
@ mar tin Єдиний спосіб, який я зробив, - це взяти вихідні дані, перекинути їх у текстовий формат і розділити комою "/", а потім скопіювати та вставити перший елемент. Який біль у дупі.
— Тед Тейлор із життя

Відповіді:


56

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 проти boto3.resource має плюси і мінуси. Якщо ви розробляєте внутрішню спільну бібліотеку, використання boto3.resource надасть вам шар чорного ящика над використовуваними ресурсами.


1
Це дає той самий результат, який я отримую при своїй спробі у питанні. Думаю, мені доведеться вирішити важкий спосіб, захопивши всі ключі від повернутих об’єктів і розділивши рядок, щоб отримати назву папки.
— mar tin

1
@martina: ледачий пітон розділив і забрав останні дані всередині списку, наприклад ім'я файлу = ім'я ключа.split ("/") [- 1]
— mootmoot

1
@martin directory_name = os.path.dirname(directory/path/and/filename.txt)andfile_name = os.path.basename(directory/path/and/filename.txt)
— jkdev

106

Нижче фрагмент коду повертає ТІЛЬКИ "підпапки" у "папці" із сегмента 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 різних префіксів?
— Кострахб

38

Мені знадобилося багато часу, щоб це зрозуміти, але, нарешті, ось простий спосіб перерахувати вміст підпапки у сегменті 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))

3
а якщо ваша папка містить величезну кількість об’єктів?
— Pierre D,

3
я хочу сказати, що це жахливо неефективне рішення. S3 побудований для роботи з довільними роздільниками в ключах. Наприклад, '/'. Це дозволить вам пропустити "папки", повні об'єктів, без необхідності переносити їх на сторінки. І тоді, навіть якщо ви наполягаєте на повному переліку (тобто "рекурсивному" еквіваленті в aws cli), тоді ви повинні використовувати пагінатори, інакше ви перелічите лише перші 1000 об’єктів.
— Pierre D

Це чудова відповідь. Для тих, хто цього потребує, я застосував limitдо нього у своїй похідній відповіді .
— Прозріння

38

Коротка відповідь :

  • Використовуйте 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справді ексклюзивно. Дано 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}обмежує лише кількість клавіш, а не загальні префікси. Ми маємо справу з цим шляхом подальшого скорочення потоку нашого ітератора.


@ Мехді: насправді це не дуже складно для системи, яка пропонує такі неймовірні масштаби та надійність. Якщо ви коли-небудь матимете справу з кількома сотнями туберкульозів, ви отримаєте вдячність за те, що вони пропонують. Пам'ятайте, накопичувачі завжди мають MTBF> 0 ... Подумайте про наслідки для масштабного зберігання даних. Застереження: Я активний і щасливий користувач AWS, жодного іншого підключення, крім того, що я працював над даними в масштабі петабайт з 2007 року, і раніше це було набагато складніше.
— Pierre D

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

Це хороша відповідь, але вона отримає до 1000 об’єктів і не більше. Я створив похідну відповідь, яка може отримати більшу кількість об'єктів.
— Прозріння

так, @Acumenus, я думаю, ваша відповідь є більш складною
— CpILL

16

У мене була та ж проблема, але мені вдалося її вирішити за допомогою параметрів boto3.clientand list_objects_v2with 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 ()

Якщо ви хочете отримати назву каталогу І назву елемента вмісту, замініть рядок друку таким:

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

І буде виведено наступне:

secondLevelFolder/item2
secondLevelFolder/item2

Сподіваюся, це допомагає


8

Для мене працює наступне ... Об'єкти 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/

6

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

1

Використовуючи 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)

0

Перш за все, у S3 немає реальної концепції папок. У вас точно може бути файл @ '/folder/subfolder/myfile.txt'і жодної папки та підпапки.

Щоб "імітувати" папку в S3, потрібно створити порожній файл із знаком '/' у кінці її імені (див. Бото Amazon S3 - як створити папку? )

Для вашої проблеми, ймовірно, вам слід скористатися методом get_all_keysіз 2 параметрами: 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 на об'єкті відра. Я використовую boto3 версії 1.2.3.
— mar tin

Щойно перевірив boto 1.2a: там сегмент має метод listз prefixта delimiter. Гадаю, це має спрацювати.
— Піреас

1
Об’єкт Bucket, отриманий під час розміщення у запитанні, не має цих методів. Я працюю на boto3 1.2.6, до якої версії посилається ваше посилання?
— mar tin


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 / --recursive


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

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']]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.