Menu

CSV trong Python: đọc và ghi file CSV bằng module csv

Cách đọc và ghi file CSV trong Python: module csv, DictReader và DictWriter, xử lý dòng tiêu đề, dấu ngoặc kép, và khi nào nên chuyển sang pandas.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

CSV đơn giản hơn bạn nghĩ (và cũng rắc rối hơn bạn tưởng)

Một tệp CSV chỉ là văn bản: các hàng ngăn cách bằng ký tự xuống dòng, các trường ngăn cách bằng dấu phẩy. Ý tưởng là vậy. Trên thực tế, bạn sẽ gặp những chuỗi trong ngoặc kép có chứa dấu phẩy, những trường chứa ký tự xuống dòng, quy ước khác nhau giữa các vùng (dấu phẩy hay dấu chấm phẩy), những tệp lưu từ Excel có kèm BOM. Đủ trường hợp đặc biệt để việc tự viết line.split(",") gần như luôn là sai lầm.

Module csv có sẵn của Python xử lý hết những chuyện đó. Với tệp nhỏ và vừa, bạn hiếm khi cần thêm gì khác.

Đọc CSV bằng csv.reader

csv.reader trả về mỗi hàng dưới dạng một list các chuỗi:

import csv

with open("people.csv", newline="") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

Vài chi tiết không dễ thấy:

  • Luôn truyền newline="" cho open. Module csv tự xử lý ký tự xuống dòng; thiếu đối số này, bạn sẽ có thêm các hàng trống trên Windows.
  • Mọi giá trị đều là chuỗi. "42" vẫn là chuỗi cho tới khi bạn gọi int(...) trên nó. CSV không có kiểu dữ liệu.
  • Hàng tiêu đề cũng chỉ là một hàng bình thường. Nếu tệp có tiêu đề, hãy tự bỏ qua hàng đầu tiên hoặc chuyển sang DictReader.

Bỏ qua hàng tiêu đề

import csv

with open("people.csv", newline="") as f:
    reader = csv.reader(f)
    headers = next(reader)         # pulls the first row out
    for row in reader:
        print(row)

next(reader) đẩy iterator lên một bước và trả về hàng đó.

Đọc thành dict với DictReader

csv.DictReader coi hàng đầu tiên là tiêu đề và trả về mỗi hàng tiếp theo dưới dạng một dict:

import csv

with open("people.csv", newline="") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["email"])

Đây gần như luôn là thứ bạn cần. Tên cột tự giải thích ý nghĩa, và việc đổi thứ tự cột trong tệp nguồn không làm hỏng code của bạn.

Nếu tệp không có tiêu đề, hãy truyền chúng vào một cách tường minh với fieldnames=["name", "email", ...].

Ghi CSV bằng csv.writer

csv.writer biến các hàng (list) thành các dòng CSV:

import csv

rows = [
    ["name", "age", "city"],
    ["Rosa", 30, "Lisbon"],
    ["Ada", 36, "London"],
]

with open("out.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerows(rows)

writerow(row) ghi một hàng; writerows(rows) ghi cả một iterable cùng lúc. Cả hai tự động đặt trường vào ngoặc kép khi trường đó chứa dấu phẩy, dấu ngoặc kép hoặc ký tự xuống dòng, nên bạn không phải bận tâm.

Ghi dict bằng DictWriter

Khi dữ liệu đã ở dạng dict, DictWriter giúp bạn bỏ qua bước "chuyển sang list":

import csv

people = [
    {"name": "Rosa", "age": 30, "city": "Lisbon"},
    {"name": "Ada", "age": 36, "city": "London"},
]

with open("out.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "age", "city"])
    writer.writeheader()
    writer.writerows(people)

Đối số fieldnames quyết định cả dòng tiêu đề lẫn thứ tự cột. Những khóa trong dict không có trong fieldnames sẽ bị bỏ đi một cách lặng lẽ (hoặc bạn có thể cho ném lỗi bằng extrasaction="raise").

Dấu phân cách khác và dấu ngoặc kép

Không phải "CSV" nào cũng dùng dấu phẩy. Các vùng ở châu Âu thường dùng ;, tệp phân cách bằng tab dùng \t, một số hệ thống dùng |. Hãy truyền đối số delimiter=:

import csv

with open("data.tsv", newline="") as f:
    reader = csv.reader(f, delimiter="\t")
    for row in reader:
        print(row)

Với những tệp có quy tắc đặt ngoặc kép khác thường, csv.register_dialect(...) cho phép bạn cấu hình một lần rồi dùng lại. Với phần lớn tệp, các giá trị mặc định cộng với delimiter= là đủ.

Mã hóa ký tự

Tệp CSV là văn bản, nên có bảng mã (encoding). UTF-8 là mặc định hiện nay; tệp xuất từ Excel trên Windows đôi khi dùng cp1252 hoặc có kèm BOM UTF-8. Hãy ghi rõ:

with open("data.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    ...

Nếu bạn gặp UnicodeDecodeError, tệp không dùng bảng mã bạn đoán. Hãy thử những "nghi phạm" quen thuộc: utf-8-sig (xử lý BOM của Excel), cp1252 hoặc latin-1.

Chuyển hàng CSV thành kiểu dữ liệu hữu ích

Vì mọi giá trị đều đến dưới dạng chuỗi, việc chuyển đổi là phần của bạn:

(StringIO cho phép chạy ví dụ mà không cần tệp thật; trong code thực tế bạn sẽ dùng open(path).)

Với CSV có kiểu dữ liệu khó (ngày tháng, số có thể rỗng, true/false viết theo mười kiểu khác nhau), hãy cân nhắc pandas: nó có sẵn quy ước cho hầu hết các trường hợp đó.

Khi nào nên dùng pandas

pandas.read_csv(path) trả về một DataFrame, cấu trúc phù hợp ngay khi bạn muốn:

  • Lọc hàng: df[df["active"] == True]
  • Tổng hợp: df.groupby("city")["age"].mean()
  • Nối với một bảng khác
  • Ghi ngược ra tệp với định dạng đơn giản
import pandas as pd

df = pd.read_csv("people.csv")
adults = df[df["age"] >= 18]
adults.to_csv("adults.csv", index=False)

Pandas là quá mức cần thiết cho những lần đọc nhỏ, tuần tự, và nó là một thư viện phụ thuộc nặng (cài bằng pip trong một môi trường ảo). Nhưng với bất cứ thứ gì mang hình dạng dữ liệu, đó là công cụ mà phần lớn người phân tích dữ liệu bằng Python tìm đến.

Đọc dần tệp rất lớn

csv.reader vốn đã lười (lazy): nó đọc từng hàng một. Hãy giữ nguyên như vậy bằng cách lặp qua nó (thay vì gọi list(reader) ngay từ đầu), và bộ nhớ sẽ không tăng dù tệp lớn đến đâu:

import csv

with open("huge.csv", newline="") as f:
    reader = csv.DictReader(f)
    error_count = 0
    for row in reader:
        if row["status"] == "error":
            error_count += 1

print(f"Found {error_count} errors.")

Đoạn code đó xử lý tệp 10 GB cũng nhẹ nhàng như tệp 10 KB, miễn là bạn không gom các hàng vào một list.

Vài thói quen nên có

  • Luôn truyền newline="" cho open khi đọc hoặc ghi CSV.
  • Dùng DictReader/DictWriter bất cứ khi nào tệp có tiêu đề: dễ đọc hơn chỉ số nguyên.
  • Ghi rõ bảng mã, nhất là với tệp từ Excel hoặc từ nguồn không phải tiếng Anh.
  • Chuyển kiểu dữ liệu ngay ở bước đọc để code phía sau không phải làm.
  • Dùng tới pandas khi bạn muốn phân tích dữ liệu, chứ không chỉ chuyển nó đi.

Tiếp theo

Giờ bạn đã đọc được JSON và CSV. Kỹ năng thực tế cuối cùng chúng ta sẽ học là lấy dữ liệu qua mạng: đó là tài liệu tiếp theo, về HTTP request với thư viện requests.

Câu hỏi thường gặp

Làm sao đọc file CSV trong Python?

Dùng module csv có sẵn. csv.reader trả về mỗi hàng dưới dạng một list các chuỗi; csv.DictReader lấy hàng đầu tiên làm tiêu đề và trả về mỗi hàng dưới dạng một dict. Mở tệp với newline='' để Python không làm hỏng ký tự xuống dòng: with open('data.csv', newline='') as f:.

Làm sao ghi file CSV trong Python?

Kết hợp csv.writer với một tệp được mở ở chế độ ghi và newline=''. Gọi writer.writerow([...]) cho từng hàng hoặc writer.writerows([[...], [...]]) cho nhiều hàng một lúc. Với dữ liệu dạng dict, hãy dùng csv.DictWriter: nó tự xử lý dòng tiêu đề.

Nên dùng module csv hay pandas?

Dùng csv cho những lần đọc ghi nhanh, những tệp bạn xử lý từng hàng một, và khi bạn không muốn thêm thư viện phụ thuộc. Dùng pandas khi bạn cần lọc, nhóm hoặc nối bảng, hoặc khi tệp đủ lớn để các phép toán vector hóa trở nên quan trọng. Cả hai đọc được cùng loại tệp; lựa chọn phụ thuộc vào việc bạn làm gì với dữ liệu sau khi nạp.

Vì sao file CSV của tôi có dòng trống giữa các hàng trên Windows?

Bạn đã mở tệp mà không có newline=''. Module csv tự ghi ký tự kết thúc dòng của nó; thiếu đối số này, Python sẽ thêm ký tự xuống dòng thừa trên Windows. Luôn mở tệp CSV bằng open(path, newline='') cho cả việc đọc lẫn ghi.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu