it-swarm.com.ru

Boto3 для загрузки всех файлов из S3 Bucket

Я использую Boto3, чтобы получить файлы из S3 Bucket. Мне нужен подобный функционал, как 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. Как скачать папки.

43
Shan

Я получил те же самые потребности и создал следующую функцию, которая рекурсивно загружает файлы . Каталоги создаются локально, только если они содержат файлы.

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)

Функция называется так:

def _start():
    client = boto3.client('s3')
    resource = boto3.resource('s3')
    download_dir(client, resource, 'clientconf/', '/tmp')
53
glefait

Amazon S3 не имеет папок/каталогов. Это плоская файловая структура.

Чтобы сохранить внешний вид каталогов, имена path хранятся как часть объекта Key (имя файла). Например:

  • images/foo.jpg

В этом случае весь ключ - это images/foo.jpg, а не foo.jpg.

Я подозреваю, что ваша проблема в том, что boto возвращает файл с именем my_folder/.8Df54234 и пытается сохранить его в локальной файловой системе. Однако ваша локальная файловая система интерпретирует часть my_folder/ как имя каталога, а этот каталог не существует в вашей локальной файловой системе.

Вы можете либо truncate имя файла сохранить только часть .8Df54234, либо вам придется создать необходимые каталоги перед записью файлов. Обратите внимание, что это могут быть многоуровневые вложенные каталоги.

Более простым способом было бы использовать Интерфейс командной строки AWS (CLI) , который сделает всю эту работу за вас, например:

aws s3 cp --recursive s3://my_bucket_name local_folder

Также есть опция sync, которая будет копировать только новые и измененные файлы.

33
John Rotenstein
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)
23
Tushar Niras

В настоящее время я достигаю этой цели, используя следующие

#!/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)

Хотя, это делает работу, я не уверен, что это хорошо делать так. Я оставляю это здесь, чтобы помочь другим пользователям и дальнейшие ответы, с лучшими способами достижения этого

10
Shan

Лучше поздно, чем никогда :) Предыдущий ответ с 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')
7
ifoukarakis

Это очень плохая идея, чтобы получить все файлы за один раз, вы должны получить его в пакетном режиме.

Одна из реализаций, которую я использую для извлечения определенной папки (каталога) из 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
1
Ganatra

У меня есть обходной путь, который запускает интерфейс командной строки AWS в том же процессе. 

Установите 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')
1
mattalxndr
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/. Если вы хотите, вы можете изменить каталог.

0
Rajesh Rajendran