web-dev-qa-db-ja.com

boto3とコールバックを使用してS3ファイルのダウンロードの進行状況を追跡する

Boto3を使用してS3からテキストファイルをダウンロードしようとしています。

これが私が書いたものです。

class ProgressPercentage(object):
    def __init__(self, filename):
        self._filename = filename
        self._size = float(os.path.getsize(filename))
        self._seen_so_far = 0
        self._lock = threading.Lock()

    def __call__(self, bytes_amount):
        # To simplify we'll assume this is hooked up
        # to a single filename.
        with self._lock:
            self._seen_so_far += bytes_amount
            percentage = round((self._seen_so_far / self._size) * 100,2)
            LoggingFile('{} is the file name. {} out of {} done. The percentage completed is {} %'.format(str(self._filename), str(self._seen_so_far), str(self._size),str(percentage)))
            sys.stdout.flush()

そして私はそれを使ってそれを呼んでいます

transfer.download_file(BUCKET_NAME,FILE_NAME,'{}{}'.format(LOCAL_PATH_TEMP , FILE_NAME),callback = ProgressPercentage(LOCAL_PATH_TEMP + FILE_NAME))

これにより、ファイルがフォルダーに存在しないというエラーが発生します。どうやら同じフォルダに同じ名前のファイルがすでにある場合は機能しますが、新しいファイルをダウンロードするとエラーが発生します。

必要な修正とは何ですか?

10
Kshitij Marwah

callback = ProgressPercentage(LOCAL_PATH_TEMP + FILE_NAME))ProgressPercentageオブジェクトを作成し、その__init__メソッドを実行して、オブジェクトをcallbackとしてdownload_fileメソッドに渡します。これは、__init__メソッドが実行されるbeforedownload_fileが始まることを意味します。

__init__メソッドで、ダウンロード先のローカルファイルのサイズを読み取ろうとしています。ダウンロードがまだ開始されていないため、ファイルが存在しないため、例外がスローされます。すでにファイルをダウンロードしている場合は、ローカルコピーが存在し、そのサイズを読み取ることができるため、問題はありません。

もちろん、これはあなたが見ている例外の原因にすぎません。ダウンロードの進行状況の最大値として_sizeプロパティを使用しています。ただし、ローカルファイルのサイズを使用しようとしています。ファイルが完全にダウンロードされるまで、ローカルファイルシステムはファイルの大きさを認識せず、ファイルが現在どれだけの領域を占めているかを認識します。つまり、ファイルをダウンロードすると、フルサイズに達するまでファイルが徐々に大きくなります。そのため、ローカルファイルのサイズをダウンロードの最大サイズと見なしても意味がありません。既にファイルをダウンロードしている場合は機能するかもしれませんが、それはあまり役に立ちません。

問題の解決策は、ローカルコピーのサイズではなく、ダウンロードするファイルのサイズを確認することです。これにより、ダウンロードしているファイルの実際のサイズを取得し、ファイルが存在することを確認します(ダウンロードしなかった場合はダウンロードできません)。これを行うには、次のようにhead_objectを使用してリモートファイルのサイズを取得します。

class ProgressPercentage(object):
    def __init__(self, client, bucket, filename):
        # ... everything else the same
        self._size = client.head_object(Bucket=bucket, Key=filename).ContentLength

    # ...

# If you still have the client object you could pass that directly 
# instead of transfer._manager._client
progress = ProgressPercentage(transfer._manager._client, BUCKET_NAME, FILE_NAME)
transfer.download_file(..., callback=progress)

最後の注意として、コードは Boto3のドキュメント から取得しましたが、ファイルのアップロードを目的としていたため機能しませんでした。その場合、ローカルファイルがソースであり、その存在が保証されます。

9
yummies

progressbarpip3 install progressbarとともにインストールします

import boto3, os
import progressbar

bucket_name = "<your-s3-bucket-name>"
folder_name = "<your-directory-name-locally>"
file_name = "<your-filename-locally>"
path = folder_name + "/" + file_name
s3 = boto3.client('s3', aws_access_key_id="<your_aws_access_key_id>", aws_secret_access_key="<your_aws_secret_access_key>")

statinfo = os.stat(file_name)

up_progress = progressbar.progressbar.ProgressBar(maxval=statinfo.st_size)

up_progress.start()

def upload_progress(chunk):
    up_progress.update(up_progress.currval + chunk)

s3.upload_file(file_name, bucket_name, path, Callback=upload_progress)

up_progress.finish()
8
Adam Kurkiewicz

オブジェクトclient.head_object(Bucket=bucket, Key=filename)は辞書です。ファイルサイズには、['ContentLength']を使用してアクセスできます。

したがって、コード:
self._size = client.head_object(Bucket=bucket, Key=filename).ContentLength
は次のようになります:
self._size = float(client.head_object(Bucket=bucket, Key=filename)['ContentLength'])

その後、動作します。ありがとう!

2
nicolas.f.g

これを行おうとすると、誰かがこの質問に出くわす可能性があります(質問のタイトルに従って)。私が知っている最も簡単な方法は、s3アップロードの進行状況を表示することです。

プログレスバーライブラリをプロジェクトにインポートします。これは私が使用したものです: https://github.com/anler/progressbar

次に:

import progressbar
from hurry.filesize import size
import boto3

bucket = "my-bucket-name"
s3_client = boto3.resource('s3')
...
...

# you get the filesize from wherever you have the file on. your system maybe?
filesize = size(file) 

up_progress = progressbar.AnimatedProgressBar(end=filesize, width=50)
def upload_progress(chunk):
    up_progress + chunk # Notice! No len()
    up_progress.show_progress()
s3_client.meta.client.upload_file(file, bucket, s3_file_name, Callback=upload_progress)

ここで注意する重要なことは、Callbackパラメータ(大文字のC)の使用です。基本的に、s3にアップロードされたバイト数を返します。したがって、元のファイルサイズがわかっている場合は、いくつかの簡単な計算で進捗バーが表示されます。その後、任意のプログレスバーライブラリを使用できます。

1
Emmanuel N K

情報

  • @Kshitij Marwah@yummiesnicolas.f.gの投稿へのクレジット
  • Boto3を使用1.9.96pipを介したdl)
  • 削除済みthreading
  • 表示形式を変更(dlが完了するまで上記の行を書き換え)
  • オンラインドキュメントとダウンロードしたパッケージが異なるため、投稿しました

コード

class ProgressPercentage(object):
    def __init__(self, o_s3bucket, key_name):
        self._key_name = key_name
        boto_client = o_s3bucket.meta.client
        # ContentLength is an int
        self._size = boto_client.head_object(Bucket=o_s3bucket.name, Key=key_name)['ContentLength']
        self._seen_so_far = 0
        sys.stdout.write('\n')

    def __call__(self, bytes_amount):
        self._seen_so_far += bytes_amount
        percentage = (float(self._seen_so_far) / float(self._size)) * 100
        TERM_UP_ONE_LINE = '\033[A'
        TERM_CLEAR_LINE = '\033[2K'
        sys.stdout.write('\r' + TERM_UP_ONE_LINE + TERM_CLEAR_LINE)
        sys.stdout.write('{} {}/{} ({}%)\n'.format(self._key_name, str(self._seen_so_far), str(self._size), str(percentage)))
        sys.stdout.flush()

その後、それをそのように呼びました

Cの大文字のCallbackに注意してください(これはオンラインドキュメントとは異なります)

progress = ProgressPercentage(o_s3bucket, key_name)
o_s3bucket.download_file(key_name, full_local_path, Callback=progress)

ここで、o_s3bucketは:

bucket_name = 'my_bucket_name'
aws_profile = 'default' # this is used to catch creds from .aws/credentials ini file
boto_session = boto3.session.Session(profile_name=aws_profile)
o_s3bucket = boto_session.resource('s3').Bucket(bucket_name)

hth

1
Boop

公式ドキュメント に続いて、進行状況の追跡を適用することはそれほど難しくありません(download_file関数とupload_file関数は類似しています)。以下は、好ましい方法でデータサイズを確認するためにいくつかの変更を加えた完全なコードです。

import logging
import boto3
from botocore.exceptions import ClientError
import os
import sys
import threading
import math 

ACCESS_KEY = 'xxx'
SECRET_KEY = 'xxx'
REGION_NAME= 'ap-southeast-1'

class ProgressPercentage(object):
    def __init__(self, filename, filesize):
        self._filename = filename
        self._size = filesize
        self._seen_so_far = 0
        self._lock = threading.Lock()

    def __call__(self, bytes_amount):
        def convertSize(size):
            if (size == 0):
                return '0B'
            size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
            i = int(math.floor(math.log(size,1024)))
            p = math.pow(1024,i)
            s = round(size/p,2)
            return '%.2f %s' % (s,size_name[i])

        # To simplify, assume this is hooked up to a single filename
        with self._lock:
            self._seen_so_far += bytes_amount
            percentage = (self._seen_so_far / self._size) * 100
            sys.stdout.write(
                "\r%s  %s / %s  (%.2f%%)        " % (
                    self._filename, convertSize(self._seen_so_far), convertSize(self._size),
                    percentage))
            sys.stdout.flush()


def download_file(file_name, object_name, bucket_name):
    # If S3 object_name was not specified, use file_name
    if object_name is None:
        object_name = file_name

    # Initialize s3 client
    s3_client = boto3.client(service_name="s3",
                aws_access_key_id=ACCESS_KEY,
                aws_secret_access_key=SECRET_KEY,
                region_name=REGION_NAME)
    try:
        response = s3_client.download_file(
            Bucket=bucket_name, 
            Key=object_name, 
            Filename=file_name,
            Callback=ProgressPercentage(file_name, (s3_client.head_object(Bucket=bucket_name, Key=object_name))["ContentLength"])
            )
    except ClientError as e:
        logging.error(e)
        return False
    return True

file_name = "./output.csv.gz"
bucket_name = "mybucket"
object_name = "result/output.csv.gz" 
download_file(file_name, object_name, bucket_name )
1
Nguyen Van Duc