Xử lý file CSV và Excel với Python pandas: So sánh approach và hướng dẫn triển khai thực tế

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

Mình bắt đầu viết script automation đơn giản: đọc một file CSV xuất từ hệ thống monitoring, lọc server có CPU > 80%, gửi alert. Ban đầu khoảng 50 dòng, dùng module csv có sẵn, chạy được. Sau đó yêu cầu tăng dần — cần xử lý thêm file Excel từ team finance, merge dữ liệu từ nhiều nguồn, export báo cáo theo tháng. Đến lúc script phình lên 500 dòng mà vẫn còn dùng csv thuần, mình mới nhận ra mình đang đi nhầm đường.

Tốn gần ba ngày refactor mới xong. Từ đó mình không còn chọn thư viện theo quán tính nữa. Bài này đi thẳng vào câu hỏi thực tế — không phải “pandas là gì” mà là khi nào dùng gìdùng đúng cách ra sao.

So sánh ba approach chính

Python có ba thư viện chính cho bài toán này. Mỗi cái phù hợp một tình huống khác nhau:

Cách 1: Module csv có sẵn (stdlib)

import csv

with open('servers.csv', newline='', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        if float(row['cpu_usage']) > 80:
            print(row['hostname'])

Không cần cài gì thêm, chạy trên mọi môi trường. Nhưng mỗi dòng là một dict thuần Python — muốn tính trung bình CPU của 10.000 dòng thì phải tự viết vòng lặp.

Cách 2: pandas read_csv / read_excel

import pandas as pd

df = pd.read_csv('servers.csv')
high_cpu = df[df['cpu_usage'] > 80]
print(high_cpu[['hostname', 'cpu_usage']])

Cùng kết quả với 3 dòng thay vì 6 dòng, và còn có thể group, merge, aggregate ngay sau đó. Đây là approach mình dùng cho hầu hết tác vụ liên quan dữ liệu dạng bảng.

Cách 3: openpyxl / xlrd thuần (Excel-only)

from openpyxl import load_workbook

wb = load_workbook('report.xlsx')
ws = wb.active
for row in ws.iter_rows(min_row=2, values_only=True):
    hostname, cpu = row[0], row[1]
    print(hostname, cpu)

Khi cần thao tác với formatting của Excel — tô màu ô, merge cell, thêm chart — thì openpyxl là bắt buộc. pandas không giữ lại style khi đọc Excel.

Phân tích ưu nhược điểm

Tiêu chí csv module pandas openpyxl
Cài đặt Không cần pip install pandas pip install openpyxl
Xử lý CSV lớn (>1M dòng) Tốt (streaming) Ổn (cần chunk) Không áp dụng
Tính toán, aggregate Phải tự viết Rất mạnh Yếu
Giữ Excel formatting Không Không
Merge nhiều file Thủ công pd.concat / merge Thủ công
Memory footprint Thấp Cao hơn Trung bình

Điều cần nhớ về pandas: nó load toàn bộ file vào RAM. File 100MB CSV thực tế ngốn khoảng 300–500MB — gấp 3–5 lần kích thước gốc trên disk. Dưới 200MB? Không đáng lo. File hàng GB thì phải dùng chunking, hoặc quay lại csv module stream từng dòng.

Chọn approach nào?

Mình tóm quyết định lại thành bốn trường hợp:

  • Chỉ cần đọc từng dòng, xử lý đơn giản, file có thể rất lớn → dùng csv module
  • Cần lọc, tính toán, merge, export báo cáo — file dưới 500MB → dùng pandas
  • Cần đọc/ghi Excel và giữ nguyên style, màu sắc, chart → dùng openpyxl
  • Cần cả tính toán lẫn export Excel đẹp → pandas xử lý data, openpyxl lo phần styling

Nhìn lại cái script 2000 dòng đó: viết lại bằng pandas từ đầu thì chỉ khoảng 400–500 dòng. Ngay khi dữ liệu có nhiều hơn 3 cột và cần bất kỳ loại tính toán nào, pandas ngay từ đầu rẻ hơn nhiều so với refactor sau.

Hướng dẫn triển khai với pandas

Cài đặt

pip install pandas openpyxl xlrd
# openpyxl: cần cho .xlsx
# xlrd: cần cho .xls (format cũ)

Đọc và kiểm tra dữ liệu

import pandas as pd

# Đọc CSV
df = pd.read_csv('servers.csv', encoding='utf-8')

# Đọc Excel (sheet cụ thể)
df_excel = pd.read_excel('report.xlsx', sheet_name='January')

# Kiểm tra nhanh
print(df.shape)        # (số dòng, số cột)
print(df.dtypes)       # kiểu dữ liệu mỗi cột
print(df.head(5))      # 5 dòng đầu
print(df.isnull().sum()) # đếm giá trị null mỗi cột

Lọc và xử lý dữ liệu

# Lọc dòng
high_cpu = df[df['cpu_usage'] > 80]

# Lọc nhiều điều kiện
alert_servers = df[(df['cpu_usage'] > 80) & (df['status'] == 'running')]

# Chọn cột cụ thể
result = df[['hostname', 'cpu_usage', 'memory_usage']]

# Thêm cột tính toán
df['cpu_category'] = pd.cut(
    df['cpu_usage'],
    bins=[0, 50, 80, 100],
    labels=['normal', 'warning', 'critical']
)

# Group by và aggregate
summary = df.groupby('datacenter').agg({
    'cpu_usage': ['mean', 'max'],
    'hostname': 'count'
}).round(2)

Làm sạch dữ liệu — bước hay bị bỏ qua

# Xóa dòng có giá trị null ở cột quan trọng
df = df.dropna(subset=['hostname', 'cpu_usage'])

# Thay thế null bằng giá trị mặc định
df['memory_usage'] = df['memory_usage'].fillna(0)

# Chuẩn hóa kiểu dữ liệu
df['cpu_usage'] = pd.to_numeric(df['cpu_usage'], errors='coerce')
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')

# Xóa dòng trùng lặp
df = df.drop_duplicates(subset=['hostname'])

Merge nhiều file

import glob

# Merge tất cả CSV trong thư mục
all_files = glob.glob('/data/logs/*.csv')
df_all = pd.concat([pd.read_csv(f) for f in all_files], ignore_index=True)

# Join hai DataFrame theo key (như SQL JOIN)
df_merged = pd.merge(
    df_servers,
    df_owners,
    on='hostname',
    how='left'  # left, right, inner, outer
)

Export ra CSV và Excel

# Ghi CSV
df.to_csv('output.csv', index=False, encoding='utf-8-sig')  # utf-8-sig cho Excel đọc đúng tiếng Việt

# Ghi Excel — nhiều sheet
with pd.ExcelWriter('report.xlsx', engine='openpyxl') as writer:
    df_alert.to_excel(writer, sheet_name='Critical', index=False)
    summary.to_excel(writer, sheet_name='Summary', index=False)
    df_all.to_excel(writer, sheet_name='All Servers', index=False)

Xử lý file lớn: chunking

File log hàng GB thì không thể load hết vào RAM. pandas hỗ trợ đọc theo từng chunk:

chunk_size = 50000  # 50k dòng mỗi lần
results = []

for chunk in pd.read_csv('large_log.csv', chunksize=chunk_size):
    # Xử lý từng chunk
    filtered = chunk[chunk['level'] == 'ERROR']
    results.append(filtered)

# Gộp kết quả
df_errors = pd.concat(results, ignore_index=True)

Cách này giữ memory ổn định — thay vì load 2GB vào RAM, mỗi lúc chỉ cần khoảng 200MB.

Một số pattern mình dùng thường xuyên trong automation

Sau khi refactor cái script 2000 dòng kia, mình rút ra hai helper tái sử dụng nhiều nhất:

import pandas as pd
from pathlib import Path
from datetime import datetime

def load_csv_safe(filepath: str, required_cols: list) -> pd.DataFrame:
    """Đọc CSV và validate columns bắt buộc."""
    df = pd.read_csv(filepath)
    missing = set(required_cols) - set(df.columns)
    if missing:
        raise ValueError(f"Missing columns: {missing}")
    return df

def export_timestamped(df: pd.DataFrame, prefix: str, output_dir: str = ".") -> str:
    """Export CSV với timestamp trong tên file."""
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"{prefix}_{ts}.csv"
    filepath = Path(output_dir) / filename
    df.to_csv(filepath, index=False, encoding='utf-8-sig')
    return str(filepath)

# Sử dụng
df = load_csv_safe('servers.csv', ['hostname', 'cpu_usage'])
output = export_timestamped(df[df['cpu_usage'] > 80], 'high_cpu_alert')
print(f"Exported: {output}")

Script từng crash âm thầm ở dòng float(row['cpu_usage']) vì ai đó paste sai column header vào CSV. Với load_csv_safe(), lỗi đó bị bắt ngay từ bước đầu, kèm message chỉ rõ cột nào bị thiếu. Trong môi trường production, kiểu fail-fast như vậy tiết kiệm thời gian debug hơn bất kỳ pattern nào khác.

Share: