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

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

который дает словарь, чей ключ "содержание" дает мне все файлы третьего уровня вместо каталоги меток времени второго уровня, на самом деле я получаю список, содержащий вещи как

{у'ETag': '"свойство", у Кей': первого уровня/1456753904534/часть-00014', у'LastModified': значение datetime.datetime(2016, 2, 29, 13, 52, 24, tzinfo=tzutc ()),
у'Owner': {у'DisplayName': 'хозяин', у Саида': 'id'},
u'size': size, u'Storageclass':'storageclass'}

вы можете видеть, что конкретные файлы, в этом случае , а Я бы хотел узнать только название каталога. В принципе, я мог бы удалить имя каталога из всех путей, но это уродливо и дорого получить все на третьем уровне, чтобы получить второй уровень!

Я также попробовал что-то сообщить здесь:

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

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

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

10 ответов


S3-это хранилище объектов, оно не имеет реальной структуры каталогов. "/"Довольно косметический. Одна из причин, по которой люди хотят иметь структуру каталогов, потому что они могут поддерживать/обрезать/добавлять дерево в приложение. Для S3 вы рассматриваете такую структуру как своего рода индекс или тег поиска.

чтобы управлять объектом в S3, вам нужен boto3.клиент или boto3.ресурс, например Перечислить все объект

import boto3 
s3 = boto3.client("s3")
all_objects = s3.list_objects(Bucket = 'my-bucket-name') 

http://boto3.readthedocs.org/en/latest/reference/services/s3.html#S3.Client.list_objects

напоминание о boto3: boto3.ресурс-хороший API высокого уровня. Есть плюсы и минусы использования boto3.клиент против boto3.ресурс. Если вы разрабатываете внутреннюю общую библиотеку, используя boto3.ресурс даст вам слой blackbox над используемыми ресурсами.


ниже часть кода возвращает только "подпапки" в "папке" из ведра 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


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

последняя документация BOTO3 теперь рекомендует использовать list_objects_v2 http://boto3.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.list_objects_v2


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

надеюсь, что это помогает


для меня работает следующее... Объекты 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/

большая реализация с S3 заключается в том, что нет папок/каталогов только ключи. The видимая структура папок просто добавляется к имени файла чтобы стать "ключом", поэтому перечислить содержимое 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/yoMumma.jpg
some/path/to/the/file/meAndYoMuma.gif
...

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

прежде всего, в S3 нет реальной концепции папок. Вы определенно можете иметь файл @ '/folder/subfolder/myfile.txt' и ни папку ни в одну папку.

чтобы "имитировать" папку в S3, необходимо создать пустой файл с '/' в конце его имени (см. Amazon S3 boto-как создать папку?)

для вашей проблемы, вы, вероятно, должны использовать метод 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)

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

  • использовать Delimiter='/'. Это позволяет избежать рекурсивного перечисления вашего ведра. Некоторые ответы здесь ошибочно предлагают сделать полный список и использовать некоторые манипуляции со строками для извлечения имен каталогов. Это может быть ужасно неэффективно. Помните, что S3 практически не ограничивает количество объектов, которые может содержать ведро. Итак, представьте себе, что между bar/ и foo/, у вас есть триллион объектов: вы бы ждать очень долго, чтобы получить ['bar/', 'foo/'].

  • использовать Paginators. По той же причине (S3-инженерное приближение бесконечности), вы должны список через страницы и избежать хранения всего списка в памяти. Вместо этого рассмотрите свой "Листер" как итератор и обработайте поток, который он производит.

  • использовать boto3.client, а не boto3.resource. The resource версия, похоже, не обращаться хорошо . Если у вас есть ресурс, скажем 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 и файлов. dirs содержит только каталоги (каждый из которых имеет один объект). mixed содержит смесь Dir и объектов с отношением 2 объектов для каждого dir (плюс один объект под dir, конечно; 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} ограничивает только количество ключей, а не общие префиксы. Мы имеем дело с этим путем дальнейшего усечения потока нашего итератора.