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:
| Jebakan | Gejala | Solusi |
|---|---|---|
| Semua nilai bertipe string | qty berupa "2", bukan 2 | konversi eksplisit int() atau float() |
| Koma di dalam nilai | kolom bergeser, jumlah kolom berubah | quoting otomatis oleh module csv |
| Encoding tidak konsisten | UnicodeDecodeError saat membaca | nyatakan encoding="utf-8" di kedua sisi |
| Penanda baris campur | baris kosong muncul di antara data | buka berkas dengan newline="" |
Menulis dan Membaca dengan DictWriter dan DictReader #
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.
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:
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,18000Mari kita telaah baris-baris kode di atas:
- Baris 1 (
import csv): mengimpor modulecsvdari standard library — tidak perlu instalasi tambahan. - Baris 3 (
transaksi = [...]): menyiapkan empat transaksi sebagai list of dictionary, satu dictionary mewakili satu baris CSV. - Baris 10 (
open("transaksi.csv", "w", ...)): mode"w"untuk menulis,newline=""mencegah Python menerjemahkan karakter baris baru dua kali, danencoding="utf-8"menjaga karakter non-ASCII. - Baris 11 (
csv.DictWriter(...)):fieldnamesmenetapkan urutan kolom sekaligus menjadi kunci tiap dictionary yang ditulis. - Baris 12-13 (
writeheader()danwriterows(...)): baris pertama berisi nama kolom, lalu setiap dictionary ditransaksimenjadi satu baris data. - 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:
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:
1 Kopi Robusta str 90000
2 Teh Melati str 45000
3 Gula Aren, 500g str 25000
4 Susu UHT 1L str 36000Kolom 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:
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:
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.