Ich benutze boto3, um Dateien aus dem s3-Bucket zu bekommen. Ich brauche eine ähnliche Funktionalität wieaws s3 sync
Mein aktueller Code ist
#!/usr/bin/python
import boto3
s3=boto3.client('s3')
list=s3.list_objects(Bucket='my_bucket_name')['Contents']
for key in list:
s3.download_file('my_bucket_name', key['Key'], key['Key'])
Dies funktioniert einwandfrei, solange der Bucket nur Dateien enthält. Wenn sich ein Ordner im Bucket befindet, wird ein Fehler ausgegeben
Traceback (most recent call last):
File "./test", line 6, in <module>
s3.download_file('my_bucket_name', key['Key'], key['Key'])
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/inject.py", line 58, in download_file
extra_args=ExtraArgs, callback=Callback)
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 651, in download_file
extra_args, callback)
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 666, in _download_file
self._get_object(bucket, key, filename, extra_args, callback)
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 690, in _get_object
extra_args, callback)
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 707, in _do_get_object
with self._osutil.open(filename, 'wb') as f:
File "/usr/local/lib/python2.7/dist-packages/boto3/s3/transfer.py", line 323, in open
return open(filename, mode)
IOError: [Errno 2] No such file or directory: 'my_folder/.8Df54234'
Ist dies ein geeigneter Weg, um einen kompletten s3-Bucket mit boto3 herunterzuladen? So laden Sie Ordner herunter.
Antworten:
Wenn Sie mit Buckets mit mehr als 1000 Objekten arbeiten, müssen Sie eine Lösung implementieren, die die
NextContinuationToken
aufeinander folgenden Sätze von höchstens 1000 Schlüsseln verwendet. Diese Lösung erstellt zuerst eine Liste von Objekten, erstellt dann iterativ die angegebenen Verzeichnisse und lädt die vorhandenen Objekte herunter.import boto3 import os s3_client = boto3.client('s3') def download_dir(prefix, local, bucket, client=s3_client): """ params: - prefix: pattern to match in s3 - local: local path to folder in which to place files - bucket: s3 bucket with target contents - client: initialized s3 client object """ keys = [] dirs = [] next_token = '' base_kwargs = { 'Bucket':bucket, 'Prefix':prefix, } while next_token is not None: kwargs = base_kwargs.copy() if next_token != '': kwargs.update({'ContinuationToken': next_token}) results = client.list_objects_v2(**kwargs) contents = results.get('Contents') for i in contents: k = i.get('Key') if k[-1] != '/': keys.append(k) else: dirs.append(k) next_token = results.get('NextContinuationToken') for d in dirs: dest_pathname = os.path.join(local, d) if not os.path.exists(os.path.dirname(dest_pathname)): os.makedirs(os.path.dirname(dest_pathname)) for k in keys: dest_pathname = os.path.join(local, k) if not os.path.exists(os.path.dirname(dest_pathname)): os.makedirs(os.path.dirname(dest_pathname)) client.download_file(bucket, k, dest_pathname)
quelle
while next_token is not None:
Ich habe die gleichen Anforderungen und habe die folgende Funktion erstellt, mit der die Dateien rekursiv heruntergeladen werden.
Die Verzeichnisse werden nur dann lokal erstellt, wenn sie Dateien enthalten.
import boto3 import os def download_dir(client, resource, dist, local='/tmp', bucket='your_bucket'): paginator = client.get_paginator('list_objects') for result in paginator.paginate(Bucket=bucket, Delimiter='/', Prefix=dist): if result.get('CommonPrefixes') is not None: for subdir in result.get('CommonPrefixes'): download_dir(client, resource, subdir.get('Prefix'), local, bucket) for file in result.get('Contents', []): dest_pathname = os.path.join(local, file.get('Key')) if not os.path.exists(os.path.dirname(dest_pathname)): os.makedirs(os.path.dirname(dest_pathname)) resource.meta.client.download_file(bucket, file.get('Key'), dest_pathname)
Die Funktion heißt so:
def _start(): client = boto3.client('s3') resource = boto3.resource('s3') download_dir(client, resource, 'clientconf/', '/tmp', bucket='my-bucket')
quelle
resource.meta.client
.OSError: [Errno 21] Is a directory
also habe ich den Aufruf von download_file mit aufgelöstif not file.get('Key').endswith('/')
. Vielen Dank, dass Sie @glefait und @Shanaws s3 sync
es in der boto3-Bibliothek nicht ein Äquivalent zum Befehl aws-cli ?dist
hierAmazon S3 verfügt nicht über Ordner / Verzeichnisse. Es ist eine flache Dateistruktur .
Um das Erscheinungsbild von Verzeichnissen beizubehalten, werden Pfadnamen als Teil des Objektschlüssels (Dateiname) gespeichert . Zum Beispiel:
images/foo.jpg
In diesem Fall ist der gesamte Schlüssel
images/foo.jpg
nicht nurfoo.jpg
.Ich vermute, dass Ihr Problem darin besteht, dass
boto
eine aufgerufene Datei zurückgegeben wirdmy_folder/.8Df54234
und versucht wird, sie im lokalen Dateisystem zu speichern. Ihr lokales Dateisystem interpretiert denmy_folder/
Teil jedoch als Verzeichnisnamen, und dieses Verzeichnis ist in Ihrem lokalen Dateisystem nicht vorhanden .Sie könnten entweder den Dateinamen abschneiden, um nur den
.8Df54234
Teil zu speichern , oder Sie müssten die erforderlichen Verzeichnisse erstellen, bevor Sie Dateien schreiben. Beachten Sie, dass es sich um mehrstufige verschachtelte Verzeichnisse handeln kann.Eine einfachere Möglichkeit wäre die Verwendung der AWS-Befehlszeilenschnittstelle (CLI) , die all diese Arbeiten für Sie erledigt, z.
Es gibt auch eine
sync
Option, die nur neue und geänderte Dateien kopiert.quelle
aws s3 sync
. Ist es in boto3 möglich.foo/bar.txt
), sind Sie dafür verantwortlich, das Verzeichnis (foo
) vor dem Aufruf zu erstellens3.download_file
. Es ist keine automatische Fähigkeit vonboto
.s3.list_objects(Bucket='my_bucket_name')['Contents']
Ordnerschlüsseln suchen, diese filtern und erstellen.import os import boto3 #initiate s3 resource s3 = boto3.resource('s3') # select bucket my_bucket = s3.Bucket('my_bucket_name') # download file into current directory for s3_object in my_bucket.objects.all(): # Need to split s3_object.key into path and file name, else it will give error file not found. path, filename = os.path.split(s3_object.key) my_bucket.download_file(s3_object.key, filename)
quelle
os.makedirs(path)
, und dann sollte das Download-Ziel seinobject.key
.Ich erreiche gerade die Aufgabe, indem ich Folgendes verwende
#!/usr/bin/python import boto3 s3=boto3.client('s3') list=s3.list_objects(Bucket='bucket')['Contents'] for s3_key in list: s3_object = s3_key['Key'] if not s3_object.endswith("/"): s3.download_file('bucket', s3_object, s3_object) else: import os if not os.path.exists(s3_object): os.makedirs(s3_object)
Obwohl es den Job macht, bin ich mir nicht sicher, ob es gut ist, dies zu tun. Ich lasse es hier, um anderen Benutzern und weiteren Antworten zu helfen, um dies besser zu erreichen
quelle
Besser spät als nie :) Die vorherige Antwort mit Paginator ist wirklich gut. Es ist jedoch rekursiv und es kann vorkommen, dass Sie die Rekursionsgrenzen von Python erreichen. Hier ist ein alternativer Ansatz mit ein paar zusätzlichen Überprüfungen.
import os import errno import boto3 def assert_dir_exists(path): """ Checks if directory tree in path exists. If not it created them. :param path: the path to check if it exists """ try: os.makedirs(path) except OSError as e: if e.errno != errno.EEXIST: raise def download_dir(client, bucket, path, target): """ Downloads recursively the given S3 path to the target directory. :param client: S3 client to use. :param bucket: the name of the bucket to download from :param path: The S3 directory to download. :param target: the local directory to download the files to. """ # Handle missing / at end of prefix if not path.endswith('/'): path += '/' paginator = client.get_paginator('list_objects_v2') for result in paginator.paginate(Bucket=bucket, Prefix=path): # Download each file individually for key in result['Contents']: # Calculate relative path rel_path = key['Key'][len(path):] # Skip paths ending in / if not key['Key'].endswith('/'): local_file_path = os.path.join(target, rel_path) # Make sure directories exist local_file_dir = os.path.dirname(local_file_path) assert_dir_exists(local_file_dir) client.download_file(bucket, key['Key'], local_file_path) client = boto3.client('s3') download_dir(client, 'bucket-name', 'path/to/data', 'downloads')
quelle
KeyError: 'Contents'
. Eingabepfad'/arch/R/storeincomelogs/
, vollständiger Pfad/arch/R/storeincomelogs/201901/01/xxx.parquet
.Ich habe eine Problemumgehung dafür, die die AWS-CLI im selben Prozess ausführt.
awscli
Als Python-Bibliothek installieren :Dann definieren Sie diese Funktion:
from awscli.clidriver import create_clidriver def aws_cli(*cmd): old_env = dict(os.environ) try: # Environment env = os.environ.copy() env['LC_CTYPE'] = u'en_US.UTF' os.environ.update(env) # Run awscli in the same process exit_code = create_clidriver().main(*cmd) # Deal with problems if exit_code > 0: raise RuntimeError('AWS CLI exited with code {}'.format(exit_code)) finally: os.environ.clear() os.environ.update(old_env)
Ausführen:
aws_cli('s3', 'sync', '/path/to/source', 's3://bucket/destination', '--delete')
quelle
sync
Befehl zu verwenden und den Befehl einfach auszuführenaws s3 cp s3://{bucket}/{folder} {local_folder} --recursive
. Die Zeiten wurden von Minuten (fast 1logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.WARNING) logger = logging.getLogger()
und möchte nur, dass Protokolle von root ausgegeben werden. Irgendwelche Ideen?Es ist eine sehr schlechte Idee, alle Dateien auf einmal abzurufen. Sie sollten sie lieber stapelweise abrufen.
Eine Implementierung, mit der ich einen bestimmten Ordner (Verzeichnis) aus S3 abrufe, ist:
def get_directory(directory_path, download_path, exclude_file_names): # prepare session session = Session(aws_access_key_id, aws_secret_access_key, region_name) # get instances for resource and bucket resource = session.resource('s3') bucket = resource.Bucket(bucket_name) for s3_key in self.client.list_objects(Bucket=self.bucket_name, Prefix=directory_path)['Contents']: s3_object = s3_key['Key'] if s3_object not in exclude_file_names: bucket.download_file(file_path, download_path + str(s3_object.split('/')[-1])
und trotzdem, wenn Sie den ganzen Eimer bekommen möchten, verwenden Sie ihn über CIL als @John Rotenstein, wie unten erwähnt,
quelle
for objs in my_bucket.objects.all(): print(objs.key) path='/tmp/'+os.sep.join(objs.key.split(os.sep)[:-1]) try: if not os.path.exists(path): os.makedirs(path) my_bucket.download_file(objs.key, '/tmp/'+objs.key) except FileExistsError as fe: print(objs.key+' exists')
Dieser Code lädt den Inhalt im
/tmp/
Verzeichnis herunter . Wenn Sie möchten, können Sie das Verzeichnis ändern.quelle
Wenn Sie ein Bash-Skript mit Python aufrufen möchten, können Sie eine Datei auf einfache Weise aus einem Ordner im S3-Bucket in einen lokalen Ordner (auf einem Linux-Computer) laden:
import boto3 import subprocess import os ###TOEDIT### my_bucket_name = "your_my_bucket_name" bucket_folder_name = "your_bucket_folder_name" local_folder_path = "your_local_folder_path" ###TOEDIT### # 1.Load thes list of files existing in the bucket folder FILES_NAMES = [] s3 = boto3.resource('s3') my_bucket = s3.Bucket('{}'.format(my_bucket_name)) for object_summary in my_bucket.objects.filter(Prefix="{}/".format(bucket_folder_name)): # print(object_summary.key) FILES_NAMES.append(object_summary.key) # 2.List only new files that do not exist in local folder (to not copy everything!) new_filenames = list(set(FILES_NAMES )-set(os.listdir(local_folder_path))) # 3.Time to load files in your destination folder for new_filename in new_filenames: upload_S3files_CMD = """aws s3 cp s3://{}/{}/{} {}""".format(my_bucket_name,bucket_folder_name,new_filename ,local_folder_path) subprocess_call = subprocess.call([upload_S3files_CMD], shell=True) if subprocess_call != 0: print("ALERT: loading files not working correctly, please re-check new loaded files")
quelle
Ich habe die ähnliche Anforderung erhalten und Hilfe beim Lesen einiger der oben genannten Lösungen erhalten. Auf anderen Websites habe ich mir das folgende Skript ausgedacht. Ich wollte nur mitteilen, ob es jemandem helfen könnte.
from boto3.session import Session import os def sync_s3_folder(access_key_id,secret_access_key,bucket_name,folder,destination_path): session = Session(aws_access_key_id=access_key_id,aws_secret_access_key=secret_access_key) s3 = session.resource('s3') your_bucket = s3.Bucket(bucket_name) for s3_file in your_bucket.objects.all(): if folder in s3_file.key: file=os.path.join(destination_path,s3_file.key.replace('/','\\')) if not os.path.exists(os.path.dirname(file)): os.makedirs(os.path.dirname(file)) your_bucket.download_file(s3_file.key,file) sync_s3_folder(access_key_id,secret_access_key,bucket_name,folder,destination_path)
quelle
Reposting @glefaits Antwort mit einer if-Bedingung am Ende, um OS-Fehler 20 zu vermeiden. Der erste Schlüssel, den es erhält, ist der Ordnername selbst, der nicht in den Zielpfad geschrieben werden kann.
def download_dir(client, resource, dist, local='/tmp', bucket='your_bucket'): paginator = client.get_paginator('list_objects') for result in paginator.paginate(Bucket=bucket, Delimiter='/', Prefix=dist): if result.get('CommonPrefixes') is not None: for subdir in result.get('CommonPrefixes'): download_dir(client, resource, subdir.get('Prefix'), local, bucket) for file in result.get('Contents', []): print("Content: ",result) dest_pathname = os.path.join(local, file.get('Key')) print("Dest path: ",dest_pathname) if not os.path.exists(os.path.dirname(dest_pathname)): print("here last if") os.makedirs(os.path.dirname(dest_pathname)) print("else file key: ", file.get('Key')) if not file.get('Key') == dist: print("Key not equal? ",file.get('Key')) resource.meta.client.download_file(bucket, file.get('Key'), dest_pathname)enter code here
quelle
Ich bin schon eine Weile auf dieses Problem gestoßen und mit all den verschiedenen Foren, die ich durchlaufen habe, habe ich keinen vollständigen Überblick darüber gesehen, was funktioniert. Also habe ich alle Teile genommen (einige Sachen selbst hinzugefügt) und einen vollständigen End-to-End-S3-Downloader erstellt!
Dadurch werden Dateien nicht nur automatisch heruntergeladen, sondern wenn sich die S3-Dateien in Unterverzeichnissen befinden, werden sie im lokalen Speicher erstellt. In der Instanz meiner Anwendung muss ich Berechtigungen und Eigentümer festlegen, damit ich dies ebenfalls hinzugefügt habe (kann kommentiert werden, wenn es nicht benötigt wird).
Dies wurde getestet und funktioniert in einer Docker-Umgebung (K8), aber ich habe die Umgebungsvariablen im Skript hinzugefügt, nur für den Fall, dass Sie es lokal testen / ausführen möchten.
Ich hoffe, dies hilft jemandem bei der Suche nach S3-Download-Automatisierung. Ich freue mich auch über Ratschläge, Informationen usw., wie dies bei Bedarf besser optimiert werden kann.
#!/usr/bin/python3 import gc import logging import os import signal import sys import time from datetime import datetime import boto from boto.exception import S3ResponseError from pythonjsonlogger import jsonlogger formatter = jsonlogger.JsonFormatter('%(message)%(levelname)%(name)%(asctime)%(filename)%(lineno)%(funcName)') json_handler_out = logging.StreamHandler() json_handler_out.setFormatter(formatter) #Manual Testing Variables If Needed #os.environ["DOWNLOAD_LOCATION_PATH"] = "some_path" #os.environ["BUCKET_NAME"] = "some_bucket" #os.environ["AWS_ACCESS_KEY"] = "some_access_key" #os.environ["AWS_SECRET_KEY"] = "some_secret" #os.environ["LOG_LEVEL_SELECTOR"] = "DEBUG, INFO, or ERROR" #Setting Log Level Test logger = logging.getLogger('json') logger.addHandler(json_handler_out) logger_levels = { 'ERROR' : logging.ERROR, 'INFO' : logging.INFO, 'DEBUG' : logging.DEBUG } logger_level_selector = os.environ["LOG_LEVEL_SELECTOR"] logger.setLevel(logger_level_selector) #Getting Date/Time now = datetime.now() logger.info("Current date and time : ") logger.info(now.strftime("%Y-%m-%d %H:%M:%S")) #Establishing S3 Variables and Download Location download_location_path = os.environ["DOWNLOAD_LOCATION_PATH"] bucket_name = os.environ["BUCKET_NAME"] aws_access_key_id = os.environ["AWS_ACCESS_KEY"] aws_access_secret_key = os.environ["AWS_SECRET_KEY"] logger.debug("Bucket: %s" % bucket_name) logger.debug("Key: %s" % aws_access_key_id) logger.debug("Secret: %s" % aws_access_secret_key) logger.debug("Download location path: %s" % download_location_path) #Creating Download Directory if not os.path.exists(download_location_path): logger.info("Making download directory") os.makedirs(download_location_path) #Signal Hooks are fun class GracefulKiller: kill_now = False def __init__(self): signal.signal(signal.SIGINT, self.exit_gracefully) signal.signal(signal.SIGTERM, self.exit_gracefully) def exit_gracefully(self, signum, frame): self.kill_now = True #Downloading from S3 Bucket def download_s3_bucket(): conn = boto.connect_s3(aws_access_key_id, aws_access_secret_key) logger.debug("Connection established: ") bucket = conn.get_bucket(bucket_name) logger.debug("Bucket: %s" % str(bucket)) bucket_list = bucket.list() # logger.info("Number of items to download: {0}".format(len(bucket_list))) for s3_item in bucket_list: key_string = str(s3_item.key) logger.debug("S3 Bucket Item to download: %s" % key_string) s3_path = download_location_path + "/" + key_string logger.debug("Downloading to: %s" % s3_path) local_dir = os.path.dirname(s3_path) if not os.path.exists(local_dir): logger.info("Local directory doesn't exist, creating it... %s" % local_dir) os.makedirs(local_dir) logger.info("Updating local directory permissions to %s" % local_dir) #Comment or Uncomment Permissions based on Local Usage os.chmod(local_dir, 0o775) os.chown(local_dir, 60001, 60001) logger.debug("Local directory for download: %s" % local_dir) try: logger.info("Downloading File: %s" % key_string) s3_item.get_contents_to_filename(s3_path) logger.info("Successfully downloaded File: %s" % s3_path) #Updating Permissions logger.info("Updating Permissions for %s" % str(s3_path)) #Comment or Uncomment Permissions based on Local Usage os.chmod(s3_path, 0o664) os.chown(s3_path, 60001, 60001) except (OSError, S3ResponseError) as e: logger.error("Fatal error in s3_item.get_contents_to_filename", exc_info=True) # logger.error("Exception in file download from S3: {}".format(e)) continue logger.info("Deleting %s from S3 Bucket" % str(s3_item.key)) s3_item.delete() def main(): killer = GracefulKiller() while not killer.kill_now: logger.info("Checking for new files on S3 to download...") download_s3_bucket() logger.info("Done checking for new files, will check in 120s...") gc.collect() sys.stdout.flush() time.sleep(120) if __name__ == '__main__': main()
quelle
Aus AWS S3-Dokumenten (Wie verwende ich Ordner in einem S3-Bucket?):
So laden Sie alle Dateien von "mybucket" unter Berücksichtigung der emulierten Verzeichnisstruktur des Buckets in das aktuelle Verzeichnis herunter (Erstellen der Ordner aus dem Bucket, falls sie nicht bereits lokal vorhanden sind):
import boto3 import os bucket_name = "mybucket" s3 = boto3.client("s3") objects = s3.list_objects(Bucket = bucket_name)["Contents"] for s3_object in objects: s3_key = s3_object["Key"] path, filename = os.path.split(s3_key) if len(path) != 0 and not os.path.exists(path): os.makedirs(path) if not s3_key.endswith("/"): download_to = path + '/' + filename if path else filename s3.download_file(bucket_name, s3_key, download_to)
quelle