从 boto3 检索 S3 存储桶中的子文件夹名称

Posted

技术标签:

【中文标题】从 boto3 检索 S3 存储桶中的子文件夹名称【英文标题】:Retrieving subfolders names in S3 bucket from boto3 【发布时间】:2016-06-18 14:24:06 【问题描述】:

使用 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': 一级/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': 'storageclass'

您可以看到特定文件,在本例中为 part-00014 被检索,而我想单独获取目录的名称。 原则上我可以从所有路径中删除目录名称,但是在第三级检索所有内容以获得第二级是丑陋且昂贵的!

我也试过here:

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

但我没有获得所需级别的文件夹。

有没有办法解决这个问题?

【问题讨论】:

所以你是说this 不起作用?你能发布一下运行时会发生什么吗? @JordonPhillips 我已经尝试了您发送的链接的第一行,我将其粘贴在这里,我得到了存储桶第一级的文本文件,没有文件夹。 @mar tin 你有没有解决过这个问题。我面临着类似的困境,我需要每个存储桶子文件夹中的第一个元素。 @TedTaylorofLife 是的,除了获取所有对象并按/ 拆分以获取子文件夹之外别无他法 @mar tin 我所做的唯一方法是获取输出,将其转换为文本格式并用逗号分隔“/”,然后复制并粘贴第一个元素。真是让人头疼。 【参考方案1】:

首先,S3 中没有真正的文件夹概念。 你绝对可以有一个文件@'/folder/subfolder/myfile.txt',没有文件夹也没有子文件夹。

要在 S3 中“模拟”文件夹,您必须创建一个名称末尾带有“/”的空文件(请参阅Amazon S3 boto - how to create a folder?)

对于您的问题,您可能应该使用带有 2 个参数的方法 get_all_keysprefixdelimiter

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)

【讨论】:

恐怕我没有桶对象上的get_all_keys方法。我正在使用 boto3 版本 1.2.3。 刚刚检查了 boto 1.2a:在那里,bucket 有一个方法 listprefixdelimiter。我想它应该可以工作。 我在问题中发布时检索到的 Bucket 对象没有这些方法。我在boto3 1.2.6,你的链接指的是哪个版本? 见这里boto3.readthedocs.org/en/latest/reference/services/…【参考方案2】:

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 )

Documentation

另一个选项是使用 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 将为您提供一个覆盖所用资源的黑盒层。

【讨论】:

这给了我在问题中尝试得到的相同结果。我想我必须通过从返回的对象中获取所有键并拆分字符串以获取文件夹名称来解决困难的方法。 @martina :一个懒惰的 python 拆分并获取列表中的最后一个数据,例如文件名 = keyname.split("/")[-1] @martin directory_name = os.path.dirname(directory/path/and/filename.txt)file_name = os.path.basename(directory/path/and/filename.txt) 迂腐,但不清楚“真正的目录结构”是什么意思。 S3 的目录抽象比典型的文件系统更薄,但它们都只是抽象,并且对于 op 的目的来说,这里的功能是相同的 真的想使用os.path 实用程序来操作存储桶键吗?我在寻找一种编辑 S3 存储桶路径的犹太方法时发现了这一点,正是为了避免本土路径拼接和/或使用 os.path,这恰好在 Linux 上工作,但在 Windows 上似乎肯定会失败,并且否则在概念上“只是错误的”。【参考方案3】:

以下代码仅返回 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

【讨论】:

如果我想列出特定子文件夹的内容怎么办? @azhar22k,我假设您可以为每个“子文件夹”递归地运行该函数。 如果有超过 1000 个不同的前缀怎么办?【参考方案4】:

我花了很多时间才弄清楚,但最后这里有一个使用 boto3 列出 S3 存储桶中子文件夹内容的简单方法。希望对你有帮助

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

【讨论】:

如果您的文件夹包含大量对象怎么办? 我的观点是,这是一个非常低效的解决方案。 S3 旨在处理键中的任意分隔符。例如,'/'。这让您可以跳过充满对象的“文件夹”,而不必对它们进行分页。然后,即使您坚持完整列出(即 aws cli 中的“递归”等效项),也必须使用分页器,否则您将仅列出前 1000 个对象。 这是一个很好的答案。对于那些需要它的人,我在my derived answer 中应用了limit 这是一个很好的答案!有时我们对性能不感兴趣,而是对要维护的简单代码感兴趣。这很简单,而且效果很好!【参考方案5】:

当您运行 aws s3 ls s3://my-bucket/ 时,AWS cli 会执行此操作(大概没有获取和遍历存储桶中的所有键),所以我认为必须有一种使用 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

【讨论】:

【参考方案6】:

以下对我有用... 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/

【讨论】:

这是唯一对我有用的解决方案,因为我需要存储桶根目录中的“文件夹”,前缀必须是“'”,否则它必须以“/”结尾【参考方案7】:

我遇到了同样的问题,但设法使用 boto3.clientlist_objects_v2 以及 BucketStartAfter 参数解决了它。

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 Documentation

为了只去除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() Documentation

如果您想获取目录名称和内容项名称,请将打印行替换为以下内容:

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

会输出以下内容:

secondLevelFolder/item2
secondLevelFolder/item2

希望对你有帮助

【讨论】:

【参考方案8】:

S3 最大的实现是没有文件夹/目录,只有键。 明显的文件夹结构只是添加到文件名成为'Key',所以要列出myBucketsome/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 个对象而不会更多。我制作了一个derived answer,它可以检索更多的对象。 是的,@Acumenus 我想你的答案更复杂【参考方案9】:

简答

使用Delimiter='/'。这避免了对您的存储桶进行递归列表。这里的一些答案错误地建议进行完整列表并使用一些字符串操作来检索目录名称。这可能是非常低效的。请记住,S3 实际上对存储桶可以包含的对象数量没有限制。所以,想象一下,在bar/foo/ 之间,你有一万亿个对象:你会等待很长时间才能得到['bar/', 'foo/']

使用Paginators。出于同样的原因(S3 是工程师对无穷大的近似值),您必须逐页列出并避免将所有列表存储在内存中。相反,将您的“lister”视为一个迭代器,并处理它产生的流。

使用boto3.client,而不是boto3.resourceresource 版本似乎不能很好地处理 Delimiter 选项。如果你有资源,比如bucket = boto3.resource('s3').Bucket(name),你可以通过bucket.meta.client获取对应的客户端。

长答案

以下是我用于简单存储桶的迭代器(无版本处理)。

import os
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('bucket-name')

        # iterate through all S3 objects under some dir
        >>> for p in s3list(bucket, 'some/dir'):
        ...     print(p)

        # iterate through up to 20 S3 objects under some dir, starting with foo_0010
        >>> for p in s3list(bucket, 'some/dir', limit=20, start='foo_0010'):
        ...     print(p)

        # non-recursive listing under some dir:
        >>> for p in s3list(bucket, 'some/dir', recursive=False):
        ...     print(p)

        # non-recursive listing under some dir, listing only dirs:
        >>> for p in s3list(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

测试

以下内容有助于测试paginatorlist_objects 的行为。它创建了许多目录和文件。由于页面最多有 1000 个条目,因此我们将其倍数用于目录和文件。 dirs 仅包含目录(每个目录都有一个对象)。 mixed 包含 dirs 和 objects 的混合,每个 dir 有 2 个对象的比例(当然,在 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' 将使列表在该键之后开始(根据AmazonS3 API),即.../mixed/0500_foo_b。这就是__prev_str() 的原因。

使用Delimiter,在列出mixed/ 时,来自paginator 的每个响应都包含666 个键和334 个公共前缀。它非常擅长不产生大量响应。

相比之下,当列出 dirs/ 时,来自 paginator 的每个响应都包含 1000 个公共前缀(并且没有键)。

通过PaginationConfig='MaxItems': limit 形式的限制仅限制键的数量,而不限制公共前缀。我们通过进一步截断迭代器的流来处理这个问题。

【讨论】:

@Mehdi :对于一个提供如此令人难以置信的规模和可靠性的系统来说,这真的不是很复杂。如果您曾经处理过超过几百个 TB,您将会对他们所提供的内容感到赞赏。请记住,驱动器的 MTBF 总是 > 0...想想对大规模数据存储的影响。免责声明:我是一名活跃且快乐的 AWS 用户,没有其他联系,除了自 2007 年以来我一直在处理 PB 级数据,而且过去要困难得多。 为您的代码添加修复。如果有人想以非递归方式列出存储桶中的所有目录,他们将发送以下内容:s3list(bucket, '', recursive=False, list_objs=False) 所以我将and len(path) > 0: 添加到if not path.endswith('/') 喜欢“kwargs”的用法。避免使用和不使用 ContinuationToken 将 list_objects_v2 加倍的好技巧 只是为了澄清一下,我是否正确理解列出的目录的唯一性不能保证?我猜在同一个页面中,commonPrefixes 可能只包含唯一的前缀,但是在 2 个不同的页面之间,一些前缀可以重复。 只有recursive=False, list_dirs=True 时才会列出目录。列表中没有重复项。【参考方案10】:

为什么不使用s3path 包,它与使用pathlib 一样方便?如果你必须使用boto3:

使用boto3.resource

这建立在answer by 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://***.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 并以 answer by 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://***.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)

【讨论】:

那个 s3path 库是救命稻草!非常感谢!【参考方案11】:

我知道 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

【讨论】:

【参考方案12】:

如果您尝试获取大量 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 键并继续下一页。【参考方案13】:

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

【讨论】:

【参考方案14】:

这是一个可能的解决方案:

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

【讨论】:

【参考方案15】:

使用递归方法列出 S3 存储桶中的所有不同路径。

def common_prefix(bucket_name,paths,prefix=''):
    client = boto3.client('s3')
    paginator = client.get_paginator('list_objects')
    result = paginator.paginate(Bucket=bucket_name, Prefix=prefix, Delimiter='/')
    for prefix in result.search('CommonPrefixes'):
        if prefix == None:
            break
        paths.append(prefix.get('Prefix'))
        common_prefix(bucket_name,paths,prefix.get('Prefix'))

【讨论】:

【参考方案16】:

这对我来说非常适合仅检索存储桶下的第一级文件夹:

client = boto3.client('s3')
bucket = 'my-bucket-name'
folders = set()

for prefix in client.list_objects(Bucket=bucket, Delimiter='/')['CommonPrefixes']:
    folders.add(prefix['Prefix'][:-1])
    
print(folders)

您可以对列表而不是集合执行相同的操作,因为文件夹名称是唯一的

【讨论】:

【参考方案17】:

要列出的“目录”并不是真正的对象,而是对象键的子字符串,因此它们不会出现在 objects.filter 方法中。您可以在此处使用客户端的list_objects 并指定前缀。

import boto3

s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket-name')
res = bucket.meta.client.list_objects(Bucket=bucket.name, Delimiter='/', Prefix = 'sub-folder/')
for o in res.get('CommonPrefixes'):
    print(o.get('Prefix'))

【讨论】:

【参考方案18】:

这个问题的一些很好的答案。

我一直在使用 boto3 资源 objects.filter 方法来获取所有文件。 objects.filter 方法以迭代器的形式返回,速度极快。 虽然将其转换为列表很耗时。

list_objects_v2 返回实际内容,而不是迭代器。 但是,您需要循环获取所有内容,因为它的大小限制为 1000。

为了只获取文件夹,我像这样应用列表理解

[x.split('/')[index] for x in files]

以下是各种方法所花费的时间。 运行这些测试时文件数为 125077。

%%timeit

s3 = boto3.resource('s3')
response = s3.Bucket('bucket').objects.filter(Prefix='foo/bar/')
3.95 ms ± 17.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%%timeit

s3 = boto3.resource('s3')
response = s3.Bucket('foo').objects.filter(Prefix='foo/bar/')
files = list(response)
26.6 s ± 1.08 s per loop (mean ± std. dev. of 7 runs, 1 loop each)
%%timeit

s3 = boto3.client('s3')
response = s3.list_objects_v2(Bucket='bucket', Prefix='foo/bar/')
files = response['Contents']
while 'NextContinuationToken' in response:
    response = s3.list_objects_v2(Bucket='bucket', Prefix='foo/bar/', ContinuationToken=response['NextContinuationToken'])
    files.extend(response['Contents'])
22.8 s ± 1.11 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

以上是关于从 boto3 检索 S3 存储桶中的子文件夹名称的主要内容,如果未能解决你的问题,请参考以下文章

使用Python boto3上传Windows EC2实例中的文件至S3存储桶中

S3 选择 CSV 中的检索标头

Boto3 从 S3 存储桶下载所有文件

图像错误,未加载 S3 图像检索

使用 boto3 检查 s3 的存储桶中是不是存在密钥

从 S3 存储桶中读取大量 CSV 文件