---
title: "Tôi Đã Xây Dựng Một Công Cụ Để Chuyển Đổi 500+ Hình Ảnh Sang WebP Trong Một Giờ"
description: "Điểm Lighthouse khóc vì ảnh nặng? Tôi viết script Python ETL chuyển hàng loạt ảnh sang WebP và tự động rewrite toàn bộ URL - đây là cách làm."
pubDate: "2023-05-24"
lang: "vi"
slug: "bulk-webp-migration"
url: "https://hoangyell.com/vi/bulk-webp-migration/"
author: "HoangYell"
translation_en: "https://hoangyell.com/bulk-webp-migration/"
categories: ["Developer Tools"]
tags: ["performance", "python", "webp"]
heroImage: "https://raw.githubusercontent.com/HoangGeek/store/main/webp/photo-1553877522-43269d4ea984.webp"
---

Tôi đang nhìn chằm chằm vào báo cáo Lighthouse của mình như thể nó nợ tôi tiền.

**Hiệu suất: 62.**

Thủ phạm? Hình ảnh. Hàng trăm hình ảnh rải rác trong các tệp markdown, được lưu trữ trên Flickr, Imgur, GitHub... tất cả ở định dạng JPEG và PNG chưa được tối ưu hóa.

Cách sửa thủ công sẽ là:
1. Tải xuống từng hình ảnh
2. Chuyển đổi sang WebP
3. Tải lên CDN của tôi
4. Tìm và thay thế mọi URL trong mọi tệp markdown

Với **500 hình ảnh duy nhất**? Đó không phải là dự án cuối tuần. Đó là án tù.

Vì vậy, tôi đã làm điều mà bất kỳ kỹ sư lười biếng nào cũng làm: Tôi tự động hóa nó.

## Vấn Đề

Blog của tôi sử dụng Hugo với các tệp markdown. Hình ảnh được tham chiếu ở khắp nơi:

```
# Trong frontmatter
image: "https://live.staticflickr.com/65535/54519397357_403fc67f4a_k. jpg"

# Trong shortcodes gallery
< gallery id="example_id">
- https://live.staticflickr.com/65535/54525108024_adbff3cc9b_k. jpg
- https://live.staticflickr.com/65535/54520449879_784f0f24ca_k. jpg
< /gallery >

# Markdown tiêu chuẩn
![Ảnh của tôi](https://i.imgur.com/sXyG3GX. jpeg)
```

Mỗi hình ảnh phải được:
- Tải xuống từ nguồn gốc
- Chuyển đổi sang WebP (nhỏ hơn, nhanh hơn)
- Tải lên CDN mới của tôi
- URL được thay thế trong tệp markdown

Nhân với 500. Không cảm ơn.

## Giải Pháp: Pipeline ETL

Tôi đã xây dựng `bulk-webp-url-replacer` - một công cụ Python làm chính xác những gì nó nói:

```bash
python -m bulk_webp_url_replacer \
  --scan-dir ./content \
  --download-dir ./downloads \
  --output-dir ./webp_images \
  --new-url-prefix "https://cdn.example.com/images" \
  --threads 8
```

**Nó làm gì:**

1. **Extract** - Quét tất cả tệp `.md` để tìm URL hình ảnh (frontmatter, galleries, inline)
2. **Transform** - Tải xuống từng hình ảnh và chuyển đổi sang WebP
3. **Load** - Thay thế tất cả URL cũ bằng đường dẫn CDN mới

Một lệnh. 500 hình ảnh. Xong.

## Các Chi Tiết Kỹ Thuật

### Mẫu Regex Để Trích Xuất URL

Markdown có nhiều cách nhúng hình ảnh. Trình trích xuất của tôi xử lý tất cả:

```python
PATTERNS = [
    # YAML frontmatter: image: "https://..."
    re.compile(r'^image:\s*["\']?(https?://[^"\'>\s]+)["\']?\s*$'),
    # TOML frontmatter: image = "https://..."
    re.compile(r'^image\s*=\s*["\']?(https?://[^"\'>\s]+)["\']?\s*$'),
    # Gallery shortcodes: - https://...
    re.compile(r'^\s*-\s+(https?://[^\s]+\.(jpg|jpeg|png|gif|webp))\s*$'),
    # Standard markdown: <img src="https://..." alt="alt" loading="lazy" decoding="async"  />
    re.compile(r'!\[[^\]]*\]\((https?://[^)]+)\)'),
]
```

### Tải Xuống Song Song

Tải xuống 500 hình ảnh tuần tự? Chậm. Với `ThreadPoolExecutor`:

```python
with ThreadPoolExecutor(max_workers=8) as executor:
    futures = {executor.submit(process_url, url): url for url in urls}
    for future in as_completed(futures):
        # Process results as they complete
```

8 luồng = nhanh hơn 8 lần. Toán học đơn giản.

### Giới Hạn Tốc Độ & Thử Lại

Imgur không vui với sự nhiệt tình của tôi. Lỗi HTTP 429 khắp nơi.

Cách sửa: backoff theo cấp số nhân với headers giống trình duyệt.

```python
HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...',
    'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
}

for attempt in range(max_retries):
    response = requests.get(url, headers=HEADERS, timeout=30)
    if response.status_code == 429:
        time.sleep(2 ** attempt)  # 1s, 2s, 4s...
        continue
```

### Bỏ Qua Thông Minh

Công cụ lưu `mapping.json` sau mỗi lần chạy:

```json
{
  "https://old-url.com/image.jpg": "new-filename.webp"
}
```

Lần chạy tiếp theo? Nó bỏ qua các hình ảnh đã xử lý. Di chuyển gia tăng FTW.

## Kết Quả

**Trước:**
- 612 tham chiếu hình ảnh trên 72 tệp markdown
- Hình ảnh rải rác trên Flickr, Imgur, GitHub
- Lighthouse van xin lòng thương

**Sau:**
- Tất cả hình ảnh được chuyển đổi sang WebP
- Được lưu trữ trên một CDN duy nhất
- URL được cập nhật tự động
- **Một giờ làm việc** (chủ yếu xem thanh tiến trình)

**Cải thiện hiệu suất:**
- Kích thước hình ảnh trung bình: **nhỏ hơn 60-80%**
- Hiệu suất Lighthouse: **62 → 89**

## Bài Học Rút Ra

1. **Tự động hóa mở rộng.** Những gì mất vài ngày thủ công chỉ mất một giờ để xây dựng và vài phút để chạy.

2. **Giới hạn tốc độ là thực tế.** Luôn thêm thử lại và backoff. Các trang như Imgur sẽ hạn chế bạn.

3. **Chạy thử trước.** Cờ `--dry-run` đã cứu tôi khỏi vô tình phá hỏng 72 tệp.

4. **WebP đáng giá.** Chất lượng giống nhau, kích thước nhỏ hơn nhiều. Không có lý do gì để phục vụ JPEG vào năm 2026.

## Hãy Tự Thử

Công cụ mã nguồn mở trên [GitHub](https://github.com/HoangYell/bulk-webp-url-replacer).

```bash
# Xem trước những gì sẽ thay đổi
bulk-webp-url-replacer \
  --scan-dir ./content \
  --download-dir ./downloads \
  --output-dir ./webp \
  --dry-run

# Chạy thực sự
bulk-webp-url-replacer \
  --scan-dir ./content \
  --download-dir ./downloads \
  --output-dir ./webp \
  --new-url-prefix "https://your-cdn.com/images" \
  --threads 8
```

Điểm Lighthouse của bạn sẽ cảm ơn bạn. 🚀

## Ví Dụ Đầu Ra

Sau khi chạy công cụ di chuyển, các URL được cập nhật tự động để trỏ đến các phiên bản WebP được tối ưu hóa:

```
# Trong frontmatter
image: "https://raw.githubusercontent.com/HoangGeek/store/main/webp/54519397357_403fc67f4a_k.webp"

# Trong shortcodes gallery
< gallery id="example_id">
- https://raw.githubusercontent.com/HoangGeek/store/main/webp/54525108024_adbff3cc9b_k.webp
- https://raw.githubusercontent.com/HoangGeek/store/main/webp/54520449879_784f0f24ca_k.webp
< /gallery >

# Markdown tiêu chuẩn
<Gallery items={["https://raw.githubusercontent.com/HoangGeek/store/main/webp/sXyG3GX.webp"]} rows={1} />
```
