繁体   English   中英

Boto3 S3:获取文件而不获取文件夹

[英]Boto3 S3: Get files without getting folders

使用 boto3,如何在不检索文件夹的情况下检索 S3 存储桶中的所有文件?

考虑以下文件结构:

file_1.txt
folder_1/
    file_2.txt
    file_3.txt
    folder_2/
        folder_3/
            file_4.txt

在这个例子中,我只对 4 个文件感兴趣。

编辑:

手动解决方案是:

def count_files_in_folder(prefix):
    total = 0
    keys = s3_client.list_objects(Bucket=bucket_name, Prefix=prefix)
    for key in keys['Contents']:
        if key['Key'][-1:] != '/':
            total += 1
    return total

在这种情况下,总数为 4。

如果我刚刚做了

count = len(s3_client.list_objects(Bucket=bucket_name, Prefix=prefix))

结果将是 7 个对象(4 个文件和 3 个文件夹):

file.txt
folder_1/
folder_1/file_2.txt
folder_1/file_3.txt
folder_1/folder_2/
folder_1/folder_2/folder_3/
folder_1/folder_2/folder_3/file_4.txt

我只是想:

file.txt
folder_1/file_2.txt
folder_1/file_3.txt  
folder_1/folder_2/folder_3/file_4.txt

S3 是一个对象商店。 它不会在目录树下存储文件/对象。 新人总是混淆他们给出的“文件夹”选项,这实际上是对象的任意前缀。

object PREFIX是一种检索由预定义的修复文件名(键)前缀结构组织的对象的方法,例如 .

您可以想象使用一个文件系统,它不允许您创建目录,但允许您使用斜杠“/”或反斜杠“\\”作为分隔符创建文件名,您可以通过以下方式表示文件的“级别”一个共同的前缀。

因此,在 S3 中,您可以使用以下内容来“模拟不是目录的目录”。

folder1-folder2-folder3-myobject
folder1/folder2/folder3/myobject
folder1\folder2\folder3\myobject

如您所见,无论您使用哪种任意文件夹分隔符(分隔符),对象名称都可以存储在 S3 中。

但是,为了帮助用户将批量文件传输到 S3,aws cli、s3_transfer api 等工具尝试简化步骤并按照您输入的本地文件夹结构创建对象名称。

因此,如果您确定所有 S3 对象都使用/\\作为分隔符,则可以使用 S3transfer 或 AWSCcli 等工具通过使用密钥名称进行简单下载。

这是使用资源迭代器的快速而肮脏的代码。 使用 s3.resource.object.filter 将返回与 list_objects()/list_objects_v2() 没有相同 1000 个键限制的迭代器。

import os 
import boto3
s3 = boto3.resource('s3')
mybucket = s3.Bucket("mybucket")
# if blank prefix is given, return everything)
bucket_prefix="/some/prefix/here"
objs = mybucket.objects.filter(
    Prefix = bucket_prefix)

for obj in objs:
    path, filename = os.path.split(obj.key)
    # boto3 s3 download_file will throw exception if folder not exists
    try:
        os.makedirs(path) 
    except FileExistsError:
        pass
    mybucket.download_file(obj.key, obj.key)

S3 中没有文件夹。 您拥有的是四个名为的文件:

file_1.txt
folder_1/file_2.txt
folder_1/file_3.txt
folder_1/folder_2/folder_3/file_4.txt

这些是 S3 中对象的实际名称。 如果你想要的是最终结果:

file_1.txt
file_2.txt
file_3.txt
file_4.txt

所有这些都位于本地文件系统上的同一目录中,您需要操作对象的名称以仅去除文件名。 像这样的事情会起作用:

import os.path

full_name = 'folder_1/folder_2/folder_3/file_4.txt'
file_name = os.path.basename(full_name)

然后变量file_name将包含'file_4.txt'

如果您确定没有文件以正斜杠结尾,则过滤出文件夹的一种方法是检查对象的结束字符:

for object_summary in objects.all():
    if object_summary.key[-1] == "/":
        continue

正如其他答案中所述,s3 实际上没有目录树。 但是有一个方便的解决方法,即通过使用分页器来利用 s3“文件夹”的大小为零这一事实。 如果存储桶中的所有文件的大小 > 0(当然您需要调整您的区域),此代码片段将打印出所需的输出:

bucket_name = "bucketname"
s3 = boto3.client('s3', region_name='eu-central-1')
paginator = s3.get_paginator('list_objects')
[print(page['Key']) for page in paginator.paginate(Bucket=bucket_name).search("Contents[?Size > `0`][]")]

过滤是使用JMESPath完成的。

注意:当然这也会排除大小为 0 的文件,但通常您不需要存储空文件。

使用v2您还可以获得文件的大小,因此您可以过滤键。

s3_client
  .list_objects_v2(bucket: bucket_name, prefix: prefix)
  .select { |e| e[:size] > 0 }
  .map { |e| e[:key] }

跟进@airborne 的回答,您可以使用 JMESPath 过滤所有以 \ 结尾的键

这仍然会返回空文件,但会过滤掉所有非文件键(除非你有一个以 \ 结尾的文件名,这将迫使你尝试获取内容以确保它是一个文件)。

import boto3

s3 = boto3.client('s3')

def count_files_in_folder(bucket_name: str prefix: str) -> int:
    paginator = s3.get_paginator('list_objects_v2')
    result = paginator.paginate(Bucket=bucket_name, Prefix=prefix).search("Contents[? !ends_with(key, '/')]")
    return len(result)

这将返回所有键而没有任何分页。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM