Recupero dei nomi delle sottocartelle nel bucket S3 da boto3


94

Utilizzando boto3, posso accedere al mio bucket AWS S3:

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

Ora, il bucket contiene la cartella first-level, che a sua volta contiene diverse sottocartelle denominate con un timestamp, ad esempio 1456753904534. Ho bisogno di conoscere il nome di queste sottocartelle per un altro lavoro che sto facendo e mi chiedo se potrei fare in modo che boto3 le recuperi per me.

Quindi ho provato:

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

che fornisce un dizionario, la cui chiave 'Contents' mi fornisce tutti i file di terzo livello invece delle directory di timestamp di secondo livello, infatti ottengo un elenco contenente cose come

{u'ETag ':' "etag" ', u'Key': first-level / 1456753904534 / part-00014 ', u'LastModified': datetime.datetime (2016, 2, 29, 13, 52, 24, tzinfo = tzutc ()),
u'Owner ': {u'DisplayName': 'owner', u'ID ':' id '},
u'Size': size, u'StorageClass ':' storageclass '}

puoi vedere che i file specifici, in questo caso, part-00014vengono recuperati, mentre io vorrei ottenere solo il nome della directory. In linea di principio potrei togliere il nome della directory da tutti i percorsi ma è brutto e costoso recuperare tutto al terzo livello per ottenere il secondo livello!

Ho anche provato qualcosa riportato qui :

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

ma non ottengo le cartelle al livello desiderato.

C'è un modo per risolvere questo problema?


Quindi stai dicendo che questo non funziona? Potresti postare cosa succede quando lo esegui?
Jordon Phillips

1
@JordonPhillips Ho provato le prime righe di quel link che invii, che ho incollato qui, e ottengo i file di testo al primo livello del bucket e nessuna cartella.
mar tin

@mar tin Hai mai risolto questo problema. Sto affrontando un dilemma simile in cui ho bisogno del primo elemento in ogni sottocartella dei bucket.
Ted Taylor of Life

1
@TedTaylorofLife Sì, nessun altro modo che prendere tutti gli oggetti e dividersi /per ottenere le sottocartelle
mar tin

1
@ mar tin L'unico modo che ho fatto è stato prendere l'output, gettarlo in un formato di testo e delimitare una virgola con "/" e quindi copiare e incollare il primo elemento. Che rompicoglioni.
Ted Taylor of Life

Risposte:


65

S3 è un archivio di oggetti, non ha una vera struttura di directory. La "/" è piuttosto cosmetica. Uno dei motivi per cui le persone vogliono avere una struttura di directory, perché possono mantenere / eliminare / aggiungere un albero all'applicazione. Per S3, tratti tale struttura come una sorta di indice o tag di ricerca.

Per manipolare un oggetto in S3, hai bisogno di boto3.client o boto3.resource, ad esempio per elencare tutti gli oggetti

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

Infatti, se il nome dell'oggetto s3 viene memorizzato utilizzando il separatore "/". La versione più recente di list_objects (list_objects_v2) consente di limitare la risposta alle chiavi che iniziano con il prefisso specificato.

Per limitare gli elementi agli elementi in determinate sottocartelle:

    import boto3 
    s3 = boto3.client("s3")
    response = s3.list_objects_v2(
            Bucket=BUCKET,
            Prefix ='DIR1/DIR2',
            MaxKeys=100 )

Documentazione

Un'altra opzione è usare la funzione python os.path per estrarre il prefisso della cartella. Il problema è che ciò richiederà l'elenco di oggetti da directory indesiderate.

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]

Un promemoria su boto3: boto3.resource è una bella API di alto livello. Ci sono pro e contro usando boto3.client vs boto3.resource. Se sviluppi una libreria condivisa interna, l'utilizzo di boto3.resource ti darà un livello blackbox sulle risorse utilizzate.


2
Questo mi dà lo stesso risultato che ottengo con il mio tentativo nella domanda. Immagino che dovrò risolvere nel modo più difficile afferrando tutte le chiavi dagli oggetti restituiti e dividendo la stringa per ottenere il nome della cartella.
mar tin

1
@martina: un pigro python diviso e raccogli gli ultimi dati all'interno dell'elenco, ad es. filename = keyname.split ("/") [- 1]
mootmoot

1
@martin directory_name = os.path.dirname(directory/path/and/filename.txt)efile_name = os.path.basename(directory/path/and/filename.txt)
jkdev

109

La parte di codice sottostante restituisce SOLO le "sottocartelle" in una "cartella" dal bucket 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')

Per maggiori dettagli, puoi fare riferimento a https://github.com/boto/boto3/issues/134


12
E se volessi elencare i contenuti di una particolare sottocartella?
azhar22k

1
@ azhar22k, presumo che potresti semplicemente eseguire la funzione in modo ricorsivo per ogni "sottocartella".
Serban Cezar

E se ci sono più di 1000 prefissi diversi?
Kostrahb

42

Risposta breve :

  • Usa Delimiter='/'. Questo evita di fare un elenco ricorsivo del tuo bucket. Alcune risposte qui suggeriscono erroneamente di fare un elenco completo e di utilizzare alcune manipolazioni di stringhe per recuperare i nomi delle directory. Questo potrebbe essere orribilmente inefficiente. Ricorda che S3 non ha praticamente limiti al numero di oggetti che un bucket può contenere. Quindi, immagina che, tra bar/e foo/, hai un trilione di oggetti: aspetteresti molto tempo per ottenere ['bar/', 'foo/'].

  • Usa Paginators. Per lo stesso motivo (S3 è l'approssimazione dell'infinito di un ingegnere), è necessario elencare le pagine ed evitare di memorizzare tutto l'elenco in memoria. Considera invece il tuo "lister" come un iteratore e gestisci il flusso che produce.

  • Usa boto3.client, no boto3.resource. La resourceversione non sembra gestire bene l' Delimiteropzione. Se si dispone di una risorsa, diciamo una bucket = boto3.resource('s3').Bucket(name), è possibile ottenere il cliente corrispondente: bucket.meta.client.

Risposta lunga :

Quello che segue è un iteratore che utilizzo per bucket semplici (nessuna gestione della versione).

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

Prova :

Quanto segue è utile per testare il comportamento di paginatore list_objects. Crea una serie di directory e file. Poiché le pagine contengono fino a 1000 voci, ne utilizziamo un multiplo per directory e file. dirscontiene solo directory (ciascuna con un oggetto). mixedcontiene un mix di directory e oggetti, con un rapporto di 2 oggetti per ogni directory (più un oggetto sotto dir, ovviamente; S3 memorizza solo gli oggetti).

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 struttura risultante è:

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

Con un po 'di dottorato del codice sopra dato per s3listispezionare le risposte dal paginator, puoi osservare alcuni fatti divertenti:

  • La Markerè davvero esclusiva. Dato Marker=topdir + 'mixed/0500_foo_a'farà iniziare l'elenco dopo quella chiave (come per l' API AmazonS3 ), cioè con .../mixed/0500_foo_b. Questo è il motivo __prev_str().

  • Utilizzando Delimiter, quando si elenca mixed/, ogni risposta dal paginatorcontiene 666 chiavi e 334 prefissi comuni. È abbastanza bravo a non creare risposte enormi.

  • Al contrario, quando si elenca dirs/, ogni risposta dal paginatorcontiene 1000 prefissi comuni (e nessuna chiave).

  • Il superamento di un limite sotto forma di PaginationConfig={'MaxItems': limit}limiti solo il numero di chiavi, non i prefissi comuni. Ci occupiamo di questo troncando ulteriormente il flusso del nostro iteratore.


@Mehdi: non è davvero molto complicato, per un sistema che offre una scala e un'affidabilità così incredibili. Se hai a che fare con più di poche centinaia di TB, otterrai un apprezzamento per ciò che stanno offrendo. Ricorda, le unità hanno sempre un MTBF> 0 ... Pensa alle implicazioni per l'archiviazione dei dati su larga scala. Dichiarazione di non responsabilità: sono un utente AWS attivo e felice, nessun'altra connessione, tranne che ho lavorato su dati su scala petabyte dal 2007 ed era molto più difficile.
Pierre D

39

Mi ci è voluto molto tempo per capirlo, ma finalmente ecco un modo semplice per elencare il contenuto di una sottocartella nel bucket S3 utilizzando boto3. Spero che sia d'aiuto

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
cosa succede se la tua cartella contiene un numero enorme di oggetti?
Pierre D

3
il punto è che questa è una soluzione orribilmente inefficiente. S3 è progettato per gestire separatori arbitrari nelle chiavi. Ad esempio '/',. Ciò consente di saltare "cartelle" piene di oggetti senza doverle impaginare. E poi, anche se insisti per un elenco completo (cioè l'equivalente "ricorsivo" in aws cli), devi usare i paginatori o elencherai solo i primi 1000 oggetti.
Pierre D,

Questa è un'ottima risposta. Per coloro che ne hanno bisogno, ho applicato limita nella mia risposta derivata .
Acumenus

16

La grande realizzazione con S3 è che non ci sono cartelle / directory solo chiavi. L' apparente struttura della cartella viene semplicemente anteposta al nome del file per diventare la "Chiave", quindi per elencare il contenuto di myBucket"s" some/path/to/the/file/puoi provare:

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

che ti darebbe qualcosa come:

some/path/to/the/file/yo.jpg
some/path/to/the/file/meAndYou.gif
...

Questa è una buona risposta, ma recupererà solo fino a 1000 oggetti e non di più. Ho prodotto una risposta derivata che può recuperare un numero maggiore di oggetti.
Acumenus

sì, @Acumenus immagino che la tua risposta sia più complessa
CpILL

16

Ho avuto lo stesso problema, ma sono riuscito a risolverlo utilizzando boto3.cliente list_objects_v2con Buckete StartAfterparametri.

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

Il risultato dell'output per il codice precedente visualizzerà quanto segue:

firstlevelFolder/secondLevelFolder/item1
firstlevelFolder/secondLevelFolder/item2

Documentazione Boto3 list_objects_v2

Per rimuovere solo il nome della directory secondLevelFolderho appena usato il metodo 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]

Il risultato dell'output per il codice precedente visualizzerà quanto segue:

secondLevelFolder
secondLevelFolder

Documentazione di Python split ()

Se desideri ottenere il nome della directory E il nome dell'elemento di contenuto, sostituisci la riga di stampa con quanto segue:

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

E verrà prodotto quanto segue:

secondLevelFolder/item2
secondLevelFolder/item2

Spero che sia di aiuto


9

Il seguente funziona per me ... Oggetti S3:

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

Utilizzando:

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

noi abbiamo:

form1/
form2/
...

Con:

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

noi abbiamo:

form1/section11/
form1/section12/

Questa è l'unica soluzione che ha funzionato per me perché avevo bisogno delle "cartelle" nella radice del bucket, il prefisso deve essere "" mentre altrimenti deve terminare con "/"
Oliver

7

Il cli di AWS esegue questa operazione (presumibilmente senza recuperare e iterare attraverso tutte le chiavi nel bucket) quando esegui aws s3 ls s3://my-bucket/ , quindi ho pensato che ci doveva essere un modo per usare boto3.

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

Sembra che utilizzino effettivamente Prefix e Delimiter: sono stato in grado di scrivere una funzione che mi avrebbe portato tutte le directory a livello di root di un bucket modificando un po 'quel codice:

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

Ecco una possibile soluzione:

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

2

Perché non utilizzare il s3pathpacchetto che lo rende comodo come lavorare con pathlib? Se devi comunque usareboto3 :

Utilizzando boto3.resource

Questo si basa sulla risposta di itz-azhar per applicare un facoltativo limit. Ovviamente è sostanzialmente più semplice da usare rispetto alla boto3.clientversione.

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)

Utilizzando boto3.client

Questo utilizza list_objects_v2e si basa sulla risposta di CpILL per consentire il recupero di più di 1000 oggetti.

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

Prima di tutto, non esiste un vero concetto di cartella in S3. Puoi sicuramente avere un file @'/folder/subfolder/myfile.txt' e nessuna cartella né sottocartella.

Per "simulare" una cartella in S3, devi creare un file vuoto con un "/" alla fine del suo nome (vedi Amazon S3 boto - come creare una cartella? )

Per il tuo problema, dovresti probabilmente usare il metodo get_all_keyscon i 2 parametri: prefixedelimiter

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
Temo di non avere il metodo get_all_keys sull'oggetto bucket. Sto usando boto3 versione 1.2.3.
mar tin

Ho appena controllato boto 1.2a: lì, bucket ha un metodo listcon prefixe delimiter. Suppongo che dovrebbe funzionare.
Pirheas

1
L'oggetto Bucket recuperato durante l'inserimento nella domanda non dispone di questi metodi. Sono su boto3 1.2.6, a quale versione fa riferimento il tuo link?
mar tin


0

So che boto3 è l'argomento in discussione qui, ma trovo che di solito sia più veloce e più intuitivo usare semplicemente awscli per qualcosa di simile: awscli conserva più funzionalità di boto3 per quello che vale.

Ad esempio, se ho oggetti salvati in "sottocartelle" associate a un determinato bucket, posso elencarli tutti con qualcosa del genere:

1) "mydata" = nome del bucket

2) "f1 / f2 / f3" = "percorso" che porta a "file" o oggetti

3) 'foo2.csv, barfar.segy, gar.tar' = tutti gli oggetti "dentro" f3

Quindi, possiamo pensare al "percorso assoluto" che porta a questi oggetti è: 'mydata / f1 / f2 / f3 / foo2.csv' ...

Utilizzando i comandi awscli, possiamo facilmente elencare tutti gli oggetti all'interno di una data "sottocartella" tramite:

aws s3 ls s3: // mydata / f1 / f2 / f3 / --recursive


0

Di seguito è riportato il pezzo di codice che può gestire l'impaginazione, se stai cercando di recuperare un numero elevato di oggetti del bucket 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

cosa succede se la prima pagina è piena di "CommonPrefixes" e non fornisce alcun tasto "Contents". Penso che una corretta implementazione salterà la chiave dei contenuti mancanti e continuerà con la pagina successiva.
Jan Vlcinsky

0

Per quanto riguarda Boto 1.13.3, risulta essere così semplice (se salti tutte le considerazioni sull'impaginazione, che sono state trattate in altre risposte):

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']]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.