Boto3 для загрузки всех файлов из корзины S3
Я использую boto3 для получения файлов из корзины s3. Мне нужна подобная функциональность, как aws s3 sync
Мой текущий код
#!/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'])
Это работает нормально, пока в ведре есть только файлы. Если папка присутствует внутри корзины, то она выдает ошибку
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'
Это правильный способ загрузить полное ведро s3 с помощью boto3. Как загружать папки.
8 ответов:
Я получил те же потребности и создал следующую функцию, которая рекурсивно загружает файлы. Каталоги создаются локально только в том случае, если они содержат файлы.
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) if result.get('Contents') is not None: for file in result.get('Contents'): if not os.path.exists(os.path.dirname(local + os.sep + file.get('Key'))): os.makedirs(os.path.dirname(local + os.sep + file.get('Key'))) resource.meta.client.download_file(bucket, file.get('Key'), local + os.sep + file.get('Key'))
Функция вызывается следующим образом:
def _start(): client = boto3.client('s3') resource = boto3.resource('s3') download_dir(client, resource, 'clientconf/', '/tmp')
В Amazon S3 нет папок / каталогов. Этоплоская файловая структура .
Для сохранения внешнего вида каталогов имена путей хранятся как часть ключа объекта (filename). Например:
images/foo.jpg
В этом случае весь ключ-это
Я подозреваю, что ваша проблема заключается в том, чтоimages/foo.jpg
, а не простоfoo.jpg
.boto
возвращает файл с именемmy_folder/.8Df54234
и пытается сохранить его в локальной файловой системе. Однако, ваш локальная файловая система интерпретирует частьmy_folder/
как имя каталога, и этот каталог не существует в вашей локальной файловой системе.Вы можете либо усечь имя файла, чтобы сохранить только часть
.8Df54234
, либо вам придется создать необходимые каталоги Перед записью файлов. Обратите внимание, что это могут быть многоуровневые вложенные каталоги.Более простым способом было бы использовать интерфейс командной строки AWS (CLI), который сделает все эта работа для вас, например:
aws s3 cp --recursive s3://my_bucket_name local_folder
Есть также опция
sync
, которая будет копировать только новые и измененные файлы.
import os import boto3 #intiate s3 resource s3 = boto3.resource('s3') # select bucket my_bucket = s3.Bucket('my_bucket_name') # download file into current directory for object in my_bucket.objects.all(): # Need to split object.key into path and file name, else it will give error file not found. path, filename = os.path.split(obj.key) my_bucket.download_file(object.key, filename))
В настоящее время я решаю эту задачу, используя следующие
#!/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)
Хотя, это делает работу, я не уверен, что это хорошо делать таким образом. Я оставляю его здесь, чтобы помочь другим пользователям и дальнейшим ответам, с лучшим способом достижения этого
Лучше поздно, чем никогда:) предыдущий ответ с paginator действительно хорош. Однако это рекурсивно, и вы можете в конечном итоге попасть в пределы рекурсии Python. Вот альтернативный подход, с парой дополнительных проверок.
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')
Это очень плохая идея, чтобы получить все файлы за один раз, вы должны скорее получить его в пакетах.
Одна реализация, которую я использую для извлечения определенной папки (каталога) из S3,
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])
И все же, если вы хотите получить целое ведро, используйте его через CIL, как @John Rotenstein упоминал, как показано ниже,
aws s3 cp --recursive s3://bucket_name download_path
У меня есть обходной путь для этого, который запускает AWS CLI в том же процессе.
Установить
awscli
как Python lib:pip install awscli
Затем определите эту функцию:
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)
Для выполнения:
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')
Этот код загрузит содержимое в каталог
/tmp/
. Если вы хотите, вы можете изменить каталог.