445 votes

Comment télécharger une image en utilisant des requêtes

J'essaie de télécharger et d'enregistrer une image à partir du web en utilisant la fonction python requests module.

Voici le code (fonctionnel) 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 (qui ne fonctionne pas) 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 à déterminer l'attribut de la réponse à utiliser à partir de requests ?

18 votes

Pour utiliser r.raw, vous devez définir stream=True

0 votes

583voto

Martijn Pieters Points 271458

Vous pouvez soit utiliser le response.raw Objet du fichier ou d'itérer sur la réponse.

Pour utiliser le response.raw ne décodera pas, par défaut, les réponses compressées (avec GZIP ou deflate). Vous pouvez le forcer à décompresser pour vous en définissant l'option decode_content de l'attribut True ( requests le fixe à False pour contrôler le décodage lui-même). Vous pouvez alors utiliser shutil.copyfileobj() pour que Python transmette les données à 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 itérer sur la réponse, utilisez une boucle ; en itérant ainsi, vous vous assurez que les données sont décompressées à ce stade :

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)

Les données seront lues par morceaux de 128 octets ; si vous pensez qu'une autre taille de morceaux est plus appropriée, utilisez la commande Response.iter_content() méthode avec une taille de morceau 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 défini stream=True de sorte que requests ne télécharge pas d'abord l'image entière en mémoire.

2 votes

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 les suivantes r2 = requests.post(r.url, data); print r2.content . Mais maintenant, je veux aussi savoir filename . existe-t-il un moyen propre ? -- Actuellement, je trouve le nom du fichier dans l'en-tête r2.headers['content-disposition'] qui me donne comme résultat : 'attachment; filename=DELS36532G290115.csi' J'analyse cette chaîne pour trouver le nom du fichier... Y a-t-il un moyen plus propre ?

8 votes

@GrijeshChauhan : oui, la content-disposition est la solution à adopter ici ; utilisez l'en-tête cgi.parse_header() pour l'analyser et obtenir les paramètres ; params = cgi.parse_header(r2.headers['content-disposition'])[1] puis params['filename'] .

1 votes

Pour obtenir les morceaux de 128 octets par défaut, vous devez itérer sur les requests.Response lui-même : for chunk in r: ... . Appeler iter_content() sans un chunk_size sera itérer dans des morceaux de 1 octet .

263voto

Oleh Prypin Points 9086

Obtenir un objet de type fichier à partir de la requête et le copier dans un fichier. Cela évitera également de tout lire en mémoire en une seule 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

15 votes

Merci beaucoup d'être revenu et de répondre à cette question. Bien que l'autre réponse fonctionne, celle-ci est beaucoup plus simple.

13 votes

Il convient de noter que peu de serveurs sont configurés pour compresser leurs images en GZIP, car les images ont déjà leur propre compression. C'est contre-productif, cela gaspille des cycles de CPU avec peu d'avantages. Ainsi, si cela peut être un problème avec le contenu textuel, ce n'est pas le cas avec les images.

3 votes

Y a-t-il un moyen d'accéder au nom du fichier original ?

199voto

kiranbkrishna Points 86

Que pensez-vous de ça, 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)

1 votes

Que voulez-vous dire avec ! f = open("/Users/apple/Desktop/sample.jpg", 'wb') Qu'est-ce que vous voulez dire avec ce chemin ? Je veux télécharger l'image.

5 votes

Cela ouvre un descripteur de fichier dans le chemin spécifié dans lequel le fichier image peut être écrit.

0 votes

@AndrewGlazkov Je pense que ce serait plus Pythonique d'utiliser if response.ok:

84voto

Zhenyi Zhang Points 141

J'ai le même besoin de télécharger des images en utilisant des 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 beaucoup d'appels de fonction par rapport à urllib et urllib2.

J'ai ensuite essayé le moyen recommandé par l'auteur des demandes de module :

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 permis de réduire beaucoup plus le nombre d'appels de fonctions, et donc d'accélérer mon application. Voici le code de mon profiler 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 pour testRequest2 :

3129 function calls (3105 primitive calls) in 0.024 seconds

13 votes

C'est parce que vous n'avez pas spécifié l'option chunk_size qui a la valeur 1 par défaut, donc iter_content itère sur le flux de résultats, un octet à la fois. Voir la documentation python-requests.org/en/latest/api/ .

10 votes

Cela charge également l'ensemble de la réponse en mémoire, ce que vous pouvez vouloir éviter. Il n'est pas nécessaire d'utiliser PIL ici non plus, juste with open(image_name, 'wb') as outfile: outfile.write(r.content) est suffisant.

3 votes

PIL n'est pas non plus dans la bibliothèque standard, ce qui rend cette méthode un peu moins portable.

10voto

zwidny Points 1

Et de cette façon :

# filename.py    
import requests

url = 'http://www.example.com/image.jpg'
page = requests.get(url)
with open('test', 'wb') as test:
test.write(page.content)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X