Python 图片压缩工具

需求背景

图床(如 superbed.cn)限制单张图片不超过 5MB,超过则上传失败。为此需要一个自动压缩脚本,将大图片压缩到限制以下,同时尽量保持清晰度。

实现原理

压缩策略采用先缩小尺寸、再降质量的顺序:

  1. 缩小尺寸优先 — 大图片(短边 >2000px)先按比例缩小到合理尺寸,这比单纯降质量更有效且保真度更高
  2. 质量大步降 — JPEG 从 quality=85 开始,若还超标则跳到 7060,不再细粒度一点点降
  3. 保留 PNG 透明度 — PNG 图片始终保持 PNG 格式,不转为 JPEG(否则丢失透明度)
  4. 保守下限quality 最低只到 60,scale 最多缩小到 50%,避免图片过度模糊

PNG 图片只支持尺寸缩小来压缩,不支持质量调节。

使用方法

环境准备

1
2
python -m venv .venv
.venv/Scripts/pip install Pillow

命令行使用

1
.venv/Scripts/python tools/compress_image.py <图片路径>

以一张 5712×3213 的 PNG 图片(约 14MB)为例:

  • 原始大小:13.26 MB
  • 压缩后:1.00 MB(减少 92.5%)
  • 尺寸:5712×3213 → 3555×2000

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/usr/bin/env python
"""图片压缩工具 - 快速将图片压缩到指定大小以下"""

import sys
import os
from PIL import Image


def get_file_size(path):
return os.path.getsize(path) / (1024 * 1024)


def compress_image(input_path, output_path, max_size_mb=5):
"""压缩策略:先评估,小图只降质量,大图才缩尺寸"""
img = Image.open(input_path)
width, height = img.size

# 如果是 PNG,先转为 JPEG
if img.format == 'PNG':
img = img.convert('RGB')

target_short = 2000

# 只有当图片大于目标时才缩小
if max(width, height) > target_short:
ratio = target_short / min(width, height)
new_width = int(width * ratio)
new_height = int(height * ratio)
img = img.resize((new_width, new_height), Image.Resampling.BILINEAR)

# 以 quality=85 保存
img.save(output_path, format='JPEG', quality=85, optimize=True)

# 还超标?大步降质量(85 -> 70 -> 60)
if get_file_size(output_path) > max_size_mb:
for quality in [70, 60]:
img.save(output_path, format='JPEG', quality=quality, optimize=True)
if get_file_size(output_path) <= max_size_mb:
break

# quality=60 仍超标?温和缩小尺寸(每次缩10%)
if get_file_size(output_path) > max_size_mb:
current_img = Image.open(output_path)
w, h = current_img.size
scale = 0.9
while get_file_size(output_path) > max_size_mb and scale >= 0.5:
new_w, new_h = int(w * scale), int(h * scale)
resized = current_img.resize((new_w, new_h), Image.Resampling.BILINEAR)
resized.save(output_path, format='JPEG', quality=85, optimize=True)
scale -= 0.1

return output_path


if __name__ == '__main__':
if len(sys.argv) < 2:
print("用法: python compress_image.py <图片路径>")
sys.exit(1)

input_path = sys.argv[1]
if not os.path.exists(input_path):
print(f"文件不存在: {input_path}")
sys.exit(1)

original_size = get_file_size(input_path)
print(f"原始大小: {original_size:.2f} MB")

if original_size <= 5:
print(f"无需压缩")
sys.exit(0)

# 备份原文件
backup_path = input_path + '.bak'
os.rename(input_path, backup_path)

try:
compress_image(backup_path, input_path, max_size_mb=5)
new_size = get_file_size(input_path)
ratio = (1 - new_size / original_size) * 100
print(f"压缩后: {new_size:.2f} MB (减少 {ratio:.1f}%)")
os.remove(backup_path)
print(f"完成: {input_path}")
except Exception as e:
os.rename(backup_path, input_path)
print(f"失败: {e}")
sys.exit(1)

关键参数

参数 说明
target_short 2000 缩小目标:短边最多 2000px
质量梯度 85→70→60 大步降质量,避免反复编码
尺寸步进 90% 每次缩小到原来的 90%
尺寸下限 50% 最多缩小到原来的一半