it-swarm.com.de

Boto3, um alle Dateien von einem S3-Bucket herunterzuladen

Ich verwende boto3, um Dateien aus dem s3-Bucket abzurufen. Ich brauche eine ähnliche Funktion wie aws s3 sync

Mein aktueller Code lautet

#!/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 ein Ordner im Bucket vorhanden ist, 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 guter Weg, um einen kompletten s3-Bucket mit boto3 herunterzuladen. So laden Sie Ordner herunter.

43
Shan

Ich habe die gleichen Anforderungen und erstelle die folgende Funktion, die die Dateien rekursiv herunterlädt .. Die Verzeichnisse werden nur 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')
53
glefait

Amazon S3 verfügt nicht über Ordner/Verzeichnisse. Es ist eine flat-Dateistruktur.

Um das Aussehen von Verzeichnissen zu erhalten, werden Pfadnamen als Teil des Objekts Key (Dateiname) gespeichert. Zum Beispiel:

  • images/foo.jpg

In diesem Fall ist der gesamte Schlüssel images/foo.jpg und nicht nur foo.jpg.

Ich vermute, dass Ihr Problem darin besteht, dass boto eine Datei mit dem Namen my_folder/.8Df54234 zurückgibt und versucht, sie im lokalen Dateisystem zu speichern. Ihr lokales Dateisystem interpretiert jedoch den my_folder/-Teil als Verzeichnisnamen und dieses Verzeichnis ist in Ihrem lokalen Dateisystem nicht vorhanden.

Sie können entweder truncate den Dateinamen verwenden, um nur den .8Df54234-Teil zu speichern, oder Sie müssen die erforderlichen Verzeichnisse erstellen, bevor Sie Dateien schreiben. Beachten Sie, dass es sich dabei um verschachtelte Verzeichnisse mit mehreren Ebenen handeln kann.

Ein einfacherer Weg wäre die Verwendung der AWS-Befehlszeilenschnittstelle (CLI) , die all diese Arbeit für Sie erledigt, z.

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

Es gibt auch eine sync-Option, die nur neue und geänderte Dateien kopiert.

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

Ich erfülle die Aufgabe derzeit mit den folgenden

#!/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 die Aufgabe erfüllt, bin ich mir nicht sicher, ob es gut ist, dies zu tun. Ich lasse es hier, um anderen Benutzern zu helfen und weitere Antworten zu geben, um dies besser zu erreichen

10
Shan

Wenn Sie mit Buckets mit mehr als 1000 Objekten arbeiten, müssen Sie eine Lösung implementieren, die NextContinuationToken für aufeinanderfolgende 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)
9
Grant Langseth

Besser spät als nie :) Die vorhergehende Antwort mit Paginator ist wirklich gut. Es ist jedoch rekursiv und Sie könnten am Ende die Rekursionsgrenzen von Python erreichen. Hier ist ein alternativer Ansatz mit ein paar zusätzlichen Prü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')
7
ifoukarakis

Es ist eine sehr schlechte Idee, alle Dateien auf einmal zu bekommen. Sie sollten sie lieber stapelweise erhalten.

Eine Implementierung, die ich zum Abrufen eines bestimmten Ordners (Verzeichnis) von S3 verwende, 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 wollen, benutzen Sie ihn über CIL als @John Rotenstein genannt wie unten,

aws s3 cp --recursive s3://bucket_name download_path
1
Ganatra

Es gibt eine Problemumgehung, bei der die AWS-CLI in demselben Prozess ausgeführt wird. 

Installieren Sie awscli als Python-Bibliothek:

pip install awscli

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')
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')

Dieser Code lädt den Inhalt im /tmp/-Verzeichnis herunter. Wenn Sie möchten, können Sie das Verzeichnis ändern.

0

Wenn Sie ein Bash-Skript mit Python aufrufen möchten, ist dies eine einfache Methode, um eine Datei aus einem Ordner im S3-Bucket in einen lokalen Ordner (auf einem Linux-Computer) zu 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")
0
snat2100