【问题标题】:Upload image available at public URL to S3 using boto使用 boto 将公共 URL 上可用的图像上传到 S3
【发布时间】:2012-12-30 01:02:19
【问题描述】:

我在 Python Web 环境中工作,我可以使用 boto 的 key.set_contents_from_filename(path/to/file) 简单地将文件从文件系统上传到 S3。不过,我想上传一张已经在网络上的图片(比如https://pbs.twimg.com/media/A9h_htACIAAaCf6.jpg:large)。

我应该以某种方式将图像下载到文件系统,然后像往常一样使用 boto 将其上传到 S3,然后删除图像吗?

理想的情况是,如果有一种方法可以获取 boto 的 key.set_contents_from_file 或其他可以接受 URL 并将图像很好地流式传输到 S3 的命令,而无需将文件副本显式下载到我的服务器。

def upload(url):
    try:
        conn = boto.connect_s3(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
        bucket_name = settings.AWS_STORAGE_BUCKET_NAME
        bucket = conn.get_bucket(bucket_name)
        k = Key(bucket)
        k.key = "test"
        k.set_contents_from_file(url)
        k.make_public()
                return "Success?"
    except Exception, e:
            return e

使用 set_contents_from_file,如上所述,我收到“字符串对象没有属性 'tell'”错误。将 set_contents_from_filename 与 url 一起使用,我得到一个 No such file or directory 错误。 boto storage documentation 没有提到上传本地文件,也没有提到上传远程存储的文件。

【问题讨论】:

  • 您只是想避免写入磁盘吗?或者您是否试图完全避免将文件传输到您的机器上?
  • 嗯,理想情况下,可以将 URL 传递给 S3,这样我的服务器就不必写入磁盘或加载到内存中。我认为这不是对 S3 服务的合理期望。如果我的服务器必须处理这个问题,我不想写入磁盘。

标签: python django amazon-s3 boto


【解决方案1】:

这是我使用requests 的方法,关键是在最初发出请求时设置stream=True,然后使用upload.fileobj() 方法上传到s3:

import requests
import boto3

url = "https://upload.wikimedia.org/wikipedia/en/a/a9/Example.jpg"
r = requests.get(url, stream=True)

session = boto3.Session()
s3 = session.resource('s3')

bucket_name = 'your-bucket-name'
key = 'your-key-name' # key is the name of file on your bucket

bucket = s3.Bucket(bucket_name)
bucket.upload_fileobj(r.raw, key)

【讨论】:

  • 我只是在学习 boto 并越来越熟悉 AWS。你能用外行的话告诉我为什么你不能只做s3 = boto3.resource('s3')吗?没有启动默认会话吗?
  • @heartmo 此处的讨论很好地概述了客户端、会话和资源之间的差异。 stackoverflow.com/questions/42809096/…
  • 工作。非常感谢。
【解决方案2】:

好的,来自@garnaat,听起来 S3 目前不允许通过 url 上传。我设法通过仅将远程图像读入内存来将它们上传到 S3。这行得通。

def upload(url):
    try:
        conn = boto.connect_s3(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
        bucket_name = settings.AWS_STORAGE_BUCKET_NAME
        bucket = conn.get_bucket(bucket_name)
        k = Key(bucket)
        k.key = url.split('/')[::-1][0]    # In my situation, ids at the end are unique
        file_object = urllib2.urlopen(url)           # 'Like' a file object
        fp = StringIO.StringIO(file_object.read())   # Wrap object    
        k.set_contents_from_file(fp)
        return "Success"
    except Exception, e:
        return e

也感谢How can I create a GzipFile instance from the “file-like object” that urllib.urlopen() returns?

【讨论】:

  • 我不是 100% 确定,但我相信 url.split('/')[::-1][0] 可以简单地重写为 url.split('/')[-1]。我的意思是,我想不出任何结果会有所不同的情况。
【解决方案3】:

对于使用官方“boto3”包(而不是原始答案中的旧“boto”包)的这个问题的 2017 年相关答案:

Python 3.5

如果您使用的是干净的 Python 安装,请先 pip 安装两个软件包:

pip install boto3

pip install requests

import boto3
import requests

# Uses the creds in ~/.aws/credentials
s3 = boto3.resource('s3')
bucket_name_to_upload_image_to = 'photos'
s3_image_filename = 'test_s3_image.png'
internet_image_url = 'https://docs.python.org/3.7/_static/py.png'


# Do this as a quick and easy check to make sure your S3 access is OK
for bucket in s3.buckets.all():
    if bucket.name == bucket_name_to_upload_image_to:
        print('Good to go. Found the bucket to upload the image into.')
        good_to_go = True

if not good_to_go:
    print('Not seeing your s3 bucket, might want to double check permissions in IAM')

# Given an Internet-accessible URL, download the image and upload it to S3,
# without needing to persist the image to disk locally
req_for_image = requests.get(internet_image_url, stream=True)
file_object_from_req = req_for_image.raw
req_data = file_object_from_req.read()

# Do the actual upload to s3
s3.Bucket(bucket_name_to_upload_image_to).put_object(Key=s3_image_filename, Body=req_data)

【讨论】:

  • 上述方法出现异常:S3 上传异常:_send_request() 需要 5 个位置参数,但给出了 6 个
  • @ifti 看起来你可能遇到了这个错误 - github.com/boto/botocore/issues/1079 现在看起来已经修复了。
【解决方案4】:

不幸的是,真的没有办法做到这一点。至少目前不是。我们可以向 boto 添加一个方法,例如 set_contents_from_url,但该方法仍然需要将文件下载到本地计算机然后上传。它可能仍然是一种方便的方法,但它不会为您节省任何东西。

为了做您真正想做的事情,我们需要在 S3 服务本身上有一些功能,允许我们将 URL 传递给它,并将 URL 存储到我们的存储桶中。这听起来像是一个非常有用的功能。您可能希望将其发布到 S3 论坛。

【讨论】:

  • 谢谢,很高兴知道我没有错过一个可能有用的 S3 功能。我在论坛中记录了一个功能请求。
  • 这可以通过使用 boto 的 upload_fileobj()stream=True 流式传输请求的内容来完成。有关详细信息,请参阅下面的答案。
【解决方案5】:

一个简单的 3 行实现,适用于开箱即用的 lambda:

import boto3
import requests

s3_object = boto3.resource('s3').Object(bucket_name, object_key)

with requests.get(url, stream=True) as r:
    s3_object.put(Body=r.content)

.get 部分的来源直接来自requests documentation

【讨论】:

  • 您尝试过哪些文件类型?从 s3 打开时,我的 jpg 文件已损坏。
【解决方案6】:

使用 boto3 upload_fileobj 方法,您可以将文件流式传输到 S3 存储桶,而无需保存到磁盘。这是我的功能:

import boto3
import StringIO
import contextlib
import requests

def upload(url):
    # Get the service client
    s3 = boto3.client('s3')

    # Rember to se stream = True.
    with contextlib.closing(requests.get(url, stream=True, verify=False)) as response:
        # Set up file stream from response content.
        fp = StringIO.StringIO(response.content)
        # Upload data to S3
        s3.upload_fileobj(fp, 'my-bucket', 'my-dir/' + url.split('/')[-1])

【讨论】:

    【解决方案7】:

    我已尝试使用 boto3 进行以下操作,它对我有用:

    import boto3;
    import contextlib;
    import requests;
    from io import BytesIO;
    
    s3 = boto3.resource('s3');
    s3Client = boto3.client('s3')
    for bucket in s3.buckets.all():
      print(bucket.name)
    
    
    url = "@resource url";
    with contextlib.closing(requests.get(url, stream=True, verify=False)) as response:
            # Set up file stream from response content.
            fp = BytesIO(response.content)
            # Upload data to S3
            s3Client.upload_fileobj(fp, 'aws-books', 'reviews_Electronics_5.json.gz')
    

    【讨论】:

      【解决方案8】:

      目前看来,S3 不支持远程上传。您可以使用以下类将图像上传到 S3。这里的上传方法首先尝试下载图像并将其保存在内存中一段时间​​,直到它被上传。为了能够连接到 S3,您必须使用命令 pip install awscli 安装 AWS CLI,然后使用命令 aws configure 输入一些凭据:

      import urllib3
      import uuid
      from pathlib import Path
      from io import BytesIO
      from errors import custom_exceptions as cex
      
      BUCKET_NAME = "xxx.yyy.zzz"
      POSTERS_BASE_PATH = "assets/wallcontent"
      CLOUDFRONT_BASE_URL = "https://xxx.cloudfront.net/"
      
      
      class S3(object):
          def __init__(self):
              self.client = boto3.client('s3')
              self.bucket_name = BUCKET_NAME
              self.posters_base_path = POSTERS_BASE_PATH
      
          def __download_image(self, url):
              manager = urllib3.PoolManager()
              try:
                  res = manager.request('GET', url)
              except Exception:
                  print("Could not download the image from URL: ", url)
                  raise cex.ImageDownloadFailed
              return BytesIO(res.data)  # any file-like object that implements read()
      
          def upload_image(self, url):
              try:
                  image_file = self.__download_image(url)
              except cex.ImageDownloadFailed:
                  raise cex.ImageUploadFailed
      
              extension = Path(url).suffix
              id = uuid.uuid1().hex + extension
              final_path = self.posters_base_path + "/" + id
              try:
                  self.client.upload_fileobj(image_file,
                                             self.bucket_name,
                                             final_path
                                             )
              except Exception:
                  print("Image Upload Error for URL: ", url)
                  raise cex.ImageUploadFailed
      
              return CLOUDFRONT_BASE_URL + id
      

      【讨论】:

        【解决方案9】:
        from io import BytesIO
        def send_image_to_s3(url, name):
            print("sending image")
            bucket_name = 'XXX'
            AWS_SECRET_ACCESS_KEY = "XXX"
            AWS_ACCESS_KEY_ID = "XXX"
        
            s3 = boto3.client('s3', aws_access_key_id=AWS_ACCESS_KEY_ID,
                              aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
        
            response = requests.get(url)
            img = BytesIO(response.content)
        
            file_name = f'path/{name}'
            print('sending {}'.format(file_name))
            r = s3.upload_fileobj(img, bucket_name, file_name)
        
            s3_path = 'path/' + name
            return s3_path
        

        【讨论】:

          【解决方案10】:
          import boto
          from boto.s3.key import Key
          from boto.s3.connection import OrdinaryCallingFormat
          from urllib import urlopen
          
          
          def upload_images_s3(img_url):
              try:
                  connection = boto.connect_s3('access_key', 'secret_key', calling_format=OrdinaryCallingFormat())       
                  bucket = connection.get_bucket('boto-demo-1519388451')
                  file_obj = Key(bucket)
                  file_obj.key = img_url.split('/')[::-1][0]
                  fp = urlopen(img_url)
                  result = file_obj.set_contents_from_string(fp.read())
              except Exception, e:
                  return e
          

          【讨论】:

          • 真的有用吗?尽管文件格式?
          猜你喜欢
          • 2012-09-02
          • 2021-04-27
          • 2014-10-12
          • 1970-01-01
          • 2023-01-26
          • 2017-08-18
          • 2011-03-25
          • 2022-01-27
          相关资源
          最近更新 更多