Fri Jun 26 2026 20:00:00 GMT-0400 (北美东部夏令时间)
Hướng dẫn nén ảnh bằng Python sử dụng Pillow, WebP và mozjpeg
Nén các định dạng ảnh JPEG, PNG và WebP trong Python bằng cách sử dụng thư viện Pillow, cwebp và mozjpeg. Hỗ trợ cài đặt chất lượng, vòng lặp kích thước mục tiêu và script batch tốc độ cao.

Last updated: June 27, 2026
Bạn có một thư mục các file JPEG kích thước camera, một bước build để gửi chúng đến người dùng, và một ngân sách page-weight liên tục bị vượt quá. Việc nén thủ công những file đó bằng trình chỉnh sửa ảnh không thể mở rộng quy mô được. Hướng dẫn này sẽ chỉ cho bạn chính xác đoạn Python cần thiết để thu nhỏ các file JPEG, PNG và WebP theo lịch trình, trong một job CI, hoặc bên trong bộ xử lý upload.
Câu trả lời nhanh: làm thế nào để nén ảnh bằng Python?
Mở ảnh bằng Pillow, sau đó lưu nó với các cờ định dạng (format flags) phù hợp. Đối với ảnh chụp, hãy lưu dưới dạng JPEG với quality=80, optimize=True, và progressive=True. Đối với các file hiện đại nhỏ nhất, hãy lưu dưới dạng WebP với quality=80, method=6. Đối với đồ họa phẳng và ảnh chụp màn hình, giữ PNG và truyền optimize=True.
from PIL import Image
def compress_jpeg(src, dst, quality=80):
img = Image.open(src)
if img.mode in ("RGBA", "P"):
img = img.convert("RGB")
img.save(dst, "JPEG", quality=quality, optimize=True, progressive=True)
Hàm bốn dòng đó đã cắt giảm 40 đến 70 phần trăm hầu hết các file JPEG từ điện thoại và DSLR. Phần còn lại của hướng dẫn này bổ sung thêm các mục tiêu kích thước, WebP, mozjpeg, và một bộ chạy hàng loạt (batch runner) để bạn có thể trỏ nó vào một thư mục và bỏ đi.
Bạn cần gì trước khi bắt đầu?
Bạn cần Python 3.9 trở lên và Pillow, bản fork được bảo trì của PIL. Pillow xử lý JPEG, PNG và WebP ngay lập tức trên mọi nền tảng phổ biến.
pip install Pillow
Để có đầu ra nhỏ nhất, bạn cũng sẽ muốn hai bộ mã hóa dòng lệnh (command-line encoders) mà Python có thể gọi: cwebp từ libwebp và cjpeg từ mozjpeg.
brew install webp mozjpeg # macOS; Linux: apt install webp, build mozjpeg
Hãy kiểm tra mục đích của từng thành phần trước khi viết mã:
- Pillow đọc và ghi mọi định dạng và thực hiện việc thay đổi kích thước (resizing).
- cwebp tạo ra các file WebP nén chặt nhất và cung cấp nhiều tùy chỉnh hơn Pillow.
- mozjpeg tái mã hóa JPEG nhỏ hơn tiêu chuẩn libjpeg mà Pillow sử dụng.
- Một helper báo cáo để mỗi lần chạy in ra số byte bạn đã tiết kiệm được.
Đây là helper được sử dụng xuyên suốt hướng dẫn này:
import os
def report(src, dst):
before, after = os.path.getsize(src), os.path.getsize(dst)
saved = (1 - after / before) * 100
print(f"{before/1024:.0f} KB -> {after/1024:.0f} KB ({saved:.0f}% smaller)")
Nếu bạn chỉ cần nén một lần và không muốn cài đặt bất cứ thứ gì, image compressor dựa trên trình duyệt cũng thực hiện công việc tương tự cho một số ít file.
Làm thế nào để nén JPEG bằng Pillow?
Ba cờ quan trọng đối với JPEG là quality, optimize, và progressive. quality kiểm soát mức độ chi tiết mà bộ mã hóa loại bỏ. optimize=True chạy một lần truyền thứ hai để xây dựng bảng Huffman nhỏ hơn. progressive=True sắp xếp lại file sao cho nó vẽ từ trên xuống dưới ở độ phân giải thấp trước, điều này cảm thấy nhanh hơn trên các kết nối chậm và thường làm giảm kích thước file thêm một chút nữa.

Hãy chọn giá trị chất lượng dựa trên nơi hình ảnh được sử dụng, chứ không phải một con số toàn cầu duy nhất:
| Quality value | Typical use | What to expect |
|---|---|---|
| 90-95 | Hero images, print proofs | Near-original, noticeably larger files |
| 75-85 | Most web photos | The safe default, large savings |
| 60-70 | Thumbnails and previews | Visible softening on fine detail |
| Below 50 | Avoid for photos | Blocky artifacts around edges |
Một lỗi thường gặp khiến hầu hết mọi người mắc phải: JPEG không có kênh alpha (alpha channel). Nếu bạn mở một ảnh chụp màn hình PNG có độ trong suốt và lưu nó trực tiếp sang JPEG, Pillow sẽ báo cannot write mode RGBA as JPEG. Lệnh convert("RGB") trong hàm trước đó đã làm phẳng độ trong suốt lên một nền màu đặc, đó là lý do tại sao nó chạy trước mọi lần lưu JPEG.
Nén PNG và chuyển đổi sang WebP
PNG là định dạng không mất dữ liệu (lossless), vì vậy bạn không thể đánh đổi chất lượng lấy kích thước như khi dùng JPEG. Điều bạn có thể làm là chạy bộ tối ưu hóa của Pillow và, đối với bất cứ thứ gì thực sự là ảnh chụp, hãy thay đổi hoàn toàn định dạng.
def compress_png(src, dst):
Image.open(src).save(dst, "PNG", optimize=True, compress_level=9)
def to_webp(src, dst, quality=80):
img = Image.open(src)
img.save(dst, "WEBP", quality=quality, method=6)
method=6 yêu cầu bộ mã hóa WebP dành nhiều thời gian hơn để tìm kiếm một file nhỏ hơn. Nó chậm hơn cho mỗi ảnh nhưng đáng giá trong một bước build. Đối với một bức ảnh ban đầu là PNG 2 MB, WebP ở chất lượng 80 thường nằm dưới 200 KB mà trông giống hệt khi xem bình thường. Các phép đo của Google tự thân đã đặt WebP nhỏ hơn khoảng 25 đến 35 phần trăm so với JPEG tương đương, theo WebP documentation.
Sử dụng bảng này để chọn định dạng thay vì đoán:
| Source content | Best format | Why |
|---|---|---|
| Photograph | WebP or JPEG | Smooth gradients compress well lossy |
| Logo, icon, flat UI | PNG or WebP lossless | Sharp edges stay crisp |
| Screenshot with text | PNG or lossless WebP | Avoids fuzzy text from lossy passes |
| Image needing transparency | WebP or PNG | JPEG cannot store an alpha channel |
Nếu bạn vẫn đang phân vân giữa các định dạng cho toàn bộ trang web, bài phân tích trong WebP vs JPEG vs PNG bao gồm hỗ trợ trình duyệt và sự đánh đổi chi tiết hơn.
Đạt kích thước file mục tiêu trong một vòng lặp
"Làm sao để dưới 200 KB" là yêu cầu phổ biến từ các công cụ email, marketplace, và giới hạn upload của CMS. Bạn không thể chọn một con số chất lượng nào mà đạt được mục tiêu byte ngay lần đầu tiên, bởi vì cùng một chất lượng sẽ tạo ra kích thước khác nhau đáng kể cho một bức ảnh phức tạp so với một hình phẳng. Hãy lặp giảm từ chất lượng cao cho đến khi bạn nằm dưới mức giới hạn:
def compress_to_target(src, dst, target_kb=200, q=90, floor=35):
img = Image.open(src).convert("RGB")
while q >= floor:
img.save(dst, "JPEG", quality=q, optimize=True, progressive=True)
if os.path.getsize(dst) <= target_kb * 1024:
return q
q -= 5
return q
Đối số floor ngăn vòng lặp giảm chất lượng xuống vùng bị nhòe (smear territory). Nếu một ảnh không thể đạt được mục tiêu trên mức sàn, hàm vẫn trả về để bạn có thể ghi lại và thay đổi kích thước ảnh thay vì nén mạnh hơn. Đối với các ngân sách byte cố định như 100 KB hoặc 50 KB, vòng lặp tương tự hoạt động; hướng dẫn chi tiết trong compress images without losing quality giải thích khi nào việc thay đổi kích thước tốt hơn là giảm chất lượng.
Làm thế nào để nén JPEG nhỏ hơn bằng mozjpeg và cwebp?
Pillow lưu các file JPEG với bộ mã hóa libjpeg tiêu chuẩn. mozjpeg là một encoder drop-in từ Mozilla tạo ra các file nhỏ hơn với cùng chất lượng hình ảnh, thường nhỏ hơn thêm 5 đến 15 phần trăm. Vì Pillow không đóng gói nó, hãy gọi binary cjpeg thông qua subprocess. cjpeg của mozjpeg đọc PPM đáng tin cậy, vì vậy hãy làm phẳng ảnh bằng Pillow trước:
import subprocess
def mozjpeg(src, dst, quality=80):
Image.open(src).convert("RGB").save("_tmp.ppm")
subprocess.run(["cjpeg", "-quality", str(quality), "-progressive",
"-optimize", "-outfile", dst, "_tmp.ppm"], check=True)
os.remove("_tmp.ppm")
Đối với WebP, gọi cwebp trực tiếp cung cấp cho bạn các nút điều chỉnh mà Pillow không phơi bày, như -m 6 để nỗ lực tối đa và -sharp_yuv để cạnh màu sạch hơn:
def cwebp(src, dst, quality=82):
subprocess.run(["cwebp", "-q", str(quality), "-m", "6",
"-sharp_yuv", src, "-o", dst], check=True)
Đây là cách các tùy chọn được sắp xếp để bạn có thể quyết định cài đặt cái gì:
| Tool | Best for | Trade-off |
|---|---|---|
| Pillow | General scripting, every format | Not the smallest JPEG |
| mozjpeg | Smallest progressive JPEG | Extra binary, PPM round-trip |
| cwebp (libwebp) | Tightest WebP control | Separate binary to install |
| pillow-simd | Faster resizing on x86 servers | Harder to build and pin |
Dự án mozjpeg tài liệu hóa bộ mã hóa và các bài kiểm tra hiệu suất của nó trong mozjpeg repository, và danh sách đầy đủ các tùy chọn lưu của Pillow nằm trong Pillow image file formats reference.
Làm thế nào để nén hàng loạt một thư mục nhanh chóng?
Một ảnh đơn lẻ thì dễ. Nhiệm vụ thực sự là một thư mục chứa hàng trăm ảnh. Thay đổi kích thước bất cứ thứ gì rộng hơn nhu cầu layout của bạn, sau đó nén, và chạy công việc trên các luồng (threads) để một thư mục 500 ảnh hoàn thành trong vài giây thay vì vài phút.

from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
def batch(in_dir, out_dir, quality=80, max_width=1920):
out = Path(out_dir); out.mkdir(parents=True, exist_ok=True)
files = [p for p in Path(in_dir).iterdir()
if p.suffix.lower() in {".jpg", ".jpeg", ".png"}]
def work(p):
img = Image.open(p)
if img.width > max_width:
h = round(img.height * max_width / img.width)
img = img.resize((max_width, h), Image.LANCZOS)
img.convert("RGB").save(out / f"{p.stem}.jpg", "JPEG",
quality=quality, optimize=True, progressive=True)
with ThreadPoolExecutor() as pool:
list(pool.map(work, files))
return len(files)
Một vài lưu ý thực tế khi chạy cái này trong môi trường sản xuất:
- Thay đổi kích thước trước khi nén. Việc phục vụ một ảnh 6000 px cho cột 1200 px lãng phí byte mà không cài đặt chất lượng nào có thể khắc phục được.
- Pillow giải phóng GIL trong quá trình mã hóa, vì vậy luồng (threads) mang lại tốc độ thực tế ở đây; bạn không cần multiprocessing đối với hầu hết các thư mục.
- Viết ra một thư mục đầu ra riêng biệt để một lần chạy lỗi không bao giờ ghi đè lên bản gốc của bạn.
- Sử dụng
Image.LANCZOScho việc giảm kích thước; nó giữ cạnh sắc nét hơn bộ lọc mặc định. - Ghi nhật ký mọi file bằng helper
reportvà giữ lại bản gốc cho đến khi bạn đã xem xét mẫu thử.
Nếu công việc của bạn là định kỳ thay vì một lần, các mẫu trong batch image processing bao gồm hàng đợi (queues), thử lại (retries), và quy ước đặt tên. Đối với chạy không cần code trên một zip file, batch tool và image converter xử lý thay đổi định dạng mà không cần script.
Bạn nên chọn phương pháp nào?
Hãy khớp phương pháp với công việc thay vì luôn tìm đến công cụ nặng nhất:
- Ảnh web trong bước build: Pillow WebP ở chất lượng 80, hoặc cwebp với
-m 6khi bạn muốn vài phần trăm cuối cùng. - Ngân sách byte nghiêm ngặt: vòng lặp
compress_to_target, chuyển sang thay đổi kích thước khi chạm mức sàn. - Các pipeline JPEG cũ không thể thay đổi: tái mã hóa đầu ra bằng mozjpeg để tiết kiệm miễn phí.
- Ảnh chụp màn hình và sơ đồ: giữ PNG với
optimize=True, hoặc WebP lossless nếu trình duyệt của bạn hỗ trợ nó.
Để hiểu lý thuyết đằng sau tại sao các cài đặt này hoạt động, image compression guide giải thích về lossy so với lossless và cách bảng lượng tử hóa (quantization tables) ảnh hưởng đến kết quả.
Các lỗi thường gặp và cách khắc phục
Một số ngoại lệ chiếm phần lớn các lần chạy thất bại:
cannot write mode RGBA as JPEG— gọiimg.convert("RGB")trước khi lưu sang JPEG.Image.DecompressionBombError— Pillow bảo vệ chống lại các ảnh khổng lồ; chỉ nâng giới hạn cho những file bạn tin tưởng vớiImage.MAX_IMAGE_PIXELS = 200_000_000.OSError: broken data stream— file nguồn bị cắt cụt; đặtImageFile.LOAD_TRUNCATED_IMAGES = Trueđể cứu nó, sau đó tải lại khi có thể.- Output lớn hơn input — bạn đã lưu lại một JPEG đã nén; hãy nén từ bản gốc, chứ không phải từ lần xuất trước đó.
- Màu sắc trông bị rửa trôi — nguồn CMYK được lưu mà không chuyển đổi; gọi
convert("RGB")trước.
from PIL import Image, ImageFile
Image.MAX_IMAGE_PIXELS = 200_000_000
ImageFile.LOAD_TRUNCATED_IMAGES = True

Bài học chính
Pillow xử lý 90 phần trăm việc nén ảnh trong Python với ba cờ JPEG và một lệnh lưu WebP. Hãy tìm đến mozjpeg và cwebp khi bạn cần các file nhỏ nhất có thể, bọc mọi thứ trong vòng lặp kích thước mục tiêu khi ngân sách byte cố định, và chạy một thư mục qua hàm batch luồng khi khối lượng tăng lên. Giữ lại bản gốc của bạn, thay đổi kích thước trước khi nén, và in ra mức tiết kiệm trên mỗi lần chạy để bạn luôn biết pipeline thực sự đang làm gì.
Sử dụng các công cụ miễn phí trong khi bạn theo dõi hướng dẫn.
Đọc tiếp

Wed Mar 18 2026 20:00:00 GMT-0400 (北美东部夏令时间)
Bộ Chuyển Đổi WebP: Hướng Dẫn Chuyển Ảnh Sang WebP (Kích Thước Thực)
Chuyển đổi ảnh JPEG và PNG sang định dạng WebP để tối ưu hóa kích thước tệp web. Bài viết bao gồm các kích thước đo thực tế, lệnh cwebp, phương pháp sử dụng Python/trình duyệt, cùng chiến lược dự phòng (fallback) cho JPEG/PNG.

Tue Mar 17 2026 20:00:00 GMT-0400 (北美东部夏令时间)
PNG sang WebP: Hướng dẫn Chuyển đổi và Thu nhỏ Ảnh PNG
Chuyển đổi PNG sang WebP để tối ưu hóa kích thước tệp web. Tìm hiểu khi nào nên dùng WebP không mất dữ liệu (lossless), khi nào dùng kiểu nén có tổn thất (lossy), cùng các lệnh cwebp và Pillow với fallback PNG.

Tue Mar 10 2026 20:00:00 GMT-0400 (北美东部夏令时间)
Tối ưu hóa Image SEO: Danh sách kiểm tra thực tế năm 2026
Danh sách kiểm tra Image SEO thực tế cho năm 2026: bao gồm alt text, tên tệp, định dạng, nén, Core Web Vitals, structured data và đo lường.