Cómo descargar imágenes usando solicitudes


369

Estoy tratando de descargar y guardar una imagen de la web usando el requestsmódulo de Python .

Aquí está el código (de trabajo) que utilicé:

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

Aquí está el nuevo código (que no funciona) usando 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)

¿Me pueden ayudar en qué atributo de la respuesta usar requests?


16
para usar r.raw necesitas configurar stream = True
clsung

Respuestas:


517

Puede usar el response.rawobjeto de archivo o iterar sobre la respuesta.

El uso del response.rawobjeto tipo archivo no descodificará, de forma predeterminada, las respuestas comprimidas (con GZIP o desinflado). De todos modos, puede forzarlo a descomprimirse configurando el decode_contentatributo en True(lo requestsconfigura Falsepara controlar la decodificación). Luego puede usar shutil.copyfileobj()para que Python transmita los datos a un objeto de archivo:

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)        

Para iterar sobre la respuesta, use un bucle; iterar así asegura que los datos se descompriman en esta etapa:

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)

Esto leerá los datos en fragmentos de 128 bytes; Si cree que otro tamaño de fragmento funciona mejor, utilice el Response.iter_content()método con un tamaño de fragmento personalizado:

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)

Tenga en cuenta que debe abrir el archivo de destino en modo binario para asegurarse de que Python no intente traducir nuevas líneas por usted. También lo configuramos stream=Truepara que requestsno descargue la imagen completa en la memoria primero.


2
Con la ayuda de su respuesta pude encontrar datos en un archivo de texto, los pasos que utilicé son r2 = requests.post(r.url, data); print r2.content. Pero ahora también quiero saber filename. ¿hay alguna forma limpia? - actualmente encontré el nombre del archivo en el encabezado - r2.headers['content-disposition'] eso me da salida como: 'attachment; filename=DELS36532G290115.csi' Estoy analizando esta cadena para el nombre del archivo ... ¿es su forma más limpia?
Grijesh Chauhan

66
@GrijeshChauhan: sí, el content-dispositionencabezado es el camino a seguir aquí; use cgi.parse_header()para analizarlo y obtener los parámetros; params = cgi.parse_header(r2.headers['content-disposition'])[1]entonces params['filename'].
Martijn Pieters

1
Para obtener los trozos por defecto 128 bytes, es necesario iterar sobre la requests.Responsemisma : for chunk in r: .... Llamando iter_content()sin chunk_sizetendrá una iteración en trozos de 1 byte .
dtk

@dtk: gracias, actualizaré la respuesta. La iteración cambió después de que publiqué mi respuesta .
Martijn Pieters

1
@KumZ dos razones: response.oknunca se documentó, y produce verdadero para cualquier estado 1xx, 2xx o 3xx, pero solo una respuesta 200 tiene un cuerpo de respuesta.
Martijn Pieters

232

Obtenga un objeto similar a un archivo de la solicitud y cópielo en un archivo. Esto también evitará leer todo en la memoria de una vez.

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

14
Muchas gracias por volver y responder esto. Aunque la otra respuesta es funciona, esta es más simple a pasos agigantados
dkroy

11
Vale la pena señalar que pocos servidores están configurados para GZIP sus imágenes porque las imágenes ya tienen su propia compresión. Es contraproducente, desperdicia ciclos de CPU con poco beneficio. Entonces, si bien esto puede ser un problema con el contenido de texto, específicamente con las imágenes, no lo es.
phette23

3
¿hay alguna manera de acceder al nombre de archivo original
mahes

@ phette23 También vale la pena señalar que Google PageSpeed ​​informa y lo hace por defecto.
Wernight

8
Debería establecerse r.raw.decode_content = Trueantes shutil.copyfileobj(response.raw, out_file)porque by default, decode compressed responses (with GZIP or deflate), por lo que obtendrá una imagen de archivo cero.
Simin Jie

167

Qué tal esto, una solución rápida.

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
a qué te refieres con ! f = open("/Users/apple/Desktop/sample.jpg", 'wb')¿Qué quieres decir con este camino? Quiero descargar la imagen
sonríe el

3
Eso abre un descriptor de archivo en la ruta especificada en la que se puede escribir el archivo de imagen.
kiranbkrishna

@AndrewGlazkov Creo que sería más Pythonic usarif response.ok:
EndermanAPM

55
response.ok es verdadero para cualquier estado 1xx, 2xx o 3xx, pero solo una respuesta 200 tiene un cuerpo de respuesta como @Martijn Pieters mencionado en los comentarios anteriores
annndrey

75

Tengo la misma necesidad de descargar imágenes usando solicitudes. Primero probé la respuesta de Martijn Pieters, y funciona bien. Pero cuando hice un perfil en esta función simple, descubrí que usa tantas llamadas de función en comparación con urllib y urllib2.

Luego probé la forma recomendada por el autor del módulo de solicitudes:

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

Esto redujo mucho más el número de llamadas a funciones, acelerando así mi aplicación. Aquí está el código de mi perfilador y el resultado.

#!/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()')

El resultado para testRequest:

343080 function calls (343068 primitive calls) in 2.580 seconds

Y el resultado para testRequest2:

3129 function calls (3105 primitive calls) in 0.024 seconds

13
Esto se debe a que no ha especificado el chunk_sizeparámetro que por defecto es 1, por lo que iter_contentestá iterando sobre el flujo de resultados 1 byte a la vez. Consulte la documentación de python-requests.org/en/latest/api/… .
CadentOrange

10
Esto también carga toda la respuesta en la memoria, que es posible que desee evitar. No hay que usar PILaquí tampoco, solo with open(image_name, 'wb') as outfile: outfile.write(r.content)es suficiente.
Martijn Pieters

3
PILtampoco está en la biblioteca estándar, lo que lo hace un poco menos portátil.
jjj

2
@ZhenyiZhang iter_contentes lento porque tu chunk_sizees demasiado pequeño, si lo aumentas a 100k será mucho más rápido.
Wang

Esta es la mejor respuesta. No siempre es mejor leer el archivo en la memoria, pero OP especificó "imágenes", lo que significa que los archivos generalmente tendrán menos de 4 MB, lo que tendrá un impacto trivial en la memoria.
Chris Conlan

52

Esto podría ser más fácil que usar requests. Esta es la única vez que sugeriré no usar requestspara hacer cosas HTTP.

Dos revestimientos usando urllib:

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

También hay un bonito módulo Python llamado wgetque es bastante fácil de usar. Encontrado aquí .

Esto demuestra la simplicidad del diseño:

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

Disfrutar.

Editar: también puede agregar un outparámetro para especificar una ruta.

>>> out_filepath = <output_filepath>    
>>> filename = wget.download(url, out=out_filepath)

Utilicé wgetsin problemas. Gracias por indicar los beneficios de usarurllib3
h3xh4wk

1
Tenga en cuenta que esta respuesta es para Python 2. Para Python 3 debe hacerlo urllib.request.urlretrieve("http://example.com", "file.ext").
Husky

1
Gracias @Husky Actualizado.
Blairg23

28

El siguiente fragmento de código descarga un archivo.

El archivo se guarda con su nombre de archivo como en la URL especificada.

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)

16

Hay 2 formas principales:

  1. Usando .content(más simple / oficial) (ver la respuesta 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. Utilizando .raw (ver la respuesta 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.

La sincronización de ambos no muestra una diferencia notable.


2
Intenté un montón de respuestas, y tu 1.respuesta (usando io.BytesIOy Image) fue la primera que funcionó para mí en Python 3.6. No te olvides from PIL import Image(y pip install Pillow).
colllin

¿Qué es diferente entre .content y .raw?
foxiris

13

Tan fácil como importar imágenes y solicitudes

from PIL import Image
import requests

img = Image.open(requests.get(url, stream = True).raw)
img.save('img1.jpg')

4

Aquí hay una respuesta más fácil de usar que todavía usa la transmisión.

Simplemente defina estas funciones y llame getImage(). Utilizará el mismo nombre de archivo que la url y escribirá en el directorio actual de forma predeterminada, pero ambos se pueden cambiar.

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')

Las requestagallas de getImage()se basan en la respuesta aquí y las agallas de getImageFast()se basan en la respuesta anterior .


3

Voy a publicar una respuesta ya que no tengo suficiente representante para hacer un comentario, pero con wget publicado por Blairg23, también puede proporcionar un parámetro de salida para la ruta.

 wget.download(url, out=path)

2

Esta es la primera respuesta que surge para las búsquedas de Google sobre cómo descargar un archivo binario con solicitudes. En caso de que necesite descargar un archivo arbitrario con solicitudes, puede usar:

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)

1
¡Agradable! Tiene incluso una implícita .close(). Esta es la mejor respuesta a partir de 2019, supongo.
Daniel W.

2

Así es como lo hice

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()

-1

Puedes hacer algo como esto:

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)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.