Requests и Pillow: получаем изображение по HTTP и делаем миниатюру
Автор: Казачкин Даниил Михайлович · Обновлено
Получить картинку по HTTP и уменьшить её — задача двух библиотек: Requests работает с сетевым ответом, Pillow декодирует и изменяет изображение. Практикум соединяет их через небольшой сервер на вашем компьютере. Вы полностью контролируете входные данные и можете воспроизвести как успешный ответ, так и ошибку.
Установка Requests и Pillow
В активированном проектном окружении:
python -m pip install requests pillow
python -m pip checkPillow импортируется через from PIL import Image; команда pip install PIL не является установкой этого дистрибутива. Requests предоставляет модуль requests. Убедитесь, что рядом со скриптом нет файлов requests.py или каталога PIL, заслоняющих библиотеки.
Учебный сервер и клиент в одном файле
Сохраните http_images.py. Сервер слушает только 127.0.0.1; порт выбирает операционная система. Изображение создаётся программой и не скачивается с внешнего сайта.
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from io import BytesIO
from threading import Thread
import requests
from PIL import Image
source = BytesIO()
Image.new("RGB", (200, 120), (40, 130, 210)).save(source, format="PNG")
payload = source.getvalue()
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path != "/image.png":
self.send_error(404)
return
self.send_response(200)
self.send_header("Content-Type", "image/png")
self.send_header("Content-Length", str(len(payload)))
self.end_headers()
self.wfile.write(payload)
def log_message(self, format, *args):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
address = f"http://127.0.0.1:{server.server_port}/image.png"
with requests.get(address, timeout=(2, 5)) as response:
response.raise_for_status()
if response.headers.get("Content-Type") != "image/png":
raise ValueError("Ожидалось PNG-изображение")
with Image.open(BytesIO(response.content)) as image:
image.thumbnail((80, 80), Image.Resampling.LANCZOS)
image.save("thumbnail.png")
print("HTTP:", response.status_code)
print("Миниатюра:", image.size)
finally:
server.shutdown()
server.server_close()
thread.join()Запустите python http_images.py. Ожидаемый результат:
HTTP: 200
Миниатюра: (80, 48)Создаётся thumbnail.png. В finally сервер прекращает работу, сокет закрывается, поток завершается. Поэтому повторный запуск не зависит от оставленного вручную процесса. http.server здесь используется как учебная заглушка; справочник стандартного сервера находится в документации Python.
Проверка ответа до обработки пикселей
raise_for_status() превращает HTTP-ошибку в исключение. Заголовок проверяется до открытия тела как картинки, а сам декодер Pillow дополнительно проверяет формат данных. Одно расширение .png в адресе ничего не доказывает. Параметр timeout=(2, 5) задаёт ожидания соединения и чтения; это не общий пятисекундный срок всей операции. Requests Quickstart.
thumbnail((80, 80)) сохраняет пропорции. Исходные 200×120 уменьшаются до 80×48, а не растягиваются в квадрат. Если нужен квадрат 80×80, придётся отдельно выбрать правило обрезки или добавления полей — это другой контракт результата. Pillow Tutorial.
Три независимых класса сбоев
- Сервер недоступен: Requests сообщает ошибку соединения или timeout; до Pillow выполнение не доходит.
- Сервер вернул 404: соединение состоялось, но
raise_for_status()отклоняет ответ. - Ответ 200 содержит неверные байты: заголовок ещё может выглядеть правильным, однако декодирование изображения завершается ошибкой.
Такое разделение помогает не «лечить сеть» переустановкой Pillow. В этом небольшом опыте размер ответа заранее ограничен собственным изображением. При переходе к произвольным внешним файлам понадобится отдельный лимит скачиваемых байтов и размеров изображения; заголовок Content-Length сам по себе не обеспечивает такой лимит.
Практика: управляемые ошибки
Замените адрес /image.png на /missing.png и убедитесь, что программа получает HTTP-ошибку. Затем верните адрес, но отправьте из обработчика b"not an image": это уже ошибка декодера. Поймайте исключения в разных ветках и напечатайте разные сообщения, сохранив закрытие сервера в finally.
Для третьего опыта создайте исходник 120×200. Ожидайте миниатюру 48×80. Проверка пройдена, если вы можете объяснить каждую пару размеров и отличить сетевой сбой от неверного содержимого ответа. Локальный HTTP-запрос и обработка PNG исполнены в Linux; внешних запросов пример не делает.