Сжатие данных в Python: чек-лист рабочих приёмов
Какой алгоритм сжатия выбрать под задачу, чем zstd отличается от gzip на практике и какие ошибки убивают степень сжатия. Чек-лист для тех, кто уже пишет на Python 3.12.

Вы отдаёте по сети JSON на пару мегабайт и упаковываете его через gzip, потому что так делают все. Он жмёт в семь раз, но тратит на это заметное время, а разжатие на клиенте тормозит. Или наоборот: вы храните архив логов через стандартный zipfile с уровнем по умолчанию и удивляетесь, почему диск забивается быстрее, чем ожидалось. Проблема почти всегда не в самом Python, а в том, что алгоритм и его параметры выбраны наугад.
Этот чек-лист — про то, как выбрать инструмент под конкретную задачу и не потерять степень сжатия на ровном месте. Всё проверялось на Python 3.12, стандартная библиотека плюс пакет zstandard. Смежная тема — потоковое сжатие больших файлов, которые не влезают в память, — вынесена отдельно, здесь работаем с данными, помещающимися в оперативку.
Что есть в стандартной библиотеке
Python из коробки даёт четыре алгоритма, и у каждого свой профиль скорость/степень:
zlib— низкоуровневый, работает с сырыми потоками. Основа для gzip.gzip— обёртка над zlib с заголовком и CRC. Формат.gz, совместим со всем миром.bz2— жмёт сильнее gzip, но заметно медленнее. Хорош для архивов, которые пишутся редко, читаются ещё реже.lzma— формат.xz, максимальная степень сжатия из стандартных, самый прожорливый по CPU и памяти.
Пятый вариант, zstandard (алгоритм Zstd от Facebook), в стандартную библиотеку не входит. Ставится через pip install zstandard. Он даёт близкую к gzip степень сжатия на порядок быстрее — и именно его чаще всего стоит смотреть первым для новых задач.
Базовый уровень: не оставляйте параметры по умолчанию
Самая частая ошибка — вызвать gzip.compress(data) и забыть. Уровень по умолчанию у zlib и gzip — 9 (максимум), а вот у Zstd — 3, что почти всегда недобор. Проверяйте, что вы передаёте уровень осознанно.
import gzip
import zlib
data = b"..." * 100000 # ваши байты
# gzip: уровень 1 (быстро) ... 9 (сильно), по умолчанию 9
low = gzip.compress(data, compresslevel=1)
high = gzip.compress(data, compresslevel=9)
# zlib напрямую, без gzip-заголовка — на 18 байт меньше на каждый блок
raw = zlib.compress(data, level=6)
print(len(low), len(high), len(raw))
Разница между уровнем 1 и 9 у gzip на текстовых данных — обычно 10–20% размера при кратной разнице во времени. Если вы жмёте на лету перед отправкой в сеть, уровень 9 может оказаться дороже, чем сэкономленный трафик.
Zstd: то, что стоит попробовать в первую очередь
Zstd покрывает диапазон уровней от 1 до 22 и умеет то, чего нет у стандартных модулей, — обучаемый словарь для множества мелких однотипных сообщений.
import zstandard as zstd
data = b"..." * 100000
cctx = zstd.ZstdCompressor(level=10)
compressed = cctx.compress(data)
dctx = zstd.ZstdDecompressor()
restored = dctx.decompress(compressed)
assert restored == data
Уровни 19–22 у Zstd приближаются к lzma по степени, оставаясь быстрее на разжатии. Для потоков API-ответов уровень 6–10 обычно даёт лучший компромисс: жмёт близко к gzip -9, а времени тратит в разы меньше.
Правило, которое экономит часы: сначала измерьте свои реальные данные на трёх-четырёх уровнях, потом выбирайте. Синтетические бенчмарки из интернета сделаны не на ваших JSON и не на ваших логах.
Словарь для мелких сообщений
Если вы жмёте тысячи маленьких похожих объектов по отдельности (например, события телеметрии), обычное сжатие каждого проигрывает: заголовок и таблицы занимают долю от крошечного тела. Обученный словарь решает это — общие фрагменты выносятся в него один раз.
import zstandard as zstd
# samples — список bytes с типичными сообщениями
samples = [b'{"event":"click","x":10}', b'{"event":"click","x":42}']
dict_data = zstd.train_dictionary(16384, samples)
cctx = zstd.ZstdCompressor(dict_data=dict_data, level=10)
small = cctx.compress(b'{"event":"click","x":7}')
Словарь придётся хранить и передавать вместе с данными или зашить в обе стороны. Точную сигнатуру train_dictionary для вашей версии пакета сверьте в документации zstandard — API менялся между мажорными релизами.
Сравнение по профилям
Цифры ниже — порядок величины на текстовых/JSON-данных, а не гарантия. Точные значения зависят от энтропии ваших данных, замеряйте на своих.
| Алгоритм | Степень | Скорость сжатия | Скорость разжатия | Когда брать |
|---|---|---|---|---|
| zlib / gzip | средняя | средняя | высокая | совместимость, HTTP, .gz |
| bz2 | выше gzip | низкая | средняя | редкие архивы |
| lzma / xz | максимальная | очень низкая | средняя | долгое хранение, дистрибутивы |
| zstd (6–10) | ~gzip | высокая | очень высокая | сеть, кеши, потоки — почти всё |
| zstd (19–22) | ~lzma | низкая | очень высокая | жмём один раз, читаем часто |
Чек-лист: что проверить перед выбором
- Данные уже сжаты? JPEG, PNG, MP4, PDF, готовые архивы почти не жмутся. Повторное сжатие тратит CPU впустую и иногда даёт размер больше исходного. Проверьте по типу контента до вызова компрессора.
- Сколько раз читаете против скольки пишете? Пишете раз, читаете миллион — берите высокий уровень, время сжатия окупится. Жмёте на каждый запрос — экономьте CPU.
- Данные текстовые или бинарные? Текст и JSON жмутся в 5–10 раз, случайные байты — почти нет. Числовые массивы часто лучше сначала привести к колоночному виду, а потом сжать.
- Много мелких однотипных сообщений? Смотрите обучаемый словарь Zstd вместо сжатия каждого по отдельности.
- Нужна совместимость с внешними системами? Если файл читает не только ваш код, а браузер или сторонний сервис — gzip/zlib безопаснее, Zstd поддержан не везде.
- Замерили на своих данных? Три уровня, реальный образец,
len()результата и время. Без этого выбор — гадание.
Ошибки, которые убивают степень сжатия
Сжатие уже сжатого
Оборачивать .gz в .zip или гнать PNG через gzip — классика. Файл не уменьшится, а CPU потратится. Отсекайте по типу или расширению до сжатия.
Сжатие каждого мелкого объекта отдельно
Тысяча вызовов gzip.compress на тысячу коротких строк даст суммарный размер больше, чем один вызов на объединённых данных. Собирайте в блок, потом жмите — или используйте словарь.
Игнор кодировки
Компрессоры работают с bytes. Если вы каждый раз делаете str.encode('utf-8') — это нормально, но следите, чтобы на входе была одна кодировка. Смешение UTF-8 и cp1251 в одном потоке снижает предсказуемость.
Как замерить у себя
Не доверяйте таблицам, включая мою. Возьмите репрезентативный образец своих данных и прогоните цикл по уровням, глядя на два числа: итоговый размер и время. Для времени используйте time.perf_counter вокруг вызова сжатия, для памяти при больших объёмах — tracemalloc. Разжатие меряйте отдельно: у Zstd оно почти не зависит от уровня сжатия, у lzma — зависит.
Что делать дальше
Начните с замера: возьмите один реальный файл или пачку сообщений и сравните gzip -6, gzip -9 и zstd -10 по размеру и времени на своей машине. Если данные не влезают в память — переходите к потоковым API (gzip.open, zstd.ZstdCompressor().stream_writer), это отдельный разговор про буферы и чанки. Если жмёте числовые массивы — смотрите в сторону специализированных форматов вроде Parquet, где сжатие идёт по колонкам и работает заметно лучше построчного.
Тендерный специалист 44-ФЗ / 223-ФЗ: Разбор PDF-документации закупки, поиск «заточек» под поставщика, чек-лист заявки.
Спросить за 20 ₽Источники: Python docs — Data Compression and Archiving, zstandard — Python bindings documentation
Частые вопросы
У меня старая версия пакета zstandard, примеры не работают
Почему сжатый файл получился больше исходного?
Что выбрать, если нужна максимальная совместимость?
Зачем нужен обучаемый словарь и когда он вреден?
Насколько уровень сжатия влияет на скорость разжатия?
Стоит ли жать данные перед отправкой в базу или кеш?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту. Instagram, Facebook и WhatsApp принадлежат компании Meta Platforms Inc., признанной в России экстремистской организацией; её деятельность на территории Российской Федерации запрещена.