web-dev-qa-db-fra.com

Importer une image disponible sur l'URL publique vers S3 à l'aide de boto

Je travaille dans un environnement Web Python et je peux simplement télécharger un fichier du système de fichiers sur S3 à l'aide de la clé de boto.set_contents_from_filename (chemin/vers/fichier). Cependant, je souhaiterais télécharger une image déjà présente sur le Web (par exemple https://pbs.twimg.com/media/A9h_htACIAAaCf6.jpg:large ).

Dois-je en quelque sorte télécharger l'image sur le système de fichiers, puis la télécharger sur S3 en utilisant boto comme d'habitude, puis supprimer l'image? 

L'idéal serait de trouver un moyen d'obtenir le fichier key.set_contents_from_file de boto ou une autre commande acceptant une URL et transmettant joliment l'image à S3 sans avoir à télécharger explicitement une copie de fichier sur mon serveur.

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

En utilisant set_contents_from_file, comme ci-dessus, j'obtiens une erreur "l'objet chaîne n'a pas d'attribut 'tell'". En utilisant set_contents_from_filename avec l’URL, j’obtiens une telle erreur de fichier ou de répertoire. La documentation de stockage boto s'interdit de télécharger des fichiers locaux et ne mentionne pas le téléchargement de fichiers stockés à distance.

26
dgh

Malheureusement, il n'y a vraiment aucun moyen de faire cela. Au moins, pas maintenant. Nous pourrions ajouter une méthode à boto, par exemple set_contents_from_url, mais cette méthode devra toujours télécharger le fichier sur la machine locale, puis le télécharger. C'est peut-être une méthode pratique mais cela ne vous épargnera rien.

Afin de faire ce que vous voulez vraiment faire, nous aurions besoin d'une capacité sur le service S3 lui-même qui nous permettrait de lui transmettre l'URL et de le stocker dans un compartiment pour nous. Cela semble être une fonctionnalité très utile. Vous voudrez peut-être poster cela sur les forums S3.

7
garnaat

Ok, de @garnaat, il ne semble pas que S3 autorise actuellement les téléchargements par URL. J'ai réussi à télécharger des images distantes sur S3 en les lisant uniquement dans la mémoire. Cela marche.

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

Merci également à Comment créer une instance de GzipFile à partir de «l'objet semblable à un fichier» renvoyé par urllib.urlopen ()?

21
dgh

Pour une réponse pertinente pour 2017 à cette question qui utilise le package officiel «boto3» (au lieu de l'ancien package «boto» de la réponse d'origine):

Python 3.5

Si vous êtes sur une installation Python vierge, installez d'abord les deux paquets:

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)
10
GISD

Voici comment je l'ai fait avec request , la clé étant de définir stream=True lors de la demande initiale et de le télécharger sur s3 à l'aide de la méthode upload.fileobj():

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'

bucket = s3.Bucket(bucket_name)
bucket.upload_fileobj(r.raw, key_name)
4
blaklaybul

En utilisant la méthode boto3 upload_fileobj, vous pouvez diffuser un fichier dans un compartiment S3 sans enregistrer sur le disque. Voici ma fonction:

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])
1
x00x70
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
0

J'ai essayé comme suit avec boto3 et cela me fonctionne:

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')
0
yunus kula

Une implémentation simple à 3 lignes qui fonctionne sur un lambda prêt à l'emploi:

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)

La source de la partie .get provient directement de la documentation requests

0
Filippo Vitale