DEV Community

Cover image for Cắt và chuyển ảnh sang WebP hàng loạt bằng Python trước khi tải lên WordPress
Onic Ai
Onic Ai

Posted on Originally published at onic.vn

Cắt và chuyển ảnh sang WebP hàng loạt bằng Python trước khi tải lên WordPress

Plugin nén ảnh trên WordPress xử lý dung lượng, nhưng không xử lý gốc rễ: ảnh tải lên quá khổ. Ảnh rộng 4000 pixel nén xong vẫn là ảnh 4000 pixel. Bài này viết một script Python nhỏ cắt ảnh về đúng chiều rộng, xuất WebP và cảnh báo ảnh vượt ngân sách dung lượng, chạy trên máy trước khi tải lên.

Vì sao làm ở máy thay vì để plugin

Google viết rằng ảnh thường là phần đóng góp lớn nhất vào dung lượng một trang. Plugin nén chạy sau khi ảnh đã lên máy chủ, và nhiều plugin tính mỗi kích thước ảnh WordPress sinh ra là một lượt nén. Cắt và đổi định dạng trước giúp ảnh gốc nhỏ ngay từ đầu, mọi kích thước sinh ra sau đó đều nhỏ theo.

Ngân sách dung lượng theo vị trí

Google không đưa con số KB chuẩn. Mốc dưới đây lấy từ bài gốc trên Onic.vn, dùng như ngưỡng cảnh báo chứ không phải luật cứng:

NGAN_SACH = {
    "bai-viet":  {"rong": 1200, "kb": 150},
    "dai-dien":  {"rong": 1200, "kb": 100},  # khổ 1200x628
    "san-pham":  {"rong": 1200, "kb": 200},
    "thu-nho":   {"rong": 600,  "kb": 40},
    "banner":    {"rong": 1920, "kb": 250},
}
Enter fullscreen mode Exit fullscreen mode

Script

import sys
from pathlib import Path
from PIL import Image

def xu_ly(thu_muc: str, vi_tri: str = "bai-viet", chat_luong: int = 80):
    ns = NGAN_SACH[vi_tri]
    ra = Path(thu_muc) / "webp"
    ra.mkdir(exist_ok=True)
    for f in sorted(Path(thu_muc).iterdir()):
        if f.suffix.lower() not in {".jpg", ".jpeg", ".png"}:
            continue
        im = Image.open(f)
        if im.width > ns["rong"]:
            cao = round(im.height * ns["rong"] / im.width)
            im = im.resize((ns["rong"], cao), Image.LANCZOS)
        dich = ra / (f.stem.lower() + ".webp")
        lossless = f.suffix.lower() == ".png" and im.mode == "RGBA"
        im.save(dich, "WEBP", quality=chat_luong, lossless=lossless, method=6)
        kb = dich.stat().st_size / 1024
        canh_bao = "  <-- vượt ngân sách" if kb > ns["kb"] else ""
        print(f"{f.name}: {f.stat().st_size // 1024} KB -> {kb:.0f} KB{canh_bao}")

if __name__ == "__main__":
    xu_ly(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "bai-viet")
Enter fullscreen mode Exit fullscreen mode

Vài quyết định trong script

  • Cắt trước, nén sau. Bước cắt về đúng chiều rộng hiển thị là bước giảm dung lượng nhiều nhất.
  • PNG có kênh trong suốt xuất WebP không mất dữ liệu. Theo trang WebP của Google, bản này nhỏ hơn PNG 26%; còn WebP có mất dữ liệu nhỏ hơn JPEG 25–34% ở cùng chất lượng.
  • Không tự hạ chất lượng khi vượt ngân sách. Ảnh chụp màn hình có chữ nhỏ dễ nhòe khi nén mạnh; ảnh bị cảnh báo nên được người xem lại, phóng to vùng có chữ rồi mới quyết định.
  • Tên file chuyển chữ thường. Đặt tên có nghĩa vẫn là việc của người, script không đoán nội dung ảnh.

Muốn thêm AVIF thì cần bản Pillow có hỗ trợ AVIF; lưu ý nén AVIF tốn tài nguyên hơn WebP. WordPress hỗ trợ tải lên AVIF từ bản 6.5.

Phần so sánh 6 công cụ và plugin nén ảnh cho người không viết code, cùng bảng mốc dung lượng đầy đủ, có trong bài Nén ảnh chuẩn SEO trên Onic.vn.


Bài gốc: https://onic.vn/kien-thuc/nen-anh-chuan-seo/

Top comments (0)