Как скачать изображение с помощью запросов

369

Я пытаюсь загрузить и сохранить изображение из Интернета с помощью requestsмодуля Python .

Вот (рабочий) код, который я использовал:

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

Вот новый (нерабочий) код, использующий 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)

Можете ли вы помочь мне с каким атрибутом из ответа использовать requests?

shkschneider
источник
16
чтобы использовать r.raw вам нужно установить stream = True
clsung
Отвечает ли это на ваш вопрос? Скачать большой файл в python с запросами
AMC

Ответы:

517

Вы можете использовать response.rawобъект файла или выполнить итерацию ответа.

Использование response.rawфайлового объекта по умолчанию не будет декодировать сжатые ответы (с помощью GZIP или deflate). В любом случае вы можете принудительно распаковать его, установив для decode_contentатрибута значение True( requestsзадает Falseдля управления самим декодированием). Затем вы можете использовать shutil.copyfileobj()Python для потоковой передачи данных в файловый объект:

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)        

Для перебора ответа используйте цикл; итерация, подобная этой, гарантирует, что данные распаковываются на этом этапе:

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)

Это прочитает данные в 128 байтных чанках; если вы чувствуете, что другой размер чанка работает лучше, используйте Response.iter_content()метод с пользовательским размером чанка:

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)

Обратите внимание, что вам нужно открыть файл назначения в двоичном режиме, чтобы python не пытался переводить переводы строк для вас. Мы также настроили stream=Trueтак, чтобы requestsсначала не загружать все изображение в память.

Мартейн Питерс
источник
2
С помощью вашего ответа я смог найти данные в текстовом файле, шаги, которые я использовал r2 = requests.post(r.url, data); print r2.content. Но теперь я тоже хочу знать filename. их какой-нибудь очищенный способ? - в настоящее время я нашел имя файла в заголовке - r2.headers['content-disposition'] это дает мне вывод как: 'attachment; filename=DELS36532G290115.csi' я анализирую эту строку для имени файла ... их какой-либо более чистый способ?
Грижеш Чаухан
6
@GrijeshChauhan: да, content-dispositionзаголовок это путь сюда; использовать cgi.parse_header()для его анализа и получения параметров; params = cgi.parse_header(r2.headers['content-disposition'])[1]тогда params['filename'].
Мартин Питерс
1
Чтобы получить по умолчанию 128 байт ломти с, вам нужно перебрать на requests.Responseсебя : for chunk in r: .... Вызов iter_content()без операцииchunk_size будет повторяться в 1-байтовых чанках .
23: 23
@dtk: спасибо, я обновлю ответ. Итерация изменилась после того, как я опубликовал свой ответ .
Мартин Питерс
1
@KumZ две причины: response.okникогда не был задокументирован, и он дает истину для любого статуса 1xx, 2xx или 3xx, но только ответ 200 имеет тело ответа.
Мартин Питерс
232

Получите объектоподобный объект из запроса и скопируйте его в файл. Это также позволит избежать одновременного считывания всего этого в память.

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
Большое спасибо за то, что вернулись и ответили на это. Хотя другой ответ работает, на этот раз все проще
dkroy
11
Стоит отметить, что немногие серверы настроили GZIP для своих изображений, потому что изображения уже имеют свою собственную компрессию. Это контрпродуктивно, тратит впустую циклы процессора с небольшой выгодой. Так что, хотя это может быть проблемой с текстовым контентом, особенно с изображениями, это не так.
phette23
3
есть ли способ получить доступ к исходному имени файла
mahes
@ phette23 Также стоит отметить, что Google PageSpeed ​​сообщает и делает это по умолчанию.
Вечерняя ночь
8
Следует установить r.raw.decode_content = Trueраньше, shutil.copyfileobj(response.raw, out_file)потому что by default, decode compressed responses (with GZIP or deflate), так что вы получите изображение с нулевым файлом.
Симин Цзе
167

Как насчет этого, быстрое решение.

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
источник
1
что ты имеешь в виду! f = open("/Users/apple/Desktop/sample.jpg", 'wb')что ты имеешь ввиду под этим путем ?! Я хочу скачать изображение
улыбка
3
Это открывает дескриптор файла по указанному пути, в который можно записать файл изображения.
Киранбкришна
@AndrewGlazkov Я думаю, что это было бы более Python для использованияif response.ok:
EndermanAPM
5
response.ok имеет значение True для любого статуса 1xx, 2xx или 3xx, но только ответ 200 имеет тело ответа в виде @Martijn Pieters, упомянутого в комментариях выше
annndrey
75

У меня такая же потребность в загрузке изображений с использованием запросов. Сначала я попробовал ответ Martijn Pieters, и он хорошо работает. Но когда я сделал профиль для этой простой функции, я обнаружил, что она использует так много вызовов функций по сравнению с urllib и urllib2.

Затем я попробовал способ, рекомендованный автором модуля запросов:

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

Это значительно уменьшило количество вызовов функций и ускорило работу моего приложения. Вот код моего профилировщика и результат.

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

Результат для testRequest:

343080 function calls (343068 primitive calls) in 2.580 seconds

И результат для testRequest2:

3129 function calls (3105 primitive calls) in 0.024 seconds
Чжень Чжан
источник
13
Это потому, что вы не указали chunk_sizeпараметр, который по умолчанию iter_contentравен 1, поэтому выполняется итерация по потоку результатов по 1 байту за раз. Смотрите документацию python-requests.org/en/latest/api/… .
CadentOrange
10
Это также загружает весь ответ в память, чего вы можете избежать. Здесь PILтоже нельзя использовать , with open(image_name, 'wb') as outfile: outfile.write(r.content)достаточно просто .
Мартин Питерс
3
PILтакже отсутствует в стандартной библиотеке, что делает его немного менее переносимым.
JJJ
2
@ZhenyiZhang iter_contentмедленный, потому что ваш chunk_sizeслишком мал, если вы увеличите его до 100k, это будет намного быстрее.
Ван
Это лучший ответ. Не всегда лучше читать файл в память, но OP указывает «изображения», то есть размер файлов обычно не превышает 4 МБ, что оказывает тривиальное влияние на память.
Крис Конлан
52

Это может быть проще, чем использовать requests. Это единственный раз, когда я советую не использовать requestsHTTP-вещи.

Два лайнера с использованием urllib:

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

Есть также хороший модуль с именем Python, wgetкоторый довольно прост в использовании. Нашел здесь .

Это демонстрирует простоту конструкции:

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

Наслаждаться.

Редактировать: Вы также можете добавить outпараметр, чтобы указать путь.

>>> out_filepath = <output_filepath>    
>>> filename = wget.download(url, out=out_filepath)
Blairg23
источник
Я использовал wgetбез каких-либо неприятностей. Спасибо за указание преимуществ использованияurllib3
h3xh4wk
1
Обратите внимание, что этот ответ для Python 2. Для Python 3 вам нужно сделать urllib.request.urlretrieve("http://example.com", "file.ext").
Хаски
1
Спасибо @ Хаски. Обновлено.
Blairg23
28

Следующий фрагмент кода загружает файл.

Файл сохраняется с именем файла, как указано в URL.

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

Есть 2 основных способа:

  1. Используя .content( самый простой / официальный) (см. Ответ Женьи Чжана ):

    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. Используя .raw(см . Ответ Мартин Питерс ):

    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.

Сроки оба не показывают заметной разницы.

Wernight
источник
2
Я попробовал кучу ответов, и ваш 1.ответ (с использованием io.BytesIOи Image) был первым, который работал для меня на Python 3.6. Не забывайте from PIL import Imagepip install Pillow).
Коллин
Чем отличается .content от .raw?
foxiris
13

Так же просто, как импортировать изображения и запросы

from PIL import Image
import requests

img = Image.open(requests.get(url, stream = True).raw)
img.save('img1.jpg')
Риккардо Д
источник
4

Вот более удобный ответ, который все еще использует потоковую передачу.

Просто определите эти функции и вызовите getImage(). Он будет использовать то же имя файла, что и URL, и будет записывать в текущий каталог по умолчанию, но оба они могут быть изменены.

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

В requestВнутренности getImage()основаны на ответ здесь и кишках getImageFast()основаны на ответ выше .

Крис Редфорд
источник
3

Я собираюсь опубликовать ответ, так как мне не хватает представителя, чтобы оставить комментарий, но с помощью wget, опубликованной Blairg23, вы также можете указать параметр out для пути.

 wget.download(url, out=path)
justincc
источник
2

Это первый ответ, который появляется в поиске Google о том, как загрузить двоичный файл с запросами. Если вам нужно скачать произвольный файл с запросами, вы можете использовать:

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
источник
1
Ницца! Это даже неявное .close(). Наверное, это лучший ответ на 2019 год.
Даниэль В.
2

Вот как я это сделал

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

Вы можете сделать что-то вроде этого:

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)
Джйотипракаш Дас
источник