Korzystając z boto3, mogę uzyskać dostęp do mojego wiadra AWS S3:
s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket-name')
Teraz zasobnik zawiera folder first-level
, który sam zawiera na przykład kilka podfolderów o nazwach ze znacznikiem czasu 1456753904534
. Muszę znać nazwy tych podfolderów do innej pracy, którą wykonuję i zastanawiam się, czy mógłbym poprosić boto3 o ich odzyskanie.
Więc spróbowałem:
objs = bucket.meta.client.list_objects(Bucket='my-bucket-name')
co daje słownik, którego klucz „Zawartość” daje mi wszystkie pliki trzeciego poziomu zamiast katalogów z datownikami drugiego poziomu, w rzeczywistości otrzymuję listę zawierającą rzeczy takie jak
{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 '}
widać, że w tym przypadku part-00014
pobierane są określone pliki, podczas gdy chciałbym uzyskać samą nazwę katalogu. W zasadzie mógłbym usunąć nazwę katalogu ze wszystkich ścieżek, ale pobieranie wszystkiego na trzecim poziomie, aby uzyskać drugi poziom, jest brzydkie i kosztowne!
Próbowałem też czegoś zgłoszonego tutaj :
for o in bucket.objects.filter(Delimiter='/'):
print(o.key)
ale nie otrzymuję folderów na pożądanym poziomie.
Czy jest sposób na rozwiązanie tego problemu?
źródło
/
aby uzyskać podfolderyOdpowiedzi:
S3 to magazyn obiektowy, nie ma rzeczywistej struktury katalogów. Znak „/” jest raczej kosmetyczny. Jednym z powodów, dla których ludzie chcą mieć strukturę katalogów, ponieważ mogą utrzymywać / przycinać / dodawać drzewo do aplikacji. W przypadku S3 traktujesz taką strukturę jako rodzaj indeksu lub tagu wyszukiwania.
Aby manipulować obiektem w S3, potrzebujesz boto3.client lub boto3.resource, np. Aby wyświetlić wszystkie obiekty
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
W rzeczywistości, jeśli nazwa obiektu s3 jest przechowywana przy użyciu separatora „/”. Nowsza wersja list_objects (list_objects_v2) umożliwia ograniczenie odpowiedzi do kluczy rozpoczynających się od określonego prefiksu.
Aby ograniczyć elementy do elementów w określonych podfolderach:
import boto3 s3 = boto3.client("s3") response = s3.list_objects_v2( Bucket=BUCKET, Prefix ='DIR1/DIR2', MaxKeys=100 )
Dokumentacja
Inną opcją jest użycie funkcji os.path w języku Python do wyodrębnienia prefiksu folderu. Problem polega na tym, że będzie to wymagało wypisywania obiektów z niepożądanych katalogów.
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]
Przypomnienie o boto3: boto3.resource to fajny interfejs API wysokiego poziomu. Korzystanie z boto3.client w porównaniu z boto3.resource ma swoje zalety i wady. Jeśli tworzysz wewnętrzną bibliotekę współdzieloną, użycie boto3.resource zapewni warstwę czarnej skrzynki nad używanymi zasobami.
źródło
directory_name = os.path.dirname(directory/path/and/filename.txt)
ifile_name = os.path.basename(directory/path/and/filename.txt)
Poniższy fragment kodu zwraca TYLKO „podfoldery” w „folderze” z zasobnika 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')
Więcej informacji można znaleźć na stronie https://github.com/boto/boto3/issues/134
źródło
Zajęło mi to dużo czasu, aby się zorientować, ale w końcu oto prosty sposób na wyświetlenie zawartości podfolderu w zasobniku S3 przy użyciu boto3. Mam nadzieję, że to pomoże
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))
źródło
'/'
. Dzięki temu możesz pomijać „foldery” pełne obiektów bez konieczności ich stronicowania. A potem, nawet jeśli nalegasz na pełną listę (tj. „Rekurencyjny” odpowiednik w aws cli), to musisz użyć stronicowania, albo wymienisz tylko pierwsze 1000 obiektów.limit
do niego w mojej pochodnej odpowiedzi .Krótka odpowiedź :
Użyj
Delimiter='/'
. Pozwala to uniknąć rekurencyjnego wyświetlania twojego zasobnika. Niektóre odpowiedzi błędnie sugerują zrobienie pełnej listy i użycie pewnych operacji na łańcuchach znaków w celu pobrania nazw katalogów. To mogłoby być okropnie nieefektywne. Pamiętaj, że S3 praktycznie nie ma ograniczenia liczby obiektów, które może zawierać zasobnik. Wyobraź sobie więc, że międzybar/
afoo/
masz bilion obiektów: czekałeś bardzo długo, aby je zdobyć['bar/', 'foo/']
.Użyj
Paginators
. Z tego samego powodu (S3 jest przybliżeniem nieskończoności przez inżyniera), musisz wyświetlić listę stron i unikać przechowywania całej listy w pamięci. Zamiast tego potraktuj swój „lister” jako iterator i obsługuj generowany przez niego strumień.Użyj
boto3.client
, nieboto3.resource
. Wydaje się, żeresource
wersja nie radzi sobie dobrze z tąDelimiter
opcją. Jeśli masz zasób, powiedzmybucket = boto3.resource('s3').Bucket(name)
, można uzyskać odpowiedniego klienta z:bucket.meta.client
.Długa odpowiedź :
Poniżej znajduje się iterator, którego używam do prostych zasobników (bez obsługi wersji).
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 :
Poniższe informacje są pomocne w testowaniu zachowania
paginator
ilist_objects
. Tworzy szereg katalogów i plików. Ponieważ strony zawierają do 1000 wpisów, używamy wielokrotności tej dla katalogów i plików.dirs
zawiera tylko katalogi (każdy ma jeden obiekt).mixed
zawiera mieszankę katalogów i obiektów, ze stosunkiem 2 obiektów dla każdego katalogu (plus oczywiście jeden obiekt w katalogu; S3 przechowuje tylko obiekty).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())
Wynikowa struktura to:
./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
Po odrobinie przeróbki kodu podanego powyżej,
s3list
aby sprawdzić odpowiedzi zpaginator
, możesz zauważyć kilka zabawnych faktów:Marker
Jest naprawdę wykluczają. PodaneMarker=topdir + 'mixed/0500_foo_a'
spowoduje, że lista rozpocznie się po tym kluczu (zgodnie z API AmazonS3 ), tj.../mixed/0500_foo_b
. Od. To jest powód__prev_str()
.Używając
Delimiter
podczas listowaniamixed/
, każda odpowiedź zpaginator
zawiera 666 kluczy i 334 typowe prefiksy. Całkiem dobrze nie tworzy ogromnych odpowiedzi.Z kolei podczas wyświetlania listy
dirs/
każda odpowiedź z elementupaginator
zawiera 1000 wspólnych prefiksów (bez kluczy).Przekazywanie limitu w postaci
PaginationConfig={'MaxItems': limit}
ogranicza tylko liczbę kluczy, a nie wspólne przedrostki. Zajmujemy się tym przez dalsze obcinanie strumienia naszego iteratora.źródło
Z S3 wynika, że nie ma folderów / katalogów tylko klucze. Widoczna struktura folderów jest poprzedzany tylko do nazwy pliku , aby stać się „klucz”, tak aby wyświetlić zawartość
myBucket
„ssome/path/to/the/file/
można spróbować:s3 = boto3.client('s3') for obj in s3.list_objects_v2(Bucket="myBucket", Prefix="some/path/to/the/file/")['Contents']: print(obj['Key'])
co dałoby ci coś takiego:
źródło
Miałem ten sam problem, ale udało mi się go rozwiązać za pomocą
boto3.client
ilist_objects_v2
z parametramiBucket
iStartAfter
.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']
Wynik wyjściowy dla powyższego kodu będzie wyglądał następująco:
Boto3 list_objects_v2 Dokumentacja
Aby usunąć tylko nazwę katalogu dla
secondLevelFolder
, właśnie użyłem metody Pythonasplit()
: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]
Wynik wyjściowy dla powyższego kodu będzie wyglądał następująco:
Dokumentacja Python split ()
Jeśli chcesz uzyskać nazwę katalogu ORAZ nazwę elementu zawartości, zamień linię drukowania na następującą:
print "{}/{}".format(fileName[1], fileName[2])
Zostaną wyświetlone następujące informacje:
Mam nadzieję że to pomoże
źródło
U mnie działa ... Obiekty S3:
Za pomocą:
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']]
otrzymujemy:
Z:
resp = s3client.list_objects(Bucket=bucket, Prefix='form1/', Delimiter="/") sections = [x['Prefix'] for x in resp['CommonPrefixes']]
otrzymujemy:
źródło
Cli AWS robi to (prawdopodobnie bez pobierania i iterowania wszystkich kluczy w wiadrze) podczas uruchamiania
aws s3 ls s3://my-bucket/
, więc pomyślałem, że musi być sposób na użycie boto3.https://github.com/aws/aws-cli/blob/0fedc4c1b6a7aee13e2ed10c3ada778c702c22c3/awscli/customizations/s3/subcommands.py#L499
Wygląda na to, że rzeczywiście używają Prefiksu i Ogranicznika - udało mi się napisać funkcję, która dostarczyłaby mi wszystkie katalogi na poziomie głównym wiadra, modyfikując nieco ten kod:
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
źródło
Oto możliwe rozwiązanie:
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']]
źródło
Za pomocą
boto3.resource
Opiera się to na odpowiedzi itz-azhar dotyczącej zastosowania opcjonalnego
limit
. Jest oczywiście znacznie prostszy w użyciu niżboto3.client
wersja.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)
Za pomocą
boto3.client
Wykorzystuje
list_objects_v2
i rozwija odpowiedź CpILL, aby umożliwić pobranie ponad 1000 obiektów.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)
źródło
Po pierwsze, w S3 nie ma prawdziwej koncepcji folderów. Na pewno możesz mieć plik @
'/folder/subfolder/myfile.txt'
bez folderu ani podfolderu.Aby „zasymulować” folder w S3, należy utworzyć pusty plik z „/” na końcu jego nazwy (zobacz boto Amazon S3 - jak utworzyć folder? )
W przypadku swojego problemu prawdopodobnie powinieneś użyć metody
get_all_keys
z dwoma parametrami:prefix
idelimiter
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ódło
list
zprefix
idelimiter
. Myślę, że to powinno działać.Wiem, że omawiamy tutaj temat boto3, ale uważam, że zwykle szybsze i bardziej intuicyjne jest po prostu użycie awscli do czegoś takiego - awscli zachowuje więcej możliwości niż boto3 ze względu na swoją wartość.
Na przykład, jeśli mam obiekty zapisane w „podfolderach” powiązanych z danym zasobnikiem, mogę je wszystkie wymienić za pomocą czegoś takiego:
Możemy więc wyobrazić sobie „ścieżkę bezwzględną” prowadzącą do tych obiektów: „moje dane / f1 / f2 / f3 / foo2.csv” ...
Używając poleceń awscli, możemy łatwo wyświetlić listę wszystkich obiektów wewnątrz danego „podfolderu” poprzez:
źródło
Poniżej znajduje się fragment kodu, który może obsłużyć paginację, jeśli próbujesz pobrać dużą liczbę obiektów zasobnika 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
źródło
Jeśli chodzi o Boto 1.13.3, okazuje się, że jest to takie proste (jeśli pominiesz wszystkie kwestie dotyczące stronicowania, które zostały omówione w innych odpowiedziach):
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']]
źródło