Ekspor dari database, lampiran laporan bank, hingga berkas yang dibuka rekan kerja di Excel — hampir semuanya berbentuk CSV (Comma-Separated Values). Format ini tampak sederhana karena hanya berupa teks dengan koma sebagai pemisah, tetapi kesederhanaan tersebut sering menipu: nilai yang mengandung koma, karakter non-ASCII, dan perbedaan penanda baris baru antar sistem operasi dapat merusak hasil pembacaan. Chapter ini membedah struktur CSV menurut RFC 4180, memakai module csv Python untuk mengelola data transaksi toko kecil, lalu menutup dengan teknik deteksi delimiter otomatis.

Struktur CSV Menurut RFC 4180 #

Dokumen RFC 4180 mendefinisikan aturan dasar CSV: satu baris mewakili satu record, setiap record berisi field yang dipisah delimiter (default koma), dan baris pertama boleh berisi nama kolom (header). Field yang mengandung delimiter, tanda kutip ganda, atau baris baru wajib diapit tanda kutip ganda; tanda kutip di dalam nilai ditulis dua kali. RFC tidak mengatur encoding, jadi praktik modern sepakat memakai UTF-8.

Dunia nyata tidak selalu memakai koma. Excel dengan pengaturan regional Indonesia memakai titik koma karena koma dipakai sebagai tanda desimal, sedangkan sebagian sistem lama memakai tab. Pemisah yang salah membuat satu field terpecah menjadi beberapa kolom. Empat jebakan paling umum beserta solusinya dirangkum berikut:

JebakanGejalaSolusi
Semua nilai bertipe stringqty berupa "2", bukan 2konversi eksplisit int() atau float()
Koma di dalam nilaikolom bergeser, jumlah kolom berubahquoting otomatis oleh module csv
Encoding tidak konsistenUnicodeDecodeError saat membacanyatakan encoding="utf-8" di kedua sisi
Penanda baris campurbaris kosong muncul di antara databuka berkas dengan newline=""

Module csv pada standard library menyediakan DictWriter dan DictReader yang bekerja dengan baris berbentuk dictionary — jauh lebih aman daripada mengurai teks secara manual. Kita mulai dari data transaksi toko kecil dengan lima kolom: id, kasir, produk, qty, dan harga. Nama produk "Gula Aren, 500g" sengaja mengandung koma untuk membuktikan mekanisme quoting bekerja.

python
import csv

transaksi = [
    {"id": 1, "kasir": "Budi", "produk": "Kopi Robusta", "qty": 2, "harga": 45000},
    {"id": 2, "kasir": "Siti", "produk": "Teh Melati", "qty": 3, "harga": 15000},
    {"id": 3, "kasir": "Budi", "produk": "Gula Aren, 500g", "qty": 1, "harga": 25000},
    {"id": 4, "kasir": "Dewi", "produk": "Susu UHT 1L", "qty": 2, "harga": 18000},
]

with open("transaksi.csv", "w", newline="", encoding="utf-8") as berkas:
    tulis = csv.DictWriter(berkas, fieldnames=["id", "kasir", "produk", "qty", "harga"])
    tulis.writeheader()
    tulis.writerows(transaksi)

print(open("transaksi.csv", encoding="utf-8").read())

Output yang Dihasilkan:

text
id,kasir,produk,qty,harga
1,Budi,Kopi Robusta,2,45000
2,Siti,Teh Melati,3,15000
3,Budi,"Gula Aren, 500g",1,25000
4,Dewi,Susu UHT 1L,2,18000

Mari kita telaah baris-baris kode di atas:

  1. Baris 1 (import csv): mengimpor module csv dari standard library — tidak perlu instalasi tambahan.
  2. Baris 3 (transaksi = [...]): menyiapkan empat transaksi sebagai list of dictionary, satu dictionary mewakili satu baris CSV.
  3. Baris 10 (open("transaksi.csv", "w", ...)): mode "w" untuk menulis, newline="" mencegah Python menerjemahkan karakter baris baru dua kali, dan encoding="utf-8" menjaga karakter non-ASCII.
  4. Baris 11 (csv.DictWriter(...)): fieldnames menetapkan urutan kolom sekaligus menjadi kunci tiap dictionary yang ditulis.
  5. Baris 12-13 (writeheader() dan writerows(...)): baris pertama berisi nama kolom, lalu setiap dictionary di transaksi menjadi satu baris data.
  6. Baris 15 (print(open(...).read())): menampilkan isi berkas untuk pembuktian — perhatikan baris ketiga: "Gula Aren, 500g" otomatis diapit tanda kutip ganda sehingga koma di dalam nama produk tidak dianggap pemisah.

Membaca kembali berkas tersebut memakai DictReader, yang mengambil baris pertama sebagai nama kolom dan mengubah setiap baris menjadi dictionary:

python
import csv

with open("transaksi.csv", encoding="utf-8", newline="") as berkas:
    for baris in csv.DictReader(berkas):
        total = int(baris["qty"]) * int(baris["harga"])
        print(baris["id"], baris["produk"], type(baris["qty"]).__name__, total)

Output yang Dihasilkan:

text
1 Kopi Robusta str 90000
2 Teh Melati str 45000
3 Gula Aren, 500g str 25000
4 Susu UHT 1L str 36000

Kolom ketiga pada hasil mencetak str — bukti bahwa DictReader mengembalikan seluruh field sebagai string, termasuk angka. Karena itu total dihitung lewat int(baris["qty"]) * int(baris["harga"]); tanpa konversi eksplisit, operasi matematika akan gagal.

Deteksi Delimiter dengan csv.Sniffer #

Ketika berkas datang dari sistem yang tidak diketahui, class csv.Sniffer dapat menebak delimiter dari cuplikan isinya. Berikut pembuktian dengan berkas berpemisah titik koma:

python
import csv

teks = (
    "id;kasir;produk;qty;harga\n"
    "1;Budi;Kopi Robusta;2;45000\n"
    "2;Siti;Teh Melati;3;15000\n"
    "3;Budi;Gula Aren 500g;1;25000\n"
    "4;Dewi;Susu UHT 1L;2;18000\n"
)
with open("transaksi_titik_koma.csv", "w", encoding="utf-8", newline="") as berkas:
    berkas.write(teks)

with open("transaksi_titik_koma.csv", encoding="utf-8") as berkas:
    dialekt = csv.Sniffer().sniff(berkas.read(), delimiters=",;\t")
    print("delimiter terdeteksi:", repr(dialekt.delimiter))

with open("transaksi_titik_koma.csv", encoding="utf-8", newline="") as berkas:
    baris_pertama = next(csv.DictReader(berkas, delimiter=dialekt.delimiter))
    print(baris_pertama)

Output yang Dihasilkan:

text
delimiter terdeteksi: ';'
{'id': '1', 'kasir': 'Budi', 'produk': 'Kopi Robusta', 'qty': '2', 'harga': '45000'}

Pemanggilan sniff() menerima daftar kandidat delimiter melalui parameter delimiters agar tebakan tidak melenceng, lalu hasil tebakan dipakai sebagai delimiter saat membaca. Sniffer bukan jaminan mutlak untuk berkas berukuran sangat kecil, jadi selalu periksa hasilnya sebelum dipakai di pipeline produksi.

CSV memang universal, tetapi format ini tidak mengenal tipe data dan struktur bersarang. Kedua kebutuhan tersebut dijawab oleh format berbasis teks berikutnya — JSON.