Estoy usando boto3 para obtener archivos del depósito s3. Necesito una funcionalidad similar comoaws s3 sync
Mi código actual es
#!/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'])
Esto funciona bien, siempre que el depósito solo tenga archivos. Si hay una carpeta dentro del depósito, está arrojando un error
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'
¿Es esta una forma adecuada de descargar un bucket de s3 completo usando boto3. Cómo descargar carpetas.
Respuestas:
Cuando se trabaja con depósitos que tienen más de 1000 objetos, es necesario implementar una solución que utilice
NextContinuationToken
conjuntos secuenciales de, como máximo, 1000 claves. Esta solución primero compila una lista de objetos, luego crea iterativamente los directorios especificados y descarga los objetos existentes.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)
fuente
while next_token is not None:
Tengo las mismas necesidades y creé la siguiente función que descarga los archivos de forma recursiva.
Los directorios se crean localmente solo si contienen archivos.
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)
La función se llama de esa manera:
def _start(): client = boto3.client('s3') resource = boto3.resource('s3') download_dir(client, resource, 'clientconf/', '/tmp', bucket='my-bucket')
fuente
resource.meta.client
.OSError: [Errno 21] Is a directory
así que envié la llamada a download_fileif not file.get('Key').endswith('/')
para resolverlo. Gracias @glefait y @Shanaws s3 sync
disponible en la biblioteca boto3?dist
aquí?Amazon S3 no tiene carpetas / directorios. Es una estructura de archivo plana .
Para mantener la apariencia de los directorios, los nombres de las rutas se almacenan como parte de la clave del objeto (nombre de archivo). Por ejemplo:
images/foo.jpg
En este caso, la clave completa es
images/foo.jpg
, en lugar de solofoo.jpg
.Sospecho que su problema es que
boto
está devolviendo un archivo llamadomy_folder/.8Df54234
y está intentando guardarlo en el sistema de archivos local. Sin embargo, su sistema de archivos local interpreta lamy_folder/
parte como un nombre de directorio y ese directorio no existe en su sistema de archivos local .Puede truncar el nombre del archivo para guardar solo la
.8Df54234
parte, o tendría que crear los directorios necesarios antes de escribir archivos. Tenga en cuenta que podrían ser directorios anidados de varios niveles.Una forma más sencilla sería utilizar la AWS Command-Line Interface (CLI) , que hará todo este trabajo por usted, por ejemplo:
También hay una
sync
opción que solo copiará archivos nuevos y modificados.fuente
aws s3 sync
. ¿Es posible en boto3.foo/bar.txt
), usted sería responsable de crear el directorio (foo
) antes de llamars3.download_file
. No es una capacidad automática deboto
.s3.list_objects(Bucket='my_bucket_name')['Contents']
y filtrar las claves de carpeta y crearlas.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)
fuente
os.makedirs(path)
y luego el destino de descarga debería serobject.key
.Actualmente estoy logrando la tarea, usando lo siguiente
#!/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)
Aunque hace el trabajo, no estoy seguro de que sea bueno hacerlo de esta manera. Lo dejo aquí para ayudar a otros usuarios y obtener más respuestas, con una mejor manera de lograrlo.
fuente
Más vale tarde que nunca :) La respuesta anterior con paginator es realmente buena. Sin embargo, es recursivo y puede terminar alcanzando los límites de recursividad de Python. Aquí hay un enfoque alternativo, con un par de comprobaciones adicionales.
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')
fuente
KeyError: 'Contents'
. ruta de entrada'/arch/R/storeincomelogs/
, ruta completa/arch/R/storeincomelogs/201901/01/xxx.parquet
.Tengo una solución para esto que ejecuta la AWS CLI en el mismo proceso.
Instalar
awscli
como python lib:Luego defina esta función:
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)
Ejecutar:
aws_cli('s3', 'sync', '/path/to/source', 's3://bucket/destination', '--delete')
fuente
sync
comando, y más bien simplemente ejecutando el comandoaws s3 cp s3://{bucket}/{folder} {local_folder} --recursive
. Tiempos reducidos de minutos (casi 1h) a literalmente segundoslogging.basicConfig(format='%(levelname)s: %(message)s', level=logging.WARNING) logger = logging.getLogger()
y solo quiero que los registros se generen desde la raíz. ¿Algunas ideas?Es una muy mala idea obtener todos los archivos de una sola vez, debería hacerlo por lotes.
Una implementación que utilizo para buscar una carpeta en particular (directorio) de S3 es,
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])
y aún así, si desea obtener todo el depósito, úselo a través de CIL como @John Rotenstein mencionó a continuación,
fuente
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')
Este código descargará el contenido en el
/tmp/
directorio. Si lo desea, puede cambiar el directorio.fuente
Si desea llamar a un script de bash usando Python, aquí hay un método simple para cargar un archivo desde una carpeta en el depósito S3 a una carpeta local (en una máquina Linux):
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")
fuente
Obtuve el requisito similar y obtuve ayuda al leer algunas de las soluciones anteriores y en otros sitios web, se me ocurrió el siguiente script, solo quería compartir si podría ayudar a alguien.
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)
fuente
Reposicionando la respuesta de @glefait con una condición if al final para evitar el error 20 del sistema operativo. La primera clave que obtiene es el nombre de la carpeta, que no se puede escribir en la ruta de destino.
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
fuente
Me he estado encontrando con este problema durante un tiempo y con todos los foros diferentes que he visitado no he visto un resumen completo de lo que funciona. Entonces, seguí adelante y tomé todas las piezas (agregué algunas cosas por mi cuenta) y ¡he creado un S3 Downloader completo de extremo a extremo!
Esto no solo descargará archivos automáticamente, sino que si los archivos S3 están en subdirectorios, los creará en el almacenamiento local. En el caso de mi aplicación, necesito establecer permisos y propietarios, así que también lo agregué (se puede comentar si no es necesario).
Esto ha sido probado y funciona en un entorno Docker (K8) pero he agregado las variables ambientales en el script en caso de que quiera probarlo / ejecutarlo localmente.
Espero que esto ayude a alguien en su búsqueda por encontrar la automatización de descargas de S3. También agradezco cualquier consejo, información, etc. sobre cómo se puede optimizar mejor si es necesario.
#!/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()
fuente
Desde AWS S3 Docs (¿Cómo uso carpetas en un bucket de S3?):
Para descargar todos los archivos de "mybucket" en el directorio actual respetando la estructura de directorio emulado del depósito (creando las carpetas del depósito si aún no existen localmente):
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)
fuente