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?

shkschneider
fonte
16
para uso r.raw você precisa fluxo set = True
clsung
Isso responde sua pergunta? Baixe arquivos grandes em python com solicitações
AMC

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.

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

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

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