Como baixar imagem usando solicitações


368

Estou tentando baixar e salvar uma imagem da web usando o requestsmódulo python .

Aqui está o código (de trabalho) que eu usei:

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

Aqui está o novo código (que não 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)

Você pode me ajudar em qual atributo da resposta usar requests?


16
para uso r.raw você precisa fluxo set = True
— clsung

Respostas:


517

Você pode usar o response.rawobjeto de arquivo ou iterar sobre a resposta.

Usar o response.rawobjeto semelhante a arquivo não decodifica, por padrão, as respostas compactadas (com GZIP ou desinflar). Você pode forçá-lo a descomprimir para você de qualquer maneira, definindo o decode_contentatributo como True( requestsdefine-o Falsepara controlar a decodificação). Você pode usar o shutil.copyfileobj()Python para transmitir os dados para um objeto de arquivo:

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 a resposta, use um loop; iterar dessa maneira garante que os dados sejam descompactados nesse estágio:

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)

Isso lerá os dados em blocos de 128 bytes; se você achar que outro tamanho de pedaço funciona melhor, use o Response.iter_content()método com um tamanho de pedaço 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)

Observe que você precisa abrir o arquivo de destino no modo binário para garantir que o python não tente e traduza novas linhas para você. Também configuramos stream=Truepara que requestsnão faça o download da imagem inteira primeiro na memória.


2
Com a ajuda da sua resposta, consegui encontrar dados no arquivo de texto, as etapas que usei são r2 = requests.post(r.url, data); print r2.content. Mas agora eu também quero saber filename. é o seu caminho limpo? - atualmente encontrei o nome do arquivo no cabeçalho - r2.headers['content-disposition'] que me dá uma saída como: 'attachment; filename=DELS36532G290115.csi' Estou analisando essa cadeia de caracteres para o nome do arquivo ... é o caminho mais limpo?
— Grijesh Chauhan

6
@GrijeshChauhan: sim, o content-dispositioncabeçalho é o caminho a percorrer aqui; use cgi.parse_header()para analisá-lo e obter os parâmetros; params = cgi.parse_header(r2.headers['content-disposition'])[1]então params['filename'].
— Martijn Pieters

11
Para obter os default 128 pedaços de bytes, você precisa interagir sobre o requests.Responsepróprio : for chunk in r: .... A chamada iter_content()sem a chunk_sizeirá iterar em pedaços de 1 byte .
— dtk

@ dtk: obrigado, vou atualizar a resposta. A iteração mudou depois de postar minha resposta .
— Martijn Pieters

11
O @KumZ dois motivos: response.oknunca foi documentado e produz true para qualquer status 1xx, 2xx ou 3xx, mas apenas uma resposta 200 tem um corpo de resposta.
— Martijn Pieters

232

Obtenha um objeto parecido com um arquivo da solicitação e copie-o para um arquivo. Isso também evitará a leitura de tudo na memória de uma só 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
Muito obrigado por voltar e responder isso. Embora a outra resposta é obras, este é trancos e barrancos mais simples
— dkroy

11
Vale ressaltar que poucos servidores estão configurados para GZIP suas imagens porque as imagens já têm sua própria compactação. É contraproducente, desperdiça ciclos de CPU com pouco benefício. Portanto, embora isso possa ser um problema com o conteúdo do texto, especificamente com imagens, não é.
— Phde23

3
Existe alguma maneira podemos acessar o nome do arquivo original
— Mahes

@ phette23 Também vale a pena notar que o Google PageSpeed ​​relata e faz isso por padrão.
— Wernight 31/05

8
Deve definir r.raw.decode_content = Trueantes shutil.copyfileobj(response.raw, out_file)porque by default, decode compressed responses (with GZIP or deflate), para obter uma imagem de arquivo zero.
— Simin Jie

166

Que tal isso, uma solução 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)

11
o que queres dizer com ! f = open("/Users/apple/Desktop/sample.jpg", 'wb')o que você quer dizer com esse caminho! eu quero imagem de download para
— sorrir

3
Isso abre um descritor de arquivo no caminho especificado no qual o arquivo de imagem pode ser gravado.
— Kiranbkrishna 3/11

@AndrewGlazkov eu acho que seria mais Pythonic usarif response.ok:
— EndermanAPM

5
response.ok é verdade para qualquer 1xx, 2xx ou 3xx status, mas apenas uma resposta 200 tem um corpo de resposta como @Martijn Pieters mencionado nos comentários acima
— annndrey

75

Tenho a mesma necessidade de baixar imagens usando solicitações. Tentei pela primeira vez a resposta de Martijn Pieters e funciona bem. Mas quando fiz um perfil nessa função simples, descobri que ele usa tantas chamadas de função em comparação com urllib e urllib2.

Eu tentei da maneira recomendada pelo autor do módulo de pedidos:

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

Isso reduziu muito o número de chamadas de função, agilizando a minha aplicação. Aqui está o código do meu criador de perfil e o 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()')

O resultado para testRequest:

343080 function calls (343068 primitive calls) in 2.580 seconds

E o resultado para testRequest2:

3129 function calls (3105 primitive calls) in 0.024 seconds

13
Isso ocorre porque você não especificou o chunk_sizeparâmetro com o padrão 1, por isso iter_contentestá iterando no fluxo de resultados 1 byte por vez. Veja a documentação python-requests.org/en/latest/api/… .
— CadentOrange

10
Isso também carrega toda a resposta na memória, o que você pode querer evitar. Também não há como usar PILaqui, basta with open(image_name, 'wb') as outfile: outfile.write(r.content).
— Martijn Pieters

3
PILtambém não está na biblioteca padrão, tornando isso um pouco menos portátil.
— jjj

2
@ZhenyiZhang iter_contenté lento porque você chunk_sizeé muito pequeno; se você aumentar para 100k, será muito mais rápido.
— 2137 Wang Wang

Esta é a melhor resposta. Nem sempre é melhor ler o arquivo na memória, mas o OP especificou "imagens", o que significa que os arquivos geralmente terão menos de 4 MB, causando um impacto trivial na memória.
— precisa

52

Isso pode ser mais fácil do que usar requests. Esta é a única vez que sugiro não usar requestspara fazer coisas HTTP.

Forro dois usando urllib:

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

Há também um bom módulo Python chamado wgetque é bastante fácil de usar. Encontrado aqui .

Isso demonstra a simplicidade do design:

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

Aproveitar.

Editar: você também pode adicionar um outparâmetro para especificar um caminho.

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

Eu usei wgetsem aborrecimentos. Obrigado por declarar os benefícios do usourllib3
— h3xh4wk

11
Observe que esta resposta é para o Python 2. Para o Python 3, você precisa fazer urllib.request.urlretrieve("http://example.com", "file.ext").
— Husky

11
Obrigado @Husky. Atualizada.
— Blairg23

28

O fragmento de código a seguir baixa um arquivo.

O arquivo é salvo com o nome do arquivo como no URL especificado.

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

Existem 2 maneiras principais:

  1. Usando .content(mais simples / oficial) (consulte a resposta 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. Usando .raw(veja a resposta 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.

O tempo de ambos mostra nenhuma diferença perceptível.


2
Tentei várias respostas, e sua 1.resposta (usando io.BytesIOe Image) foi a primeira que funcionou para mim no Python 3.6. Não esqueça from PIL import Image(e pip install Pillow).
— colllin

O que há de diferente entre .content e .raw?
— Foxiris 6/05/19

13

Tão fácil quanto importar imagens e solicitações

from PIL import Image
import requests

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

4

Aqui está uma resposta mais amigável que ainda usa streaming.

Apenas defina essas funções e chame getImage(). Ele usará o mesmo nome de arquivo que o URL e gravará no diretório atual por padrão, mas ambos podem ser alterados.

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

As requestentranhas de getImage()são baseadas na resposta aqui e as entranhas de getImageFast()são baseadas na resposta acima .


3

Vou postar uma resposta, pois não tenho representante suficiente para fazer um comentário, mas com o wget publicado por Blairg23, você também pode fornecer um parâmetro de saída para o caminho.

 wget.download(url, out=path)

2

Esta é a primeira resposta que surge nas pesquisas do Google sobre como baixar um arquivo binário com solicitações. Caso você precise baixar um arquivo arbitrário com solicitações, você pode 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)

11
Agradável! Tem até um implícito .close(). Esta é a melhor resposta a partir de 2019, eu acho.
— Daniel W.

2

Foi assim que eu fiz

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

Você pode fazer algo assim:

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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.