Usando boto3, posso acessar meu intervalo AWS S3:
s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket-name')
Agora, o depósito contém a pasta first-level
, que por sua vez contém várias subpastas nomeadas com um carimbo de data / hora, por exemplo 1456753904534
. Eu preciso saber o nome dessas subpastas para outro trabalho que estou fazendo e me pergunto se eu poderia ter boto3 recuperado para mim.
Então eu tentei:
objs = bucket.meta.client.list_objects(Bucket='my-bucket-name')
que fornece um dicionário, cuja chave 'Conteúdo' me dá todos os arquivos de terceiro nível em vez dos diretórios de carimbo de data / hora de segundo nível, na verdade, recebo uma lista contendo coisas como
{u'ETag ':' "etag" ', u'Key': primeiro nível / 1456753904534 / parte-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 '}
você pode ver que os arquivos específicos, neste caso, part-00014
são recuperados, enquanto eu gostaria de obter apenas o nome do diretório. Em princípio, eu poderia retirar o nome do diretório de todos os caminhos, mas é feio e caro recuperar tudo no terceiro nível para chegar ao segundo nível!
Também tentei algo relatado aqui :
for o in bucket.objects.filter(Delimiter='/'):
print(o.key)
mas não obtenho as pastas no nível desejado.
Há uma maneira de resolver isto?
fonte
/
para obter as subpastasRespostas:
S3 é um armazenamento de objeto, não possui uma estrutura de diretório real. O "/" é bastante cosmético. Uma razão pela qual as pessoas desejam ter uma estrutura de diretório, é porque podem manter / podar / adicionar uma árvore ao aplicativo. No S3, você trata essa estrutura como uma espécie de índice ou tag de pesquisa.
Para manipular objetos no S3, você precisa de boto3.client ou boto3.resource, por exemplo, para listar todos os 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
Na verdade, se o nome do objeto s3 for armazenado usando o separador '/'. A versão mais recente de list_objects (list_objects_v2) permite que você limite a resposta às chaves que começam com o prefixo especificado.
Para limitar os itens a itens em certas subpastas:
import boto3 s3 = boto3.client("s3") response = s3.list_objects_v2( Bucket=BUCKET, Prefix ='DIR1/DIR2', MaxKeys=100 )
Documentação
Outra opção é usar a função python os.path para extrair o prefixo da pasta. O problema é que isso exigirá a listagem de objetos de diretórios indesejados.
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]
Um lembrete sobre boto3: boto3.resource é uma boa API de alto nível. Existem prós e contras usando boto3.client vs boto3.resource. Se você desenvolver uma biblioteca compartilhada interna, o uso de boto3.resource lhe dará uma camada de caixa preta sobre os recursos usados.
fonte
directory_name = os.path.dirname(directory/path/and/filename.txt)
andfile_name = os.path.basename(directory/path/and/filename.txt)
O trecho de código abaixo retorna SOMENTE as 'subpastas' em uma 'pasta' do balde 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 obter mais detalhes, você pode consultar https://github.com/boto/boto3/issues/134
fonte
Resposta curta :
Use
Delimiter='/'
. Isso evita fazer uma listagem recursiva do seu intervalo. Algumas respostas aqui sugerem erroneamente fazer uma lista completa e usar alguma manipulação de string para recuperar os nomes dos diretórios. Isso pode ser terrivelmente ineficiente. Lembre-se de que o S3 praticamente não tem limite para o número de objetos que um depósito pode conter. Então, imagine que, entrebar/
efoo/
, você tenha um trilhão de objetos: você esperaria muito tempo para obtê-lo['bar/', 'foo/']
.Use
Paginators
. Pelo mesmo motivo (S3 é a aproximação do infinito de um engenheiro), você deve listar por meio de páginas e evitar armazenar toda a listagem na memória. Em vez disso, considere seu "lister" como um iterador e manipule o fluxo que ele produz.Use
boto3.client
, nãoboto3.resource
. Aresource
versão não parece lidar bem com aDelimiter
opção. Se você tem um recurso, digamos que umbucket = boto3.resource('s3').Bucket(name)
, você pode obter o cliente correspondente com:bucket.meta.client
.Resposta longa :
A seguir está um iterador que uso para baldes simples (sem manipulação de versão).
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
Teste :
O seguinte é útil para testar o comportamento do
paginator
elist_objects
. Ele cria vários diretórios e arquivos. Como as páginas têm até 1000 entradas, usamos um múltiplo disso para diretórios e arquivos.dirs
contém apenas diretórios (cada um com um objeto).mixed
contém uma mistura de dirs e objetos, com uma proporção de 2 objetos para cada dir (mais um objeto sob dir, é claro; o S3 armazena apenas 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())
A estrutura resultante é:
Com um pouco de adulteração do código fornecido acima para
s3list
inspecionar as respostas dopaginator
, você pode observar alguns fatos interessantes:O
Marker
é realmente exclusivo. DadoMarker=topdir + 'mixed/0500_foo_a'
fará com que a listagem comece após essa chave (de acordo com a API AmazonS3 ), ou seja, com.../mixed/0500_foo_b
. Essa é a razão para__prev_str()
.Usando
Delimiter
, ao listarmixed/
, cada resposta depaginator
contém 666 chaves e 334 prefixos comuns. É muito bom em não construir respostas enormes.Em contraste, ao listar
dirs/
, cada resposta dopaginator
contém 1000 prefixos comuns (e nenhuma chave).Passar um limite na forma de
PaginationConfig={'MaxItems': limit}
limites apenas o número de chaves, não os prefixos comuns. Lidamos com isso truncando ainda mais o fluxo de nosso iterador.fonte
Levei muito tempo para descobrir, mas finalmente aqui está uma maneira simples de listar o conteúdo de uma subpasta no balde S3 usando boto3. Espero que ajude
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))
fonte
'/'
,. Isso permite que você pule "pastas" cheias de objetos sem ter que paginar sobre eles. E então, mesmo se você insistir em uma listagem completa (ou seja, o equivalente 'recursivo' em aws cli), você deve usar paginadores ou listará apenas os primeiros 1000 objetos.limit
a em minha resposta derivada .A grande descoberta com o S3 é que não há pastas / diretórios, apenas chaves. A aparente estrutura da pasta é apenas anexada ao nome do arquivo para se tornar a 'Chave', então para listar o conteúdo de
myBucket
,some/path/to/the/file/
você pode tentar:s3 = boto3.client('s3') for obj in s3.list_objects_v2(Bucket="myBucket", Prefix="some/path/to/the/file/")['Contents']: print(obj['Key'])
o que lhe daria algo como:
fonte
Eu tive o mesmo problema, mas consegui resolver usando
boto3.client
elist_objects_v2
com os parâmetrosBucket
eStartAfter
.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']
O resultado de saída para o código acima exibiria o seguinte:
Documentação Boto3 list_objects_v2
Para remover apenas o nome do diretório,
secondLevelFolder
acabei de usar o 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]
O resultado de saída para o código acima exibiria o seguinte:
Documentação Python split ()
Se você deseja obter o nome do diretório E o nome do item de conteúdo, substitua a linha de impressão pelo seguinte:
print "{}/{}".format(fileName[1], fileName[2])
E o seguinte será gerado:
Espero que isto ajude
fonte
O seguinte funciona para mim ... objetos S3:
Usando:
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']]
Nós temos:
Com:
resp = s3client.list_objects(Bucket=bucket, Prefix='form1/', Delimiter="/") sections = [x['Prefix'] for x in resp['CommonPrefixes']]
Nós temos:
fonte
O AWS cli faz isso (presumivelmente sem buscar e iterar por todas as chaves no balde) quando você executa
aws s3 ls s3://my-bucket/
, então percebi que deve haver uma maneira de usar o boto3.https://github.com/aws/aws-cli/blob/0fedc4c1b6a7aee13e2ed10c3ada778c702c22c3/awscli/customizations/s3/subcommands.py#L499
Parece que eles realmente usam Prefixo e Delimitador - consegui escrever uma função que me levaria a todos os diretórios no nível raiz de um intervalo, modificando um pouco esse 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
fonte
Aqui está uma solução possível:
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']]
fonte
Por que não usar o
s3path
pacote que o torna tão conveniente quanto trabalharpathlib
? No entanto, se você deve usarboto3
:Usando
boto3.resource
Isso se baseia na resposta de itz-azhar para aplicar um opcional
limit
. É obviamente substancialmente mais simples de usar do que aboto3.client
versão.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)
Usando
boto3.client
Isso usa
list_objects_v2
e se baseia na resposta de CpILL para permitir a recuperação de mais 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)
fonte
Em primeiro lugar, não existe um conceito real de pasta no S3. Você definitivamente pode ter um arquivo @
'/folder/subfolder/myfile.txt'
e nenhuma pasta ou subpasta.Para "simular" uma pasta no S3, você deve criar um arquivo vazio com uma '/' no final de seu nome (consulte Amazon S3 boto - como criar uma pasta? )
Para o seu problema, você provavelmente deve usar o método
get_all_keys
com os 2 parâmetros:prefix
edelimiter
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)
fonte
list
comprefix
edelimiter
. Suponho que deve funcionar.Eu sei que o boto3 é o tópico que está sendo discutido aqui, mas acho que geralmente é mais rápido e intuitivo simplesmente usar o awscli para algo assim - o awscli retém mais recursos do que o boto3 para o que vale.
Por exemplo, se tenho objetos salvos em "subpastas" associadas a um determinado intervalo, posso listá-los todos com algo como este:
Portanto, podemos pensar no "caminho absoluto" que leva a esses objetos: 'mydata / f1 / f2 / f3 / foo2.csv' ...
Usando comandos awscli, podemos listar facilmente todos os objetos dentro de uma determinada "subpasta" via:
fonte
A seguir está o trecho de código que pode lidar com a paginação, se você estiver tentando buscar um grande número de objetos de intervalo 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
fonte
Quanto ao Boto 1.13.3, torna-se tão simples quanto isso (se você pular todas as considerações de paginação, que foram abordadas em outras respostas):
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']]
fonte