Отримання імен підпапок у сегменті 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.