Kinh nghiệm thực chiến WeasyPrint: Xuất PDF từ HTML/CSS chuẩn ‘xịn’ trong Python

Python tutorial - IT technology blog
Python tutorial - IT technology blog

Bối cảnh: Cuộc chia tay đẫm nước mắt với wkhtmltopdf

Câu chuyện xảy ra vào lúc 2 giờ sáng. Hệ thống xuất hóa đơn của khách hàng bỗng dưng báo lỗi hàng loạt. Nguyên nhân? Frontend mới cập nhật giao diện dùng Flexbox, nhưng engine wkhtmltopdf cũ kỹ (vốn dựa trên WebKit từ năm 2014) hoàn toàn bất lực trong việc render layout hiện đại. Đứng trước lựa chọn sửa lại CSS về thời kỳ “đồ đá” bằng table hay tìm công cụ mới, mình đã chọn WeasyPrint.

Tại sao lại là WeasyPrint? Khác với pdfkit hay wkhtmltopdf thường xuyên gặp lỗi thiếu thư viện liên kết (dependencies) trên Ubuntu 22.04, WeasyPrint thân thiện với Python hơn hẳn. Nó hỗ trợ CSS3, Flexbox và thậm chí là Grid rất mượt mà. Đặc biệt, bạn không cần cài thêm một trình duyệt headless nặng nề như Chrome để chạy, giúp tiết kiệm khoảng 300-500MB RAM cho server.

Tuy nhiên, đừng lầm tưởng đây là giải pháp “mì ăn liền”. Để nó chạy ổn định trên Production, bạn cần hiểu rõ cách nó tương tác với thư viện hệ thống.

Cài đặt: Vượt qua rào cản thư viện hệ thống

Sai lầm phổ biến nhất là chỉ chạy mỗi pip install weasyprint. Nếu làm vậy, bạn sẽ sớm nhận được thông báo lỗi OSError: cannot load library 'gobject-2.0' cực kỳ khó chịu. WeasyPrint cần các engine render đồ họa như Cairo và Pango để hoạt động.

1. Cài đặt trên Linux (Ubuntu/Debian)

Chỉ cần một dòng lệnh để chuẩn bị đầy đủ “vũ khí” cho server của bạn:

sudo apt-get update
sudo apt-get install build-essential python3-dev python3-pip python3-setuptools python3-wheel python3-cffi libcairo2 libpango-1.0-0 libpangocairo-1.0-0 libgdk-pixbuf2.0-0 libffi-dev shared-mime-info

2. Nỗi ám ảnh mang tên Windows

Cài WeasyPrint trên Windows thường là một cực hình vì lỗi DLL. Cách đơn giản nhất hiện nay là tải bộ GTK for Windows Runtime. Sau khi cài xong, hãy nhớ thêm đường dẫn thư mục bin vào biến môi trường PATH. Nếu không làm bước này, Python sẽ không thể tìm thấy các file thực thi cần thiết.

Thực chiến: Từ code cơ bản đến PDF chuyên nghiệp

Trong thực tế, việc nhồi nhét CSS vào HTML (inline style) là một thảm họa bảo trì. Khi khách hàng muốn đổi màu thương hiệu từ xanh sang đỏ, bạn sẽ phải lục lọi hàng trăm dòng code. Hãy tách biệt chúng ngay từ đầu.

Cách render cơ bản

Đây là phương pháp nhanh nhất để chuyển đổi một chuỗi HTML đơn giản:

from weasyprint import HTML

html_content = """
<h1 style='color: #1a73e8; font-family: sans-serif;'>Hóa đơn dịch vụ</h1>
<p>Cảm ơn bạn đã tin dùng sản phẩm của chúng tôi.</p>
"""

# Xuất file chỉ với 1 dòng code
HTML(string=html_content).write_pdf("invoice.pdf")

Xử lý font Tiếng Việt và CSS nâng cao

Vấn đề gây đau đầu nhất là lỗi font chữ biến thành ô vuông (tofu). WeasyPrint sử dụng Pango để quản lý font, vì vậy bạn cần định nghĩa font-face rõ ràng và đảm bảo font đó đã được cài trên hệ thống.

from weasyprint import HTML, CSS
from weasyprint.text.fonts import FontConfiguration

font_config = FontConfiguration()
css = CSS(string="""
    @font-face {
        font-family: 'Roboto';
        src: url(https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2);
    }
    body { font-family: 'Roboto', Arial, sans-serif; }
    .header { display: flex; justify-content: space-between; border-bottom: 2px solid #eee; }
""")

html = HTML(string='<div class="header"><h1>Báo Cáo</h1><span>Số: #123</span></div>')
html.write_pdf('report.pdf', stylesheets=[css], font_config=font_config)

Tối ưu Production: Để server không bị “treo”

Việc render PDF tiêu tốn rất nhiều tài nguyên. Một file PDF phức tạp có thể khiến CPU tăng vọt lên 80-90% trong vài giây. Nếu có 50 người cùng click xuất báo cáo, server của bạn sẽ “nghẻo” ngay lập tức.

  • Sử dụng Worker: Đừng bao giờ render PDF trực tiếp trong luồng xử lý request. Hãy đẩy tác vụ vào Celery hoặc Redis Queue. Người dùng sẽ nhận được thông báo “Đang xử lý” và tải file sau qua email hoặc link S3.
  • Quản lý ảnh thông minh: Thay vì bắt WeasyPrint tải ảnh từ URL ngoài (dễ gây timeout), hãy dùng ảnh Base64 hoặc đường dẫn file nội bộ. Việc này giúp giảm thời gian render từ 5 giây xuống còn chưa đầy 1 giây.
  • Resize ảnh: Một tấm ảnh 4K nhét vào PDF cỡ A4 là sự lãng phí. Hãy resize ảnh về đúng kích thước cần thiết. Kinh nghiệm của mình cho thấy file PDF có thể giảm từ 10MB xuống còn 200KB chỉ nhờ tối ưu ảnh.

Lời kết

WeasyPrint không chỉ là một công cụ, nó là cứu cánh cho những ai muốn có bản in PDF đẹp mắt mà không muốn quay lại thời kỳ CSS table. Tuy khâu cài đặt ban đầu hơi rắc rối, nhưng sự ổn định và khả năng hỗ trợ CSS hiện đại của nó hoàn toàn xứng đáng để bạn đầu tư. Hãy nhớ: Cài đủ thư viện, cấu hình font chuẩn và luôn chạy ngầm (background task) để có trải nghiệm tốt nhất!

Share: