【问题标题】:Track download progress of S3 file using boto3 and callbacks使用 boto3 和回调跟踪 S3 文件的下载进度
【发布时间】:2017-06-09 05:43:39
【问题描述】:

我正在尝试使用 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))

这给了我一个错误,即文件夹中不存在文件。显然,当我在同一个文件夹中已经有一个同名的文件时,它可以工作,但是当我下载一个新文件时,它会出错。

我需要做哪些更正?

【问题讨论】:

  • 这里没有说明transfer 变量是什么。
  • 您还缺少systhreading 以及LoggingFile 是什么?

标签: python amazon-s3 callback boto3


【解决方案1】:

callback = ProgressPercentage(LOCAL_PATH_TEMP + FILE_NAME)) 创建一个ProgressPercentage 对象,运行其__init__ 方法,并将该对象作为callback 传递给download_file 方法。这意味着__init__ 方法在download_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 documentation 获得了代码,但它不起作用,因为它是用于文件上传的。在这种情况下,本地文件就是源文件,并且它的存在是有保证的。

【讨论】:

  • 所以......如果只有我,但在文档中 1.9.96 命名参数是 callback 减去 c。但是在相同版本的代码中(通过 pip 下载),我得到了大写 C 来表示这个完全相同的参数 /:我 = 困惑。我将在下面发布我的代码作为示例。
  • 这对我很有用!只需要做一个小改动。 head_object 返回一个字典。 client.head_object(Bucket=bucket, Key=filename).get('ContentLength')
  • 您将如何显示所述上传或下载的进度,这仅显示如何获取特定调用的下载百分比?
  • 这不是 MVP。我不明白如何使用此代码。 transfer 也没有定义。
  • 我对代码做了一些改动,现在它可以工作了! client.Object(bucket, filename).get()['ContentLength'] 我的“客户”是boto3 resource object。我需要进行此更改,因为我使用 accessKey/accecssSecretKey 创建了 boto3 会话对象。
【解决方案2】:

pip3 install progressbar安装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()

【讨论】:

  • 刚刚得到@EmmanuelNK 的答案以使用最新的 pip3
  • from hurry.filesize import size 未使用。
【解决方案3】:

这是我的实现。没有其他依赖,破解进度回调函数来显示任何你想要的。

import sys
import boto3

s3_client = boto3.client('s3')

def download(local_file_name, s3_bucket, s3_object_key):

    meta_data = s3_client.head_object(Bucket=s3_bucket, Key=s3_object_key)
    total_length = int(meta_data.get('ContentLength', 0))
    downloaded = 0

    def progress(chunk):
        nonlocal downloaded
        downloaded += chunk
        done = int(50 * downloaded / total_length)
        sys.stdout.write("\r[%s%s]" % ('=' * done, ' ' * (50-done)) )
        sys.stdout.flush()

    print(f'Downloading {s3_object_key}')
    with open(local_file_name, 'wb') as f:
        s3_client.download_fileobj(s3_bucket, s3_object_key, f, Callback=progress)

例如

local_file_name = 'test.csv'
s3_bucket = 'my-bucket'
s3_object_key = 'industry/test.csv'

download(local_file_name, s3_bucket, s3_object_key)

演示:

boto3&gt;=1.14.19python&gt;=3.7测试

【讨论】:

    【解决方案4】:

    按照official document,应用进度跟踪并不难(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 )
    

    【讨论】:

      【解决方案5】:

      对象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'])

      然后就可以了。谢谢!

      【讨论】:

        【解决方案6】:

        尝试执行此操作时,有人可能会偶然发现此答案(根据问题标题)。我知道显示 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 的字节数。因此,如果您知道原始文件大小,一些简单的数学运算就会为您提供一个进度条。然后,您可以使用任何进度条库。

        【讨论】:

        • 不适用于我用 pip3 安装的进度条版本。
        • 我应该提到我只是使用 pip3 直接将 lib 放入我的项目中。对于那些想知道如何操作的人:创建一个名为 progressbar 的文件夹并将其与其余的 python 库一起放置,在其中添加一个空的 __init__.py 文件。然后从 github repo 添加progressbar.py 文件。然后你将它正常导入到你的项目中。
        【解决方案7】:

        信息

        • @Kshitij Marwah@yummiesnicolas.f.g 的帖子
        • 使用 boto3 1.9.96 (dl via pip)
        • 删除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()
        

        然后这样称呼它

        注意Callback 上的大写C(与在线文档不同)

        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)
        

        【讨论】:

        • 如何将Callback 传送到logging 以提供发送到AWS 的百分比?诸如INFO: 10% of xyz.file uploadedINFO: 20% of xyz.file uploaded 等直到并包括INFO: xyz.file successfully uploaded
        • 我猜每次有新数据包到来(或上传)时都会调用回调。但绝对是一堆时间。我不建议记录进度,因为没有附加价值,而且 ti 占用空间一无所获。但是您当然可以这样做:在我的__call__ 方法中,您可以调用日志记录:它将记录每个步骤。希望我能回答你的问题
        • 谢谢@Boop。当 Python 代码作为 Azure 函数托管并且数据传输量很大时,附加值(至少对我而言)就会出现。我希望只记录每 10% 或类似的记录。我会切磋琢磨,看看我能想出什么。
        【解决方案8】:

        这是我发现使用click(在应用下面的代码之前运行pip install click)库时有用的选项:

        import click
        import boto3
        import os
        
        
        file_path = os.path.join('tmp', 'file_path')
        s3_client = boto3.resource('s3')
        with click.progressbar(length=os.path.getsize(file_path)) as progress_bar:
            with open(file_path, mode='rb') as upload_file:
                s3_client.upload_fileobj(
                    upload_file,
                    'bucket_name',
                    'foo_bar',
                    Callback=progress_bar.update
        )
        
        

        【讨论】:

          【解决方案9】:

          这是另一个使用 tqdm 的简单自定义实现:

          from tqdm import tqdm
          import boto3
          
          def s3_download(s3_bucket, s3_object_key, local_file_name, s3_client=boto3.client('s3')):
              meta_data = s3_client.head_object(Bucket=s3_bucket, Key=s3_object_key)
              total_length = int(meta_data.get('ContentLength', 0))
              with tqdm(total=total_length,  desc=f'source: s3://{s3_bucket}/{s3_object_key}', bar_format="{percentage:.1f}%|{bar:25} | {rate_fmt} | {desc}",  unit='B', unit_scale=True, unit_divisor=1024) as pbar:
                  with open(local_file_name, 'wb') as f:
                      s3_client.download_fileobj(s3_bucket, s3_object_key, f, Callback=pbar.update)
          

          用法:

          s3_download(bucket, key, local_file_name)
          

          输出:

          100.0%|█████████████████████████ | 12.9MB/s | source: s3://my-bucket/my-key
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-09-21
            • 1970-01-01
            • 2017-07-29
            • 1970-01-01
            • 2012-08-18
            相关资源
            最近更新 更多