Comment télécharger une image à l'aide de requêtes

369

J'essaie de télécharger et d'enregistrer une image à partir du Web à l'aide du requestsmodule de python .

Voici le code (de travail) que j'ai utilisé:

img = urllib2.urlopen(settings.STATICMAP_URL.format(**data))
with open(path, 'w') as f:
    f.write(img.read())

Voici le nouveau code (non fonctionnel) utilisant requests:

r = requests.get(settings.STATICMAP_URL.format(**data))
if r.status_code == 200:
    img = r.raw.read()
    with open(path, 'w') as f:
        f.write(img)

Pouvez-vous m'aider sur quel attribut de la réponse utiliser requests?

shkschneider
la source
16
pour utiliser r.raw, vous devez définir stream = True
clsung
Est-ce que cela répond à votre question? Télécharger un fichier volumineux en python avec des demandes
AMC

Réponses:

517

Vous pouvez soit utiliser l' response.rawobjet fichier , soit parcourir la réponse.

Utiliser l' response.rawobjet de type fichier ne décode pas, par défaut, les réponses compressées (avec GZIP ou dégonfler). Vous pouvez quand même le forcer à décompresser pour vous en définissant l' decode_contentattribut sur True(le requestsdéfinit sur Falsepour contrôler le décodage lui-même). Vous pouvez ensuite utiliser shutil.copyfileobj()pour que Python diffuse les données vers un objet fichier:

import requests
import shutil

r = requests.get(settings.STATICMAP_URL.format(**data), stream=True)
if r.status_code == 200:
    with open(path, 'wb') as f:
        r.raw.decode_content = True
        shutil.copyfileobj(r.raw, f)        

Pour parcourir la réponse, utilisez une boucle; itérer comme ceci garantit que les données sont décompressées à cette étape:

r = requests.get(settings.STATICMAP_URL.format(**data), stream=True)
if r.status_code == 200:
    with open(path, 'wb') as f:
        for chunk in r:
            f.write(chunk)

Cela lira les données en blocs de 128 octets; si vous pensez qu'une autre taille de bloc fonctionne mieux, utilisez la Response.iter_content()méthode avec une taille de bloc personnalisée:

r = requests.get(settings.STATICMAP_URL.format(**data), stream=True)
if r.status_code == 200:
    with open(path, 'wb') as f:
        for chunk in r.iter_content(1024):
            f.write(chunk)

Notez que vous devez ouvrir le fichier de destination en mode binaire pour vous assurer que python n'essaie pas de traduire les nouvelles lignes pour vous. Nous avons également configuré stream=Truece qui requestsne télécharge pas l'image entière en mémoire en premier.

Martijn Pieters
la source
2
Avec l'aide de votre réponse, j'ai pu trouver des données dans un fichier texte, les étapes que j'ai utilisées sont r2 = requests.post(r.url, data); print r2.content. Mais maintenant, je veux aussi savoir filename. est leur moyen nettoyé? - actuellement, j'ai trouvé le nom du fichier dans l'en-tête - r2.headers['content-disposition'] cela me donne la sortie sous la forme: 'attachment; filename=DELS36532G290115.csi' je suis en train d'analyser cette chaîne pour le nom de fichier ... est-ce que leur manière est plus propre?
Grijesh Chauhan
6
@GrijeshChauhan: oui, l'en- content-dispositiontête est le chemin à parcourir ici; utiliser cgi.parse_header()pour l'analyser et obtenir les paramètres; params = cgi.parse_header(r2.headers['content-disposition'])[1]alors params['filename'].
Martijn Pieters
1
Pour obtenir les morceaux par défaut 128 octets, vous devez parcourir la requests.Responsemême : for chunk in r: .... Appeler iter_content()sans chunk_sizevolonté itérera en morceaux de 1 octet .
dtk
@dtk: merci, je mettrai à jour la réponse. L'itération a changé après avoir posté ma réponse .
Martijn Pieters
1
@KumZ deux raisons: response.okn'a jamais été documentée, et elle produit vrai pour n'importe quel état 1xx, 2xx ou 3xx, mais seule une réponse 200 a un corps de réponse.
Martijn Pieters
232

Obtenez un objet de type fichier à partir de la demande et copiez-le dans un fichier. Cela évitera également de lire le tout en mémoire à la fois.

import shutil

import requests

url = 'http://example.com/img.png'
response = requests.get(url, stream=True)
with open('img.png', 'wb') as out_file:
    shutil.copyfileobj(response.raw, out_file)
del response
Oleh Prypin
la source
14
Merci beaucoup d'être revenu et d'avoir répondu à cela. Bien que l'autre réponse soit
efficace
11
Il convient de noter que peu de serveurs sont configurés pour GZIP leurs images car les images ont déjà leur propre compression. C'est contre-productif, gaspille les cycles CPU avec peu d'avantages. Donc, même si cela peut être un problème avec le contenu texte, en particulier avec les images, ce n'est pas le cas.
phette23
3
existe-t-il un moyen d'accéder au nom de fichier d'origine
mahes
@ phette23 Il convient également de noter que Google PageSpeed ​​signale et le fait par défaut.
Wernight
8
Devrait être défini r.raw.decode_content = Trueavant shutil.copyfileobj(response.raw, out_file)car by default, decode compressed responses (with GZIP or deflate), vous obtiendrez donc une image sans fichier.
Simin Jie
167

Que diriez-vous de cela, une solution rapide.

import requests

url = "http://craphound.com/images/1006884_2adf8fc7.jpg"
response = requests.get(url)
if response.status_code == 200:
    with open("/Users/apple/Desktop/sample.jpg", 'wb') as f:
        f.write(response.content)
kiranbkrishna
la source
1
que veux-tu dire par! f = open("/Users/apple/Desktop/sample.jpg", 'wb')que voulez-vous dire par ce chemin!? je veux télécharger l'image
sourire
3
Cela ouvre un descripteur de fichier dans le chemin spécifié dans lequel le fichier image peut être écrit.
kiranbkrishna
@AndrewGlazkov Je pense que ce serait plus Pythonic à utiliserif response.ok:
EndermanAPM
5
response.ok est True pour tout état 1xx, 2xx ou 3xx, mais seule une réponse 200 a un corps de réponse comme @Martijn Pieters mentionné dans les commentaires ci
annndrey
75

J'ai le même besoin de télécharger des images à l'aide de requêtes. J'ai d'abord essayé la réponse de Martijn Pieters, et cela fonctionne bien. Mais quand j'ai fait un profil sur cette fonction simple, j'ai trouvé qu'elle utilise autant d'appels de fonction par rapport à urllib et urllib2.

J'ai ensuite essayé la voie recommandée par l'auteur du module requêtes:

import requests
from PIL import Image
# python2.x, use this instead  
# from StringIO import StringIO
# for python3.x,
from io import StringIO

r = requests.get('https://example.com/image.jpg')
i = Image.open(StringIO(r.content))

Cela a beaucoup plus réduit le nombre d'appels de fonction, accélérant ainsi mon application. Voici le code de mon profileur et le résultat.

#!/usr/bin/python
import requests
from StringIO import StringIO
from PIL import Image
import profile

def testRequest():
    image_name = 'test1.jpg'
    url = 'http://example.com/image.jpg'

    r = requests.get(url, stream=True)
    with open(image_name, 'wb') as f:
        for chunk in r.iter_content():
            f.write(chunk)

def testRequest2():
    image_name = 'test2.jpg'
    url = 'http://example.com/image.jpg'

    r = requests.get(url)

    i = Image.open(StringIO(r.content))
    i.save(image_name)

if __name__ == '__main__':
    profile.run('testUrllib()')
    profile.run('testUrllib2()')
    profile.run('testRequest()')

Le résultat de testRequest:

343080 function calls (343068 primitive calls) in 2.580 seconds

Et le résultat de testRequest2:

3129 function calls (3105 primitive calls) in 0.024 seconds
Zhenyi Zhang
la source
13
Cela est dû au fait que vous n'avez pas spécifié le chunk_sizeparamètre par défaut à 1, de même que l' iter_contentitération sur le flux de résultat 1 octet à la fois. Voir la documentation python-requests.org/en/latest/api/… .
CadentOrange
10
Cela charge également toute la réponse en mémoire, ce que vous voudrez peut-être éviter. Il n'y a pas non plus à utiliser PILici, c'est juste with open(image_name, 'wb') as outfile: outfile.write(r.content)assez.
Martijn Pieters
3
PILn'est pas non plus dans la bibliothèque standard, ce qui le rend un peu moins portable.
jjj
2
@ZhenyiZhang iter_contentest lent parce que le vôtre chunk_sizeest trop petit, si vous l'augmentez à 100k, ce sera beaucoup plus rapide.
Wang
C'est la meilleure réponse. Il n'est pas toujours préférable de lire le fichier en mémoire, mais les «images» spécifiées par OP signifient que les fichiers seront généralement inférieurs à 4 Mo, ce qui a un impact trivial sur la mémoire.
Chris Conlan
52

Cela pourrait être plus facile que d'utiliser requests. C'est la seule fois que je proposerai de ne pas utiliser requestspour faire des trucs HTTP.

Deux doublures utilisant urllib:

>>> import urllib
>>> urllib.request.urlretrieve("http://www.example.com/songs/mp3.mp3", "mp3.mp3")

Il y a aussi un joli module Python nommé wgetqui est assez facile à utiliser. Trouvé ici .

Cela démontre la simplicité de la conception:

>>> import wget
>>> url = 'http://www.futurecrew.com/skaven/song_files/mp3/razorback.mp3'
>>> filename = wget.download(url)
100% [................................................] 3841532 / 3841532>
>> filename
'razorback.mp3'

Prendre plaisir.

Modifier: vous pouvez également ajouter un outparamètre pour spécifier un chemin.

>>> out_filepath = <output_filepath>    
>>> filename = wget.download(url, out=out_filepath)
Blairg23
la source
Je l'ai utilisé wgetsans tracas. Merci d'avoir déclaré les avantages de l'utilisationurllib3
h3xh4wk
1
Notez que cette réponse est pour Python 2. Pour Python 3, vous devez le faire urllib.request.urlretrieve("http://example.com", "file.ext").
Husky
1
Merci @Husky. Mise à jour.
Blairg23
28

L'extrait de code suivant télécharge un fichier.

Le fichier est enregistré avec son nom de fichier comme dans l'URL spécifiée.

import requests

url = "http://example.com/image.jpg"
filename = url.split("/")[-1]
r = requests.get(url, timeout=0.5)

if r.status_code == 200:
    with open(filename, 'wb') as f:
        f.write(r.content)
Katja Süss
la source
16

Il existe 2 façons principales:

  1. En utilisant .content(le plus simple / officiel) (voir la réponse de Zhenyi Zhang ):

    import io  # Note: io.BytesIO is StringIO.StringIO on Python2.
    import requests
    
    r = requests.get('http://lorempixel.com/400/200')
    r.raise_for_status()
    with io.BytesIO(r.content) as f:
        with Image.open(f) as img:
            img.show()
  2. Utilisation .raw(voir la réponse de Martijn Pieters ):

    import requests
    
    r = requests.get('http://lorempixel.com/400/200', stream=True)
    r.raise_for_status()
    r.raw.decode_content = True  # Required to decompress gzip/deflate compressed responses.
    with PIL.Image.open(r.raw) as img:
        img.show()
    r.close()  # Safety when stream=True ensure the connection is released.

Le timing ne montre aucune différence notable.

Wernight
la source
2
J'ai essayé un tas de réponses, et votre 1.réponse (en utilisant io.BytesIOet Image) a été la première à fonctionner pour moi sur Python 3.6. N'oubliez pas from PIL import Image(et pip install Pillow).
colllin
Quelle est la différence entre .content et .raw?
foxiris
13

Aussi simple que d'importer une image et des requêtes

from PIL import Image
import requests

img = Image.open(requests.get(url, stream = True).raw)
img.save('img1.jpg')
Riccardo D
la source
4

Voici une réponse plus conviviale qui utilise toujours le streaming.

Définissez simplement ces fonctions et appelez getImage(). Il utilisera le même nom de fichier que l'URL et écrit dans le répertoire courant par défaut, mais les deux peuvent être modifiés.

import requests
from StringIO import StringIO
from PIL import Image

def createFilename(url, name, folder):
    dotSplit = url.split('.')
    if name == None:
        # use the same as the url
        slashSplit = dotSplit[-2].split('/')
        name = slashSplit[-1]
    ext = dotSplit[-1]
    file = '{}{}.{}'.format(folder, name, ext)
    return file

def getImage(url, name=None, folder='./'):
    file = createFilename(url, name, folder)
    with open(file, 'wb') as f:
        r = requests.get(url, stream=True)
        for block in r.iter_content(1024):
            if not block:
                break
            f.write(block)

def getImageFast(url, name=None, folder='./'):
    file = createFilename(url, name, folder)
    r = requests.get(url)
    i = Image.open(StringIO(r.content))
    i.save(file)

if __name__ == '__main__':
    # Uses Less Memory
    getImage('http://www.example.com/image.jpg')
    # Faster
    getImageFast('http://www.example.com/image.jpg')

Les requesttripes de getImage()sont basées sur la réponse ici et les tripes de getImageFast()sont basées sur la réponse ci-dessus .

Chris Redford
la source
3

Je vais poster une réponse car je n'ai pas assez de représentant pour faire un commentaire, mais avec wget tel que publié par Blairg23, vous pouvez également fournir un paramètre de sortie pour le chemin.

 wget.download(url, out=path)
justincc
la source
2

Il s'agit de la première réponse qui apparaît pour les recherches Google sur la façon de télécharger un fichier binaire avec des demandes. Si vous devez télécharger un fichier arbitraire avec des demandes, vous pouvez utiliser:

import requests
url = 'https://s3.amazonaws.com/lab-data-collections/GoogleNews-vectors-negative300.bin.gz'
open('GoogleNews-vectors-negative300.bin.gz', 'wb').write(requests.get(url, allow_redirects=True).content)
duhaime
la source
1
Agréable! Il a même un implicite .close(). C'est la meilleure réponse à partir de 2019, je suppose.
Daniel W.
2

C'est comme ça que je l'ai fait

import requests
from PIL import Image
from io import BytesIO

url = 'your_url'
files = {'file': ("C:/Users/shadow/Downloads/black.jpeg", open('C:/Users/shadow/Downloads/black.jpeg', 'rb'),'image/jpg')}
response = requests.post(url, files=files)

img = Image.open(BytesIO(response.content))
img.show()
Harshit Singhai
la source
-1

Vous pouvez faire quelque chose comme ça:

import requests
import random

url = "https://images.pexels.com/photos/1308881/pexels-photo-1308881.jpeg? auto=compress&cs=tinysrgb&dpr=1&w=500"
name=random.randrange(1,1000)
filename=str(name)+".jpg"
response = requests.get(url)
if response.status_code.ok:
   with open(filename,'w') as f:
    f.write(response.content)
Jyotiprakash Das
la source