Як завантажити зображення за допомогою запитів


367

Я намагаюся завантажити та зберегти зображення з Інтернету за допомогою 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?


15
для використання r.raw вам потрібно встановити stream = True
— clsung

Чи відповідає це на ваше запитання? Завантажте великий файл у python із запитами
— AMC

Відповіді:


516

Можна або використовувати response.rawоб’єкт файлу , або повторити відповідь.

Використання response.rawфайлоподібного об'єкта за замовчуванням не декодує стислі відповіді (за допомогою GZIP або дефляції). Ви можете змусити його декомпресувати для вас будь-який спосіб, встановивши 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' Я розбираю цей рядок на ім'я файлу ... чи є їх чистішим способом?
— Grijesh Chauhan

6
@GrijeshChauhan: так, content-dispositionзаголовок - це спосіб пройти сюди; використовувати cgi.parse_header()для розбору та отримання параметрів; params = cgi.parse_header(r2.headers['content-disposition'])[1]то params['filename'].
— Martijn Pieters

1
Щоб отримати по замовчуванням 128 байт скибки с, вам потрібно перебрати на requests.Responseсебе : for chunk in r: .... Виклик iter_content()без chunk_sizeзаповіту повторюється в 1 байт .
— dtk

@dtk: спасибі, я оновлю відповідь. Ітерація змінилася після того, як я опублікував свою відповідь .
— Martijn Pieters

1
@KumZ дві причини: response.okніколи не було задокументовано, і це відповідає дійсності для будь-якого 1xx, 2xx або 3xx статусу, але лише відповідь на 200 має тіло відповідей.
— Martijn Pieters

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 ​​звітує і робить це за замовчуванням.
— Wernight

8
Якщо встановити r.raw.decode_content = Trueдо того, shutil.copyfileobj(response.raw, out_file)бо by default, decode compressed responses (with GZIP or deflate), таким чином , ви отримаєте нульовий файл зображення.
— Сімін Джі

166

Як щодо цього, швидке рішення.

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
що ти маєш на увазі! f = open("/Users/apple/Desktop/sample.jpg", 'wb')що ти маєш на увазі цим шляхом !? Я хочу завантажити зображення
— посмішка

3
Це відкриває дескриптор файлу в шляху, вказаному в який може бути записаний файл зображення.
— kiranbkrishna

@AndrewGlazkov Я думаю, що це було б більше Pythonic використовуватиif response.ok:
— EndermanAPM

5
response.ok Істинно для будь-якого 1xx, 2xx або 3xx статусу, але лише 200 відповідей мають орган відповідей, як @Martijn Pieters, згаданий у коментарях вище
— anndrey

75

У мене така ж потреба у завантаженні зображень за допомогою запитів. Я спершу спробував відповідь Мартіна Пітерса, і це працює добре. Але коли я зробив профіль на цій простій функції, я виявив, що в ній використовується стільки викликів функцій порівняно з 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

12
Це пояснюється тим, що ви не вказали chunk_sizeпараметр, який за замовчуванням iter_contentдорівнює 1, так само він повторюється над потоком результатів 1 байтом одночасно. Дивіться документацію python-requests.org/en/latest/api/… .
— CadentOrange

9
Це також завантажує всю пам'ять у пам'ять, чого ви можете уникнути. Тут також використовувати немає PIL, просто with open(image_name, 'wb') as outfile: outfile.write(r.content)достатньо.
— Martijn Pieters

3
PILтакож немає в стандартній бібліотеці, що робить це трохи менш портативним.
— jjj

2
@ZhenyiZhang iter_contentповільний, тому що ваш chunk_sizeзанадто маленький, якщо ви збільшите його до 100k, це буде набагато швидше.
— Ван

Це найкраща відповідь. Не завжди найкраще читати файл у пам'яті, але "образи", задані ОП, тобто файли, як правило, становлять менше 4 МБ, тим самим маючи тривіальний вплив на пам'ять.
— Кріс Конлан

51

Це може бути простіше, ніж використання requests. Це єдиний раз, коли я пропоную не використовуватиrequests HTTP-матеріали.

Два вкладиші, використовуючи 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)

Я користувався wgetбез зайвих клопотів. Дякуємо, що заявили про переваги використанняurllib3
— h3xh4wk

1
Зауважте, що ця відповідь стосується Python 2. Для Python 3 вам потрібно це зробити urllib.request.urlretrieve("http://example.com", "file.ext").
— Хаскі

1
Дякую @Husky. Оновлено.
— 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

Є два основні способи:

  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.

Терміни обох не показують помітної різниці.


2
Я спробував купу відповідей, і ваша 1.відповідь (з використанням io.BytesIOта Image) була першою, яка працювала для мене на Python 3.6. Не забувайте from PIL import Image(і pip 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, ви також можете вказати параметр для шляху.

 wget.download(url, out=path)

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)

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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.