example - python s3 bucket
Boto3 para descargar todos los archivos de un S3 Bucket (10)
Estoy usando boto3 para obtener archivos del cubo s3.
Necesito una funcionalidad similar como
aws 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 cubo, arroja 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 completo de s3 usando boto3? Cómo descargar carpetas.
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 otras respuestas, con una mejor manera de lograr esto
Amazon S3 no tiene carpetas / directorios. Es una estructura de archivo plano .
Para mantener la apariencia de los directorios, los nombres de ruta 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 solo
foo.jpg
.
Sospecho que su problema es que
boto
está devolviendo un archivo llamado
my_folder/.8Df54234
e intenta guardarlo en el sistema de archivos local.
Sin embargo, su sistema de archivos local interpreta
my_folder/
porción como un nombre de directorio, y
ese directorio no existe en su sistema de archivos local
.
Puede
truncar
el nombre de archivo para guardar solo la porción
.8Df54234
, 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 fácil sería utilizar la interfaz de línea de comandos (CLI) de AWS , que hará todo este trabajo por usted, por ejemplo:
aws s3 cp --recursive s3://my_bucket_name local_folder
También hay una opción de
sync
que solo copiará archivos nuevos y modificados.
Cuando se trabaja con cubos que tienen más de 1000 objetos, es necesario implementar una solución que use
NextContinuationToken
en 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)
Es una muy mala idea obtener todos los archivos de una vez, más bien debería obtenerlos en lotes.
Una implementación que uso para recuperar una carpeta (directorio) particular 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 el cubo completo, share través de CIL como share continuación,
aws s3 cp --recursive s3://bucket_name download_path
Más vale tarde que nunca :) La respuesta anterior con paginator es realmente buena. Sin embargo, es recursivo y podrías terminar alcanzando los límites de recursión de Python. Aquí hay un enfoque alternativo, con un par de controles 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'')
Si desea llamar a un script bash usando python, aquí hay un método simple para cargar un archivo desde una carpeta en el cubo 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")
Tengo las mismas necesidades y creé la siguiente función que descarga recursivamente los archivos.
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 así:
def _start():
client = boto3.client(''s3'')
resource = boto3.resource(''s3'')
download_dir(client, resource, ''clientconf/'', ''/tmp'', bucket=''my-bucket'')
Tengo una solución alternativa para esto que ejecuta AWS CLI en el mismo proceso.
Instale
awscli
como python lib:
pip install awscli
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'')
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 directorio
/tmp/
.
Si quieres puedes cambiar el directorio.
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)