Con boto3, puedo acceder a mi bucket de AWS S3:
s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket-name')
Ahora, el depósito contiene una carpeta first-level
, que a su vez contiene varias subcarpetas nombradas con una marca de tiempo, por ejemplo 1456753904534
. Necesito saber el nombre de estas subcarpetas para otro trabajo que estoy haciendo y me pregunto si podría hacer que boto3 las recupere por mí.
Así que intenté:
objs = bucket.meta.client.list_objects(Bucket='my-bucket-name')
que da un diccionario, cuya clave 'Contenido' me da todos los archivos de tercer nivel en lugar de los directorios de marca de tiempo del segundo nivel, de hecho obtengo una lista que contiene cosas como
{u'ETag ':' "etag" ', u'Key': primer nivel / 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 '}
puede ver que los archivos específicos, en este caso, part-00014
se recuperan, mientras que me gustaría obtener el nombre del directorio solo. En principio, podría eliminar el nombre del directorio de todas las rutas, ¡pero es feo y costoso recuperar todo en el tercer nivel para obtener el segundo nivel!
También probé algo reportado aquí :
for o in bucket.objects.filter(Delimiter='/'):
print(o.key)
pero no obtengo las carpetas en el nivel deseado.
¿Hay una manera de resolver esto?
fuente
/
para obtener subcarpetasRespuestas:
S3 es un almacenamiento de objetos, no tiene una estructura de directorio real. El "/" es bastante cosmético. Una de las razones por las que la gente quiere tener una estructura de directorios es porque pueden mantener / podar / agregar un árbol a la aplicación. Para S3, trata dicha estructura como una especie de índice o etiqueta de búsqueda.
Para manipular un objeto en S3, necesita boto3.client o boto3.resource, por ejemplo, para enumerar todos los objetos
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
De hecho, si el nombre del objeto s3 se almacena usando el separador '/'. La versión más reciente de list_objects (list_objects_v2) le permite limitar la respuesta a las claves que comienzan con el prefijo especificado.
Para limitar los elementos a elementos de determinadas subcarpetas:
import boto3 s3 = boto3.client("s3") response = s3.list_objects_v2( Bucket=BUCKET, Prefix ='DIR1/DIR2', MaxKeys=100 )
Documentación
Otra opción es usar la función python os.path para extraer el prefijo de la carpeta. El problema es que esto requerirá enumerar objetos de directorios no deseados.
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 recordatorio sobre boto3: boto3.resource es una buena API de alto nivel. Existen pros y contras de usar boto3.client vs boto3.resource. Si desarrolla una biblioteca compartida interna, el uso de boto3.resource le dará una capa de caja negra sobre los recursos utilizados.
fuente
directory_name = os.path.dirname(directory/path/and/filename.txt)
yfile_name = os.path.basename(directory/path/and/filename.txt)
El siguiente fragmento de código devuelve SÓLO las 'subcarpetas' en una 'carpeta' del depósito 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')
Para obtener más detalles, puede consultar https://github.com/boto/boto3/issues/134
fuente
Me tomó mucho tiempo averiguarlo, pero finalmente aquí hay una forma simple de enumerar el contenido de una subcarpeta en el depósito S3 usando boto3. Espero eso ayude
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))
fuente
'/'
,. Eso le permite omitir "carpetas" llenas de objetos sin tener que paginarlos. Y luego, incluso si insiste en una lista completa (es decir, el equivalente 'recursivo' en aws cli), debe usar paginadores o enumerará solo los primeros 1000 objetos.limit
en mi respuesta derivada .Respuesta corta :
Utilice
Delimiter='/'
. Esto evita hacer una lista recursiva de su depósito. Algunas respuestas aquí sugieren erróneamente hacer una lista completa y usar alguna manipulación de cadenas para recuperar los nombres de directorio. Esto podría resultar terriblemente ineficaz. Recuerde que S3 prácticamente no tiene límite en la cantidad de objetos que puede contener un depósito. Entonces, imagina que, entrebar/
yfoo/
, tienes un billón de objetos: esperarías mucho tiempo para obtenerlos['bar/', 'foo/']
.Utilice
Paginators
. Por la misma razón (S3 es la aproximación de infinito de un ingeniero), debe enumerar páginas y evitar almacenar todas las listas en la memoria. En su lugar, considere su "lister" como un iterador y maneje el flujo que produce.Utilice
boto3.client
, noboto3.resource
. Laresource
versión no parece manejar bien laDelimiter
opción. Si usted tiene un recurso, por ejemplo unabucket = boto3.resource('s3').Bucket(name)
, se puede obtener el cliente correspondiente a:bucket.meta.client
.Respuesta larga :
El siguiente es un iterador que utilizo para depósitos simples (sin manejo de versiones).
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
Prueba :
Lo siguiente es útil para probar el comportamiento de
paginator
ylist_objects
. Crea varios directorios y archivos. Dado que las páginas tienen hasta 1000 entradas, usamos un múltiplo de eso para directorios y archivos.dirs
contiene solo directorios (cada uno con un objeto).mixed
contiene una mezcla de directorios y objetos, con una proporción de 2 objetos por cada directorio (más un objeto debajo de dir, por supuesto; S3 almacena solo objetos).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 estructura resultante es:
./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 poco de revisión del código proporcionado anteriormente para
s3list
inspeccionar las respuestas delpaginator
, puede observar algunos datos divertidos:El
Marker
es realmente exclusivo. DadoMarker=topdir + 'mixed/0500_foo_a'
hará que el listado comience después de esa clave (según la API de AmazonS3 ), es decir, con.../mixed/0500_foo_b
. Esa es la razón__prev_str()
.Usando
Delimiter
, al enumerarmixed/
, cada respuesta delpaginator
contiene 666 claves y 334 prefijos comunes. Es bastante bueno para no generar respuestas enormes.Por el contrario, al enumerar
dirs/
, cada respuesta delpaginator
contiene 1000 prefijos comunes (y no claves).Pasar un límite en forma de
PaginationConfig={'MaxItems': limit}
límites solo el número de claves, no los prefijos comunes. Nos ocupamos de eso truncando aún más el flujo de nuestro iterador.fuente
El gran descubrimiento con S3 es que no hay carpetas / directorios, solo claves. La aparente estructura de carpetas simplemente se antepone al nombre del archivo para convertirse en la 'Clave', por lo que para enumerar el contenido de
myBucket
'ssome/path/to/the/file/
puede probar:s3 = boto3.client('s3') for obj in s3.list_objects_v2(Bucket="myBucket", Prefix="some/path/to/the/file/")['Contents']: print(obj['Key'])
que te daría algo como:
fuente
Tuve el mismo problema pero logré resolverlo usando
boto3.client
ylist_objects_v2
conBucket
yStartAfter
parámetros.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']
El resultado de salida para el código anterior mostraría lo siguiente:
Documentación de Boto3 list_objects_v2
Para eliminar solo el nombre del directorio
secondLevelFolder
, solo usé el método Pythonsplit()
: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]
El resultado de salida para el código anterior mostraría lo siguiente:
Documentación de Python split ()
Si desea obtener el nombre del directorio Y el nombre del elemento de contenido, reemplace la línea de impresión con lo siguiente:
print "{}/{}".format(fileName[1], fileName[2])
Y saldrá lo siguiente:
Espero que esto ayude
fuente
Lo siguiente funciona para mí ... Objetos S3:
Utilizando:
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']]
obtenemos:
Con:
resp = s3client.list_objects(Bucket=bucket, Prefix='form1/', Delimiter="/") sections = [x['Prefix'] for x in resp['CommonPrefixes']]
obtenemos:
fuente
La cli de AWS hace esto (presumiblemente sin buscar e iterar a través de todas las claves en el depósito) cuando se ejecuta
aws s3 ls s3://my-bucket/
, así que pensé que debe haber una forma de usar boto3.https://github.com/aws/aws-cli/blob/0fedc4c1b6a7aee13e2ed10c3ada778c702c22c3/awscli/customizations/s3/subcommands.py#L499
Parece que de hecho usan Prefijo y Delimitador: pude escribir una función que me llevaría todos los directorios al nivel raíz de un depósito modificando un poco ese código:
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
fuente
Aquí hay una posible solución:
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']]
fuente
Utilizando
boto3.resource
Esto se basa en la respuesta de itz-azhar para aplicar un opcional
limit
. Obviamente, es sustancialmente más simple de usar que laboto3.client
versión.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)
Utilizando
boto3.client
Esto utiliza
list_objects_v2
y se basa en la respuesta de CpILL para permitir recuperar más de 1000 objetos.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)
fuente
En primer lugar, no existe un concepto de carpeta real en S3. Definitivamente puede tener un archivo @
'/folder/subfolder/myfile.txt'
y ninguna carpeta ni subcarpeta.Para "simular" una carpeta en S3, debe crear un archivo vacío con una '/' al final de su nombre (consulte el boto de Amazon S3: ¿cómo crear una carpeta? )
Para su problema, probablemente debería usar el método
get_all_keys
con los 2 parámetros:prefix
ydelimiter
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)
fuente
list
conprefix
ydelimiter
. Supongo que debería funcionar.Sé que boto3 es el tema que se está discutiendo aquí, pero encuentro que generalmente es más rápido e intuitivo usar simplemente awscli para algo como esto: awscli retiene más capacidades que boto3 por lo que vale.
Por ejemplo, si tengo objetos guardados en "subcarpetas" asociadas con un depósito determinado, puedo enumerarlos todos con algo como esto:
Entonces, podemos pensar que la "ruta absoluta" que conduce a estos objetos es: 'mydata / f1 / f2 / f3 / foo2.csv' ...
Con los comandos awscli, podemos enumerar fácilmente todos los objetos dentro de una "subcarpeta" determinada mediante:
fuente
A continuación, se muestra el fragmento de código que puede manejar la paginación, si está intentando obtener una gran cantidad de objetos de depósito de 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
fuente
En cuanto a Boto 1.13.3, resulta tan simple como eso (si omite todas las consideraciones de paginación, que se cubrieron en otras respuestas):
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']]
fuente