Как организовать скачивание, обработку и сохранение больших изображений в Python
Что проверяют
Понимаете ли вы, где при работе с большими файлами кончается память и где процесс упирается в сеть или в процессор. Следом обычно спрашивают, как обработать тысячу таких картинок, а не одну.
Как отвечать
Разложите задачу на три этапа, у каждого своё узкое место.
- скачивание потоком:
requests.get(url, stream=True, timeout=(5, 60))и запись на диск черезiter_content(chunk_size=1024 * 1024), а неresponse.content, который держит весь файл в памяти. Размер проверяют заранее поContent-Lengthи по факту при чтении. Пишут во временный файл и затем переименовывают черезos.replace, так на диске не останется половинок; - обработка:
Image.openв Pillow ленивый и сначала читает только заголовок, так что размеры и формат видны до декодирования. Для превью подходитthumbnail(), он сохраняет пропорции, а у JPEGdraft()позволяет декодировать сразу в уменьшенном масштабе.ImageOps.exif_transposeповорачивает фото по EXIF. Для по-настоящему огромных изображений лучше pyvips: libvips обрабатывает картинку по частям и почти не ест память; - сохранение: формат и параметры под задачу, например JPEG с
quality=85иoptimize=Trueили WebP. Картинку с альфа-каналом перед JPEG переводят вRGB. В объектное хранилище большой файл уходит multipart-загрузкой, вboto3её делаетupload_file.
Для множества файлов: скачивание упирается в сеть, его параллелят через asyncio с httpx или aiohttp, либо потоками. Обработка упирается в процессор, её отдают в ProcessPoolExecutor или отдельным воркерам очереди.
Где спотыкаются
Вызывают requests.get без timeout. По умолчанию таймаута нет, и зависшее соединение держит воркер бесконечно.
Делают resize((800, 800)) для любой картинки. Пропорции ломаются, фото сплющивается.
Отключают защиту Image.MAX_IMAGE_PIXELS, увидев DecompressionBombError. Лимит защищает от маленького файла, который при декодировании займёт гигабайты памяти, и снимать его без своей проверки размера нельзя.