Extraire les noms des sous-dossiers dans S3 bucket de boto3

en utilisant boto3, je peux accéder à mon seau AWS S3:

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

maintenant, le seau contient le dossier first-level , qui lui-même contient plusieurs sous-dossiers nommés avec un horodatage, par exemple 1456753904534 . J'ai besoin de connaître le nom de ces sous-dossiers pour un autre travail que je fais et je me demande si Je ne pourrais pas avoir boto3 les récupérer pour moi.

Donc j'ai essayé:

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

qui donne un dictionnaire, dont la clé 'Content' me donne tous les fichiers de troisième niveau au lieu des répertoires de second niveau timestamp, en fait je reçois une liste contenant des choses comme

{u'ETag': '"etag"', u Key': premier niveau/1456753904534/partie-00014', u'LastModified': datetime.datetime(2016, 2, 29, 13, 52, 24, tzinfo=tzutc ()),

u'Owner': {u'DisplayName': 'propriétaire', u ÎD': 'id'},

u'Size': taille, u'StorageClass': 'storageclass'}

vous pouvez voir que les fichiers spécifiques, dans ce cas part-00014 sont récupérés, alors que je voudrais obtenir le nom du répertoire seul. En principe je pourrais enlever le nom du répertoire de tous les chemins mais il est laid et coûteux de tout récupérer au troisième niveau pour obtenir le deuxième niveau!

j'ai aussi essayé quelque chose rapporté ici :

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

mais je ne comprends pas dossiers au niveau désiré.

y a-t-il un moyen de résoudre ça?

20
demandé sur mar tin 2016-03-04 21:04:19

10 réponses

S3 est un stockage objet, il n'a pas de structure de répertoire réelle. Le "/" est plutôt esthétique. Une raison pour laquelle les gens veulent avoir une structure de répertoire, parce qu'ils peuvent maintenir/élaguer/ajouter un arbre à l'application. Pour S3, vous traitez une telle structure comme une sorte d'index ou de balise de recherche.

pour manipuler l'objet en S3, vous avez besoin de boto3.client ou boto3.des ressources, par exemple La liste de tous les objets

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

Un rappel à propos de la boto3 : boto3.la ressource est une belle de haut niveau de l'API. Il y a des avantages et des inconvénients à utiliser boto3.client vs boto3.ressources. Si vous développez une bibliothèque partagée interne, utilisez boto3.la ressource vous donnera une couche blackbox sur les ressources utilisées.

11
répondu mootmoot 2017-03-22 08:15:34

au-dessous du morceau de code retourne seulement les 'sous-dossiers' dans un 'dossier' de S3 bucket.

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

pour plus de détails, vous pouvez vous référer à https://github.com/boto/boto3/issues/134

34
répondu Dipankar 2016-08-18 04:00:00

il m'a fallu beaucoup de temps pour comprendre, mais enfin voici une façon simple de lister le contenu d'un sous-dossier dans S3 bucket en utilisant boto3. Espérons que cela aide

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))
17
répondu itz-azhar 2017-06-30 07:38:34

la dernière documentation de BOTO3 recommande maintenant d'utiliser list_objects_v2 http://boto3.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.list_objects_v2

7
répondu Sameer Girolkar 2017-06-09 19:02:01

j'ai eu le même problème mais j'ai réussi à le résoudre en utilisant boto3.client et list_objects_v2 avec Bucket et StartAfter paramètres.

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

le résultat de sortie pour le code ci-dessus affichera ce qui suit:

firstlevelFolder/secondLevelFolder/item1
firstlevelFolder/secondLevelFolder/item2

Boto3 list_objects_v2 Documentation

pour supprimer seulement le nom de répertoire pour secondLevelFolder je viens d'utiliser la méthode 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]

le résultat de sortie pour le code ci-dessus affichera ce qui suit:

secondLevelFolder
secondLevelFolder

Python split() Documentation

si vous désirez obtenir le nom du répertoire et le nom de l'article contenu, remplacez la ligne d'impression par la suivante:

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

et les suivants seront produits:

secondLevelFolder/item2
secondLevelFolder/item2

J'espère que aide

7
répondu Sophie Muspratt 2018-03-22 17:13:45

les travaux suivants pour moi... Les objets S3:

s3://bucket/
    form1/
       section11/
          file111
          file112
       section12/
          file121
    form2/
       section21/
          file211
          file112
       section22/
          file221
          file222
          ...
      ...
   ...

utilisant:

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

, nous obtenons:

form1/
form2/
...

avec:

resp = s3client.list_objects(Bucket=bucket, Prefix='form1/', Delimiter="/")
sections = [x['Prefix'] for x in resp['CommonPrefixes']] 

, nous obtenons:

form1/section11/
form1/section12/
3
répondu cem 2018-01-05 21:51:32

la grande réalisation avec S3 est qu'il n'y a pas de dossiers/répertoires juste des clés. La structure apparente du dossier est juste prédéfinie au nom de fichier pour devenir la "clé", donc pour lister le contenu de myBucket 's some/path/to/the/file/ vous pouvez essayer:

s3 = boto3.client('s3')
for obj in s3.list_objects_v2(Bucket="myBucket", Prefix="some/path/to/the/file/")['Contents']:
    print(obj['Key'])

qui vous donnerait quelque chose comme:

some/path/to/the/file/yoMumma.jpg
some/path/to/the/file/meAndYoMuma.gif
...
3
répondu CpILL 2018-08-02 08:13:42

L'AWS cli fait ceci (probablement sans aller chercher et itérer toutes les clés dans le seau) quand vous lancez aws s3 ls s3://my-bucket/ , donc j'ai pensé qu'il devait y avoir un moyen d'utiliser boto3.

https://github.com/aws/aws-cli/blob/0fedc4c1b6a7aee13e2ed10c3ada778c702c22c3/awscli/customizations/s3/subcommands.py#L499

on dirait qu'ils utilisent en effet le préfixe et le délimiteur - j'ai pu écrire une fonction qui m'obtiendrait tout répertoires au niveau de la racine d'un seau en modifiant un peu ce code:

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
répondu Paul Zielinski 2017-11-17 03:07:50

tout d'abord, il n'y a pas de véritable concept de dossier dans S3. Vous pouvez certainement avoir un fichier @ '/folder/subfolder/myfile.txt' et aucun dossier ni sous-dossier.

Pour "simuler" un dossier dans S3, vous devez créer un fichier vide avec un '/' à la fin de son nom (voir Amazon S3 boto - comment créer un dossier? )

Pour votre problème, vous devriez probablement utiliser la méthode get_all_keys avec les 2 paramètres : prefix et 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
répondu Pirheas 2017-05-23 11:46:22

réponse Courte :

  • utilisation Delimiter='/' . Cela évite de faire une liste récursive de votre seau. Certaines réponses ici suggèrent à tort de faire une liste complète et d'utiliser une certaine manipulation de chaîne de caractères pour récupérer les noms de répertoire. Cela pourrait être terriblement inefficace. Rappelez-vous que S3 n'a pratiquement aucune limite sur le nombre d'objets qu'un seau peut contenir. Donc, imaginez que, entre bar/ et foo/ , vous avez un trillion d'objets: vous attendriez très longtemps pour obtenir ['bar/', 'foo/'] .

  • utiliser Paginators . Pour la même raison (S3 est une approximation de l'infini d'un ingénieur), vous doit liste à travers les pages et éviter de stocker toute la liste en mémoire. Au lieu de cela, considérez votre "lister" comme un itérateur, et gérez le flux qu'il produit.

  • utiliser boto3.client , Non. boto3.resource . La version resource ne semble pas bien gérer l'option Delimiter . Si vous avez une ressource, dites un bucket = boto3.resource('s3').Bucket(name) , vous pouvez obtenir le client correspondant avec: bucket.meta.client .

longue réponse :

ce qui suit est un itérateur que j'utilise pour les seaux simples (pas de gestion de version).

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

Test :

ce qui suit est utile pour tester le comportement du paginator et list_objects . Il crée un certain nombre de répertoires et de fichiers. Depuis les pages sont jusqu'à 1000 entrées, nous utilisons un multiple de répertoires et de fichiers. dirs ne contient que des répertoires (chacun ayant un objet). mixed contient un mélange de dirs et d'objets, avec un rapport de 2 objets pour chaque dir (plus un objet sous dir, bien sûr; S3 stocke seulement les objets).

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

la structure résultante est:

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

avec un peu de doctorage du code donné ci-dessus pour s3list pour examiner les réponses du paginator , vous pouvez observer quelques faits amusants:

  • le Marker est vraiment exclusif. Étant donné Marker=topdir + 'mixed/0500_foo_a' fera le début de la liste après cette clé (selon L'API AmazonS3 ), c.-à-d. avec .../mixed/0500_foo_b . C'est la raison de __prev_str() .

  • utilisant Delimiter , chaque réponse du paginator contient 666 clés et 334 préfixes communs. Il est assez bon pour ne pas construire des réponses énormes.

  • par contraste, lors de l'inscription dirs/ , chaque réponse du paginator contient 1000 préfixes communs (et aucune clé).

  • en passant une limite sous la forme de PaginationConfig={'MaxItems': limit} limite seulement le nombre de touches, pas les préfixes communs. Nous traitons cela en tronquant davantage le flux de notre itérateur.

1
répondu Pierre D 2018-07-17 02:27:19