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?

shkschneider
fuente
16
para usar r.raw necesitas configurar stream = True
clsung
¿Responde esto a tu pregunta? Descargue un archivo grande en Python con solicitudes
AMC

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.

Martijn Pieters
fuente
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
Oleh Prypin
fuente
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)
kiranbkrishna
fuente
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
Zhenyi Zhang
fuente
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)
Blairg23
fuente
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)
Katja Süss
fuente
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.

Wernight
fuente
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')
Riccardo D
fuente
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 .

Chris Redford
fuente
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)
justincc
fuente
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)
duhaime
fuente
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()
Harshit Singhai
fuente
-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)
Jyotiprakash Das
fuente