Abrufen von Unterordnernamen im S3-Bucket von boto3

85

Mit boto3 kann ich auf meinen AWS S3-Bucket zugreifen:

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

Jetzt enthält der Bucket einen Ordner first-level, der selbst mehrere Unterordner enthält , die beispielsweise mit einem Zeitstempel benannt sind 1456753904534. Ich muss den Namen dieser Unterordner für einen anderen Job kennen, den ich mache, und ich frage mich, ob ich boto3 diese für mich abrufen lassen könnte.

Also habe ich versucht:

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

Das gibt ein Wörterbuch, dessen Schlüssel 'Inhalt' mir alle Dateien der dritten Ebene anstelle der Zeitstempelverzeichnisse der zweiten Ebene gibt. Tatsächlich bekomme ich eine Liste mit Dingen wie

{u'ETag ':' "etag" ', u'Key': erste Ebene / 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 '}

Sie können sehen, dass die spezifischen Dateien, in diesem Fall, part-00014abgerufen werden, während ich nur den Namen des Verzeichnisses erhalten möchte. Im Prinzip könnte ich den Verzeichnisnamen aus allen Pfaden entfernen, aber es ist hässlich und teuer, alles auf der dritten Ebene abzurufen, um die zweite Ebene zu erhalten!

Ich habe auch etwas ausprobiert, über das hier berichtet wurde :

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

aber ich bekomme die Ordner nicht auf die gewünschte Ebene.

Gibt es eine Möglichkeit, dies zu lösen?

mar tin
quelle
Sie sagen also, dass dies nicht funktioniert? Könnten Sie posten, was passiert, wenn Sie das ausführen?
Jordon Phillips
1
@JordonPhillips Ich habe die ersten Zeilen dieses von Ihnen gesendeten Links ausprobiert, die ich hier eingefügt habe, und ich erhalte die Textdateien auf der allerersten Ebene des Buckets und keine Ordner.
mar tin
@mar tin Hast du dieses Problem jemals gelöst? Ich stehe vor einem ähnlichen Dilemma, in dem ich das erste Element in jedem Eimer-Unterordner benötige.
Ted Taylor des Lebens
1
@TedTaylorofLife Ja, kein anderer Weg als durch all Objekte und Spalten immer /Unterordner zu bekommen
mar tin
1
@ mar tin Der einzige Weg, den ich gemacht habe, ist, die Ausgabe zu nehmen, sie in ein Textformat und eine Komma-Begrenzung durch "/" zu werfen und dann das erste Element zu kopieren und einzufügen. Was für ein Schmerz im Arsch.
Ted Taylor des Lebens

Antworten:

56

S3 ist ein Objektspeicher, er hat keine echte Verzeichnisstruktur. Das "/" ist eher kosmetisch. Ein Grund, warum Benutzer eine Verzeichnisstruktur wünschen, weil sie der Anwendung einen Baum pflegen / beschneiden / hinzufügen können. In S3 behandeln Sie eine solche Struktur als eine Art Index oder Such-Tag.

Um ein Objekt in S3 zu bearbeiten, benötigen Sie boto3.client oder boto3.resource, z. B. um alle Objekte aufzulisten

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

In der Tat, wenn der s3-Objektname mit dem Trennzeichen '/' gespeichert wird. Mit der neueren Version von list_objects (list_objects_v2) können Sie die Antwort auf Schlüssel beschränken, die mit dem angegebenen Präfix beginnen.

So beschränken Sie die Elemente auf Elemente in bestimmten Unterordnern:

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

Dokumentation

Eine andere Option ist die Verwendung der Python os.path-Funktion, um das Ordnerpräfix zu extrahieren. Das Problem ist, dass hierfür Objekte aus unerwünschten Verzeichnissen aufgelistet werden müssen.

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]

Eine Erinnerung an boto3: boto3.resource ist eine nette High-Level-API. Es gibt Vor- und Nachteile bei der Verwendung von boto3.client vs boto3.resource. Wenn Sie eine interne gemeinsam genutzte Bibliothek entwickeln, erhalten Sie mit boto3.resource eine Blackbox-Ebene über den verwendeten Ressourcen.

Mootmoot
quelle
1
Dies gibt mir das gleiche Ergebnis, das ich mit meinem Versuch in der Frage bekomme. Ich denke, ich muss den schwierigen Weg lösen, indem ich alle Schlüssel von den zurückgegebenen Objekten nehme und die Zeichenfolge aufteile, um den Ordnernamen zu erhalten.
mar tin
1
@martina: ein fauler Python-Split und die letzten Daten in der Liste abholen, z. B. Dateiname = keyname.split ("/") [- 1]
mootmoot
1
@martin directory_name = os.path.dirname(directory/path/and/filename.txt)undfile_name = os.path.basename(directory/path/and/filename.txt)
jkdev
106

Der folgende Code gibt NUR die 'Unterordner' in einem 'Ordner' aus dem s3-Bucket zurück.

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

Weitere Informationen finden Sie unter https://github.com/boto/boto3/issues/134

Dipankar
quelle
12
Was ist, wenn ich den Inhalt eines bestimmten Unterordners auflisten möchte?
Azhar22k
1
@ azhar22k, ich nehme an, Sie könnten die Funktion einfach rekursiv für jeden 'Unterordner' ausführen.
Serban Cezar
Was ist, wenn es mehr als 1000 verschiedene Präfixe gibt?
Kostrahb
38

Ich habe viel Zeit gebraucht, um das herauszufinden, aber schließlich gibt es hier eine einfache Möglichkeit, den Inhalt eines Unterordners im S3-Bucket mit boto3 aufzulisten. Ich hoffe es hilft

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))
azhar22k
quelle
3
Was ist, wenn Ihr Ordner eine enorme Anzahl von Objekten enthält?
Pierre D
3
Mein Punkt ist, dass dies eine schrecklich ineffiziente Lösung ist. S3 wurde entwickelt, um mit beliebigen Trennzeichen in den Schlüsseln umzugehen. Zum Beispiel '/'. Damit können Sie "Ordner" voller Objekte überspringen, ohne darüber paginieren zu müssen. Und selbst wenn Sie auf einer vollständigen Auflistung bestehen (dh auf dem 'rekursiven' Äquivalent in aws cli), müssen Sie Paginatoren verwenden, oder Sie listen nur die ersten 1000 Objekte auf.
Pierre D
Dies ist eine großartige Antwort. Für diejenigen, die es brauchen, habe ich limitin meiner abgeleiteten Antwort ein a darauf angewendet .
Acumenus
38

Kurze Antwort :

  • Verwenden Sie Delimiter='/'. Dies vermeidet eine rekursive Auflistung Ihres Buckets. Einige Antworten hier schlagen fälschlicherweise vor, eine vollständige Liste zu erstellen und die Verzeichnisnamen mithilfe einer Zeichenfolgenmanipulation abzurufen. Dies könnte schrecklich ineffizient sein. Denken Sie daran, dass S3 praktisch keine Begrenzung für die Anzahl der Objekte hat, die ein Bucket enthalten kann. Stellen Sie sich also vor, Sie haben zwischen bar/und foo/eine Billion Objekte: Sie würden sehr lange warten, bis Sie sie erhalten ['bar/', 'foo/'].

  • Verwenden Sie Paginators. Aus dem gleichen Grund (S3 ist eine Annäherung der Unendlichkeit des Ingenieurs), Sie müssen durch die Seiten auflisten und vermeiden Sie alle Auflistung im Speicher zu speichern. Betrachten Sie stattdessen Ihren "Lister" als Iterator und behandeln Sie den von ihm erzeugten Stream.

  • Verwenden Sie boto3.clientnicht boto3.resource. Die resourceVersion scheint die DelimiterOption nicht gut zu handhaben . Wenn Sie eine Ressource haben, sagen Sie a bucket = boto3.resource('s3').Bucket(name), können Sie den entsprechenden Client erhalten mit : bucket.meta.client.

Lange Antwort :

Das Folgende ist ein Iterator, den ich für einfache Buckets verwende (keine Versionsbehandlung).

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 :

Das Folgende ist hilfreich, um das Verhalten von paginatorund zu testen list_objects. Es werden eine Reihe von Verzeichnissen und Dateien erstellt. Da die Seiten bis zu 1000 Einträge umfassen, verwenden wir ein Vielfaches davon für Verzeichnisse und Dateien. dirsenthält nur Verzeichnisse (jedes mit einem Objekt). mixedenthält eine Mischung aus Verzeichnissen und Objekten mit einem Verhältnis von 2 Objekten für jedes Verzeichnis (plus natürlich ein Objekt unter Verzeichnis; S3 speichert nur Objekte).

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

Die resultierende Struktur ist:

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

Wenn Sie den oben angegebenen Code ein wenig bearbeiten s3list, um die Antworten von zu überprüfen paginator, können Sie einige interessante Fakten beobachten:

  • Das Markerist wirklich exklusiv. Mit gegeben Marker=topdir + 'mixed/0500_foo_a'wird die Auflistung nach diesem Schlüssel beginnen (gemäß der AmazonS3-API ), dh mit .../mixed/0500_foo_b. Das ist der Grund dafür __prev_str().

  • Verwenden Delimiter, wenn die Auflistung mixed/, jede Antwort von der paginatorenthält 666 Schlüssel und 334 gemeinsame Präfixe. Es ist ziemlich gut, keine enormen Antworten zu erstellen.

  • Im Gegensatz dazu enthält dirs/jede Antwort von der Liste paginator1000 gemeinsame Präfixe (und keine Schlüssel).

  • Übergeben eines Limits in Form von PaginationConfig={'MaxItems': limit}Limits nur die Anzahl der Schlüssel, nicht die allgemeinen Präfixe. Wir beschäftigen uns damit, indem wir den Stream unseres Iterators weiter abschneiden.

Pierre D.
quelle
@Mehdi: Es ist wirklich nicht sehr kompliziert für ein System, das solch unglaubliche Skalierbarkeit und Zuverlässigkeit bietet. Wenn Sie jemals mit mehr als ein paar hundert TBs zu tun haben, werden Sie eine Wertschätzung für das bekommen, was sie anbieten. Denken Sie daran, Laufwerke haben immer eine MTBF> 0 ... Denken Sie über die Auswirkungen auf die Datenspeicherung in großem Maßstab nach. Haftungsausschluss: Ich bin ein aktiver und glücklicher AWS-Benutzer, keine andere Verbindung, außer dass ich seit 2007 an Daten im Petabyte-Maßstab gearbeitet habe und es früher viel schwieriger war.
Pierre D
16

Die große Erkenntnis bei S3 ist, dass es keine Ordner / Verzeichnisse gibt, nur Schlüssel. Die scheinbare Ordnerstruktur wird nur dem Dateinamen vorangestellt, um der 'Schlüssel' zu werden. Um den Inhalt von myBucket's some/path/to/the/file/aufzulisten, können Sie Folgendes versuchen:

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

das würde dir so etwas geben wie:

some/path/to/the/file/yo.jpg
some/path/to/the/file/meAndYou.gif
...
CpILL
quelle
Dies ist eine gute Antwort, aber es werden nur bis zu 1000 Objekte und nicht mehr abgerufen. Ich habe eine abgeleitete Antwort erstellt, mit der eine größere Anzahl von Objekten abgerufen werden kann.
Acumenus
Ja, @Acumenus Ich denke, Ihre Antwort ist komplexer
CpILL
16

Ich hatte das gleiche Problem, konnte es jedoch mit boto3.clientund list_objects_v2mit Bucketund StartAfterParametern beheben .

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

Das Ausgabeergebnis für den obigen Code würde Folgendes anzeigen:

firstlevelFolder/secondLevelFolder/item1
firstlevelFolder/secondLevelFolder/item2

Boto3 list_objects_v2 Dokumentation

Um nur den Verzeichnisnamen für zu entfernen, habe secondLevelFolderich gerade die Python-Methode verwendet 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]

Das Ausgabeergebnis für den obigen Code würde Folgendes anzeigen:

secondLevelFolder
secondLevelFolder

Python split () Dokumentation

Wenn Sie den Verzeichnisnamen UND den Namen des Inhaltselements erhalten möchten, ersetzen Sie die Druckzeile durch die folgende:

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

Und folgendes wird ausgegeben:

secondLevelFolder/item2
secondLevelFolder/item2

Hoffe das hilft

Sophie Muspratt
quelle
8

Folgendes funktioniert für mich ... S3-Objekte:

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

Verwenden von:

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

wir bekommen:

form1/
form2/
...

Mit:

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

wir bekommen:

form1/section11/
form1/section12/
cem
quelle
6

Die AWS-CLI tut dies (vermutlich ohne alle Schlüssel im Bucket aws s3 ls s3://my-bucket/abzurufen und zu durchlaufen), wenn Sie ausgeführt werden. Ich dachte mir, dass es eine Möglichkeit geben muss, boto3 zu verwenden.

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

Es sieht so aus, als würden sie tatsächlich Präfix und Trennzeichen verwenden. Ich konnte eine Funktion schreiben, mit der ich alle Verzeichnisse auf der Stammebene eines Buckets erhalten konnte, indem ich diesen Code ein wenig änderte:

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
Paul Zielinski
quelle
2

Hier ist eine mögliche Lösung:

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']]
Ambigus9
quelle
1

Verwenden von boto3.resource

Dies baut auf der Antwort von itz-azhar auf, ein optionales anzuwenden limit. Es ist offensichtlich wesentlich einfacher zu bedienen als die boto3.clientVersion.

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)

Verwenden von boto3.client

Dies verwendet list_objects_v2und baut auf der Antwort von CpILL auf, um das Abrufen von mehr als 1000 Objekten zu ermöglichen.

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)
Scharfsinn
quelle
0

Erstens gibt es in S3 kein echtes Ordnerkonzept. Sie können definitiv eine Datei @ '/folder/subfolder/myfile.txt'und keinen Ordner oder Unterordner haben.

Um einen Ordner in S3 zu "simulieren", müssen Sie eine leere Datei mit einem '/' am Ende des Namens erstellen (siehe Amazon S3-Boto - Wie erstelle ich einen Ordner? )

Für Ihr Problem sollten Sie wahrscheinlich die Methode get_all_keysmit den 2 Parametern verwenden: prefixunddelimiter

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)
Pirheas
quelle
1
Ich fürchte, ich habe die Methode get_all_keys nicht für das Bucket-Objekt. Ich benutze boto3 Version 1.2.3.
mar tin
Gerade Boto 1.2a überprüft: Dort hat Bucket eine Methode listmit prefixund delimiter. Ich nehme an, es sollte funktionieren.
Pirheas
1
Das Bucket-Objekt, das beim Posten in der Frage abgerufen wurde, verfügt nicht über diese Methoden. Ich bin auf boto3 1.2.6. Auf welche Version verweist Ihr Link?
mar tin
0

Ich weiß, dass boto3 das Thema ist, das hier diskutiert wird, aber ich finde, dass es normalerweise schneller und intuitiver ist, awscli einfach für so etwas zu verwenden - awscli behält mehr Funktionen als boto3 für das, was es wert ist.

Wenn ich beispielsweise Objekte in "Unterordnern" gespeichert habe, die einem bestimmten Bucket zugeordnet sind, kann ich sie alle wie folgt auflisten:

1) 'mydata' = Bucket-Name

2) 'f1 / f2 / f3' = "Pfad", der zu "Dateien" oder Objekten führt

3) 'foo2.csv, barfar.segy, gar.tar' = alle Objekte "innerhalb" f3

Wir können uns also den "absoluten Pfad" vorstellen, der zu diesen Objekten führt: 'mydata / f1 / f2 / f3 / foo2.csv' ...

Mit awscli-Befehlen können wir einfach alle Objekte in einem bestimmten "Unterordner" auflisten über:

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

Nathan Benton
quelle
0

Im Folgenden finden Sie den Code, der die Paginierung verarbeiten kann, wenn Sie versuchen, eine große Anzahl von S3-Bucket-Objekten abzurufen:

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
peterDriscoll
quelle
0

Boto 1.13.3 ist so einfach (wenn Sie alle Überlegungen zur Paginierung überspringen, die in anderen Antworten behandelt wurden):

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']]
Vitalii Kotliarenko
quelle