S3バケットからすべてのファイルをダウンロードするBoto3


82

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'

これは、boto3を使用して完全なs3バケットをダウンロードするための適切な方法ですか。フォルダをダウンロードする方法。


回答:


34

1000以上のオブジェクトを持つバケットを操作する場合NextContinuationToken、最大で1000個のキーのシーケンシャルセットを使用するソリューションを実装する必要があります。このソリューションは、最初にオブジェクトのリストをコンパイルしてから、指定されたディレクトリを繰り返し作成し、既存のオブジェクトをダウンロードします。

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)

より幅広いユースケースを処理するため、これを受け入れられた回答に変更します。ありがとうグラント
シャン

私のコードはで無限ループに入るwhile next_token is not None:
GPD

@gpdこれは、boto3クライアントが最後のページに到達したときにNextContinuationTokenのないページを返し、whileステートメントを終了するため発生しないはずです。boto3 APIを使用して取得した最後の応答(応答変数に格納されているもの)を貼り付けると、特定のケースで何が起こっているかがより明確になると思います。テストするためだけに、「results」変数を出力してみてください。私の推測では、バケットのどのコンテンツとも一致しないプレフィックスオブジェクトを指定したと思います。確認しましたか?
GrantLangseth19年

Digital Oceanで機能させるには、小さな変更が必要になることに注意してください。説明したように、ここで
デビッド・D.

1
このコードを使用すると、次のエラーが発生します: 'NoneType'オブジェクトは反復できません:TypeError
NJones

76

私は同じニーズを持っており、ファイルを再帰的にダウンロードする次の関数を作成しました。

ディレクトリは、ファイルが含まれている場合にのみローカルに作成されます。

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', bucket='my-bucket')

6
リソースとクライアントを作成する必要はないと思います。クライアントは常にリソースで利用できると思います。を使用できますresource.meta.client
TheHerk 2016

2
「download_dir(client、resource、subdir.get( 'Prefix')、local、bucket)」である必要があると思います
rm999 2016年

6
私は取得していたOSError: [Errno 21] Is a directoryのでif not file.get('Key').endswith('/')、解決するためにdownload_fileへの呼び出しをラップしました。@glefaitと@Shanに感謝します
user336828

5
aws s3 syncboto3ライブラリで利用可能なaws-cliコマンドに相当するものはありませんか?
greperror 2017

8
distここは何ですか?
ロブ・ローズ

49

AmazonS3にはフォルダ/ディレクトリがありません。それは、フラットファイル構造

ディレクトリの外観を維持するために、パス名はオブジェクトキー(ファイル名)の一部として保存されます。例えば:

  • 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新規および変更されたファイルのみをコピーするオプションもあります。


1
@jわかりました。しかし、のように自動的にフォルダを作成する必要がありましたaws s3 sync。boto3で可能ですか。
シャン

4
Pythonコードの一部としてディレクトリの作成を含める必要があります。キーにディレクトリ(例foo/bar.txt)が含まれている場合は、をfoo呼び出す前にディレクトリ()を作成する必要がありますs3.download_file。の自動機能ではありませんboto
John Rotenstein 2015

ここでは、S3バケットのコンテンツは動的であるためs3.list_objects(Bucket='my_bucket_name')['Contents']、フォルダーキーを確認してフィルター処理し、作成する必要があります。
シャン

2
Boto3でしばらく遊んだ後、ここにリストされているAWSCLIコマンドが間違いなくこれを行う最も簡単な方法です。
AdjunctProfessorFalcon 2016

1
@Ben古い(2015)質問へのコメントとして質問するのではなく、新しい質問を開始してください。
JohnRotenstein18年

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

3
クリーンでシンプル、これを使用しない理由は何ですか?他のすべてのソリューションよりもはるかに理解しやすいです。コレクションは、バックグラウンドで多くのことを行っているようです。
Joost 2017年

3
これを正しく機能させるには、最初にすべてのサブフォルダーを作成する必要があると思います。
rpanai 2018年

2
このコードは、S3でのネストの深さに関係なく、すべてをトップレベルの出力ディレクトリに配置します。また、複数のファイルが異なるディレクトリに同じ名前を持っている場合、それらは互いに踏みつけます。もう1行必要だと思います:os.makedirs(path)、そしてダウンロード先はobject.key
スコットスミス

13

私は現在、以下を使用してタスクを達成しています

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

それは仕事をしますが、私はこのようにするのが良いかどうかわかりません。これを達成するためのより良い方法で、他のユーザーとさらなる回答を助けるためにここに残しておきます


9

決して遅くなるよりはましです:)前の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')

1
取得しましたKeyError: 'Contents'。入力パス'/arch/R/storeincomelogs/、フルパス/arch/R/storeincomelogs/201901/01/xxx.parquet
ミスリル

3

同じプロセスでAWSCLIを実行するこの回避策があります。

Pythonlibawscliとしてインストールします。

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

私は同じアイデアを使用しましたが、syncコマンドを使用せず、単にコマンドを実行しましたaws s3 cp s3://{bucket}/{folder} {local_folder} --recursive。文字通り分(ほぼ1時間)秒から減少タイムズ
acaruci

このコードを使用していますが、すべてのデバッグログが表示されるという問題があります。私はこれをグローバルに宣言しました:logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.WARNING) logger = logging.getLogger()そしてログをルートからのみ出力したいのです。何か案は?
4月Polubiec19年

1

すべてのファイルを一度に取得することは非常に悪い考えです。むしろ、バッチで取得する必要があります。

S3から特定のフォルダー(ディレクトリ)をフェッチするために使用する実装の1つは、

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

それでも、バケット全体を取得したい場合は、以下で説明する@John Rotensteinのように、CILを介して使用します。

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

0
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

Pythonを使用してbashスクリプトを呼び出したい場合は、S3バケット内のフォルダーからローカルフォルダー(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")

0

私は同様の要件を取得し、上記のソリューションのいくつかを読んだり、他のWebサイト全体で助けを得たりしました。以下のスクリプトを思いつきました。誰かに役立つかどうか、共有したかっただけです。

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)

0

osエラー20を回避するために、最後にif条件を付けて@glefaitの回答を再投稿します。最初に取得するキーは、宛先パスに書き込むことができないフォルダー名自体です。

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

0

私はしばらくの間この問題に遭遇してきました、そして私が経験したさまざまなフォーラムのすべてで、私は完全なエンドツーエンドの断片を見ていません-それは何がうまくいくかです。それで、私は先に進んですべての部分を取り(自分でいくつかのものを追加し)、完全なエンドツーエンドのS3ダウンローダーを作成しました!

これにより、ファイルが自動的にダウンロードされるだけでなく、S3ファイルがサブディレクトリにある場合は、ローカルストレージに作成されます。私のアプリケーションのインスタンスでは、アクセス許可と所有者を設定する必要があるので、それも追加しました(必要がなければコメントアウトできます)。

これはテスト済みで、Docker環境(K8)で機能しますが、ローカルでテスト/実行する場合に備えて、スクリプトに環境変数を追加しました。

これが、S3ダウンロードの自動化を見つけるための誰かの助けになることを願っています。また、必要に応じてこれをより適切に最適化する方法についてのアドバイスや情報なども歓迎します。

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

0

AWS S3ドキュメントから(S3バケット内のフォルダーを使用するにはどうすればよいですか?):

Amazon S3では、バケットとオブジェクトがプライマリリソースであり、オブジェクトはバケットに保存されます。Amazon S3は、ファイルシステムで見られるような階層ではなく、フラットな構造になっています。ただし、組織を簡素化するために、AmazonS3コンソールはオブジェクトをグループ化する手段としてフォルダーの概念をサポートしています。Amazon S3は、オブジェクトに共有名プレフィックスを使用してこれを行います(つまり、オブジェクトの名前は共通の文字列で始まります)。オブジェクト名は、キー名とも呼ばれます。

たとえば、コンソールにphotosという名前のフォルダを作成し、その中にmyphoto.jpgという名前のオブジェクトを保存できます。次に、オブジェクトはキー名photos / myphoto.jpgで保存されます。ここで、photos /はプレフィックスです。

バケットのエミュレートされたディレクトリ構造を尊重して、「mybucket」から現在のディレクトリにすべてのファイルをダウンロードするには(ローカルにまだ存在しない場合はバケットからフォルダを作成します):

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)

コードの説明を含めることができれば、より良いでしょう。
ヨハン

1
@johan、フィードバックをありがとう!関連する説明を追加しました
ダリア
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.