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?
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.
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
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))
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
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
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
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/
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
...
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.
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
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)
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, entrebar/
etfoo/
, 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 versionresource
ne semble pas bien gérer l'optionDelimiter
. Si vous avez une ressource, dites unbucket = 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 dupaginator
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 dupaginator
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.