Tự động hóa Excel với Python và Openpyxl: Từ định dạng đến bảo mật Workbook

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

Bối cảnh: Cơn ác mộng lúc 2 giờ sáng

Ly cà phê đã nguội ngắt nhưng Slack vẫn báo “ting ting”. Sếp gửi một yêu cầu gấp: “Cần báo cáo doanh thu của 50 chi nhánh, có biểu đồ so sánh, định dạng KPI và phải khóa file gửi đối tác ngay sáng nay”. Nhìn đống CSV lộn xộn từ database, mình biết nếu làm thủ công cho 50 sheet thì sáng mai chỉ có nước xin nghỉ việc. Vừa kiệt sức, vừa cực kỳ dễ sai sót số liệu.

Lúc này, combo Python và Openpyxl là lựa chọn sống còn. Thực tế, một báo cáo chuyên nghiệp không chỉ là đổ dữ liệu vào ô (cell) mà còn cần tính thẩm mỹ và sự nhất quán. Dùng VBA đôi khi sẽ gặp lỗi tương thích hoặc bị trình diệt virus chặn đứng macro. Openpyxl xử lý trực tiếp file .xlsx, không cần cài Excel trên server, cực kỳ phù hợp để chạy trong các cronjob hoặc backend API.

Setup vũ khí

Trước tiên, hãy tạo một môi trường ảo (venv). Mình đã từng nếm trái đắng khi xung đột thư viện trên production chỉ vì lười tạo virtual environment. Đừng đi vào vết xe đổ đó.

# Tạo và kích hoạt môi trường ảo
python3 -m venv venv
source venv/bin/activate

# Cài đặt thư viện
pip install openpyxl

Nếu anh em định chèn thêm hình ảnh hoặc vẽ Chart phức tạp, hãy cài thêm pillow. Với nhu cầu báo cáo số liệu cơ bản, openpyxl là đã đủ cân team.

Triển khai thực chiến

Dưới đây là kịch bản mình thường dùng: Đọc dữ liệu, định dạng tự động, vẽ biểu đồ và chốt hạ bằng mật khẩu bảo vệ.

1. Khởi tạo và xử lý Style

Đừng bao giờ gửi một file trắng nhách với font Calibri mặc định. Hãy tạo ấn tượng bằng một Header chuyên nghiệp có màu nền và chữ trắng rõ ràng. Openpyxl dùng module styles để quản lý việc này rất mượt.

from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side

wb = Workbook()
ws = wb.active
ws.title = "Báo cáo doanh thu"

# Định nghĩa style cho Header
header_font = Font(bold=True, color="FFFFFF", size=12)
header_fill = PatternFill(start_color="4F81BD", end_color="4F81BD", fill_type="solid")
center_aligned = Alignment(horizontal="center", vertical="center")
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'), 
                    top=Side(style='thin'), bottom=Side(style='thin'))

headers = ["Tháng", "Doanh thu (VND)", "Chi phí (VND)", "Lợi nhuận (VND)"]
ws.append(headers)

# Apply style cho dòng đầu tiên
for cell in ws[1]:
    cell.font = header_font
    cell.fill = header_fill
    cell.alignment = center_aligned
    cell.border = thin_border

Trong lúc xử lý data thô, mình thường phải dùng Regex để lọc số điện thoại hoặc format chuỗi. Nếu cần test nhanh các pattern phức tạp mà không muốn chạy lại code Python, anh em có thể dùng công cụ tại toolcraft.app/vi/tools/developer/regex-tester. Nó chạy ngay trên trình duyệt, rất tiện khi đang vội.

2. Đổ dữ liệu và định dạng có điều kiện

Thay vì tính toán trong Excel, mình sẽ tính lợi nhuận ngay trong Python và tô màu đỏ nếu con số bị âm.

data = [
    ["Tháng 1", 150000000, 120000000],
    ["Tháng 2", 200000000, 180000000],
    ["Tháng 3", 100000000, 115000000], # Lỗ 15 triệu
]

for row_idx, row_data in enumerate(data, start=2):
    revenue, cost = row_data[1], row_data[2]
    profit = revenue - cost
    
    # Ghi dữ liệu
    ws.cell(row=row_idx, column=1, value=row_data[0])
    ws.cell(row=row_idx, column=2, value=revenue).number_format = '#,##0'
    ws.cell(row=row_idx, column=3, value=cost).number_format = '#,##0'
    profit_cell = ws.cell(row=row_idx, column=4, value=profit)
    profit_cell.number_format = '#,##0'

    # Highlight nếu lỗ (Lợi nhuận < 0)
    if profit < 0:
        profit_cell.font = Font(color="FF0000", bold=True)

3. Vẽ biểu đồ (Chart) tự động

Sếp thường thích nhìn biểu đồ hơn là dán mắt vào bảng số liệu khô khan. Một biểu đồ cột (Bar Chart) so sánh doanh thu sẽ giúp báo cáo giá trị hơn hẳn.

from openpyxl.chart import BarChart, Reference

chart = BarChart()
chart.type = "col"
chart.title = "So sánh Doanh thu theo Tháng"
chart.y_axis.title = "VND"

# Xác định vùng dữ liệu (Cột B)
data_ref = Reference(ws, min_col=2, min_row=1, max_row=4)
cats_ref = Reference(ws, min_col=1, min_row=2, max_row=4)

chart.add_data(data_ref, titles_from_data=True)
chart.set_categories(cats_ref)
ws.add_chart(chart, "F2")

4. Bảo mật và khóa file

Lưu ý: openpyxl hỗ trợ khóa cấu trúc sheet để tránh người dùng sửa công thức. Tuy nhiên, để đặt mật khẩu mở file (Open Password), anh em cần dùng thêm msoffcrypto-python. Ở mức độ cơ bản, khóa sheet là đủ để bảo vệ tính toàn vẹn của báo cáo.

ws.protection.sheet = True
ws.protection.password = "secure2026"
wb.save("Bao_Cao_Chot_So.xlsx")

Kinh nghiệm thực tế: Đừng để script “tẻo” trên Prod

Sau khi script chạy xong, đừng vội đi ngủ ngay. Với dữ liệu lớn khoảng 100.000 dòng, Openpyxl khá ngốn RAM (có thể lên tới 500MB – 1GB). Hãy cân nhắc dùng mode write_only=True để tối ưu bộ nhớ.

Bên cạnh đó, hãy luôn bọc lệnh save trong khối try...except. Một lỗi rất phổ biến là file đang bị mở bởi người dùng khác, khiến Python không có quyền ghi đè (PermissionError). Cuối cùng, hãy kiểm tra dung lượng file. Nếu data đầu vào hàng triệu dòng mà file output chỉ vài KB, chắc chắn logic của bạn đang gặp vấn đề.

Tự động hóa giúp chúng ta thoát cảnh làm nô lệ cho đống bảng tính. Nhìn script chạy 15 giây thay vì 4 tiếng làm tay, bạn sẽ thấy thời gian bỏ ra học Openpyxl là hoàn toàn xứng đáng.

Share: