Di banyak kantor Indonesia, “data” identik dengan file Excel: laporan stok, rekap penjualan, sampai master produk dikirim sebagai lampiran .xlsx. Berbeda dengan CSV, file Excel adalah binary yang mengandung banyak sheet, tipe data, dan format visual — dibuka dengan open() biasa hanya menghasilkan sampah byte. Kita memakai library openpyxl untuk membacanya secara programatik, dan pandas ketika butuh analisis cepat. Keduanya bukan standard library, jadi pastikan terpasang lewat pip install openpyxl pandas (materi chapter Menggunakan pip).
Excel vs CSV #
Kedua format ini sering dianggap sama padahal berbeda fundamental:
| Aspek | CSV | Excel (.xlsx) |
|---|---|---|
| Isi file | teks polos | binary (ZIP berisi XML) |
| Sheet | satu saja | banyak sheet per file |
| Tipe data | semua string | angka, tanggal, teks asli |
| Koma di dalam nilai | perlu tanda kutip | aman karena sel terpisah |
| Formula dan format | tidak ada | tersimpan bersama data |
Perbedaan tipe data adalah yang paling terasa: cell Excel yang berisi 85000 benar-benar disimpan sebagai angka, jadi openpyxl dan pandas mengembalikannya sebagai int — tanpa konversi manual seperti pada chapter Membaca Data CSV.
Berkas Latihan #
Buat berkas latihan berisi dua sheet dengan openpyxl — sekaligus contoh singkat cara menulis Excel:
from openpyxl import Workbook
DATA_PRODUK = [
("P001", "Kopi Gayo", 85000, "minuman", 120),
("P002", "Teh Melati", 45000, "minuman", 200),
("P003", "Gula Aren", 32000, "bahan pokok", 75),
("P004", "Beras Pandan Wangi, 5kg", 78000, "bahan pokok", 40),
("P005", "Keripik Singkong", 15000, "camilan", 250),
("P006", "Sirup Markisa", 22000, "minuman", 60),
("P007", "Sambal Bawang", 28000, "camilan", 90),
("P008", "Mie Instan Goreng", 3500, "bahan pokok", 500),
("P009", "Susu Kental Manis", 14500, "minuman", 80),
("P012", "Cangkir Keramik", 55000, "aksesoris", 25),
]
buku = Workbook()
lembar = buku.active
lembar.title = "produk"
lembar.append(["id", "nama", "harga", "kategori", "stok"])
lembar.writerows(DATA_PRODUK)
ringkasan = buku.create_sheet("ringkasan")
ringkasan.append(["kategori", "jumlah_produk"])
for baris in [("minuman", 4), ("bahan pokok", 3), ("camilan", 2), ("aksesoris", 1)]:
ringkasan.append(list(baris))
buku.save("produk.xlsx")
print("Tersimpan: produk.xlsx")Output yang Dihasilkan:
Tersimpan: produk.xlsxMethod append() menambah satu baris; writerows() menambah banyak baris sekaligus. Nama sheet diatur lewat title, dan sheet baru dibuat dengan create_sheet().
xlsx Adalah ZIP Berisi XML #
Sebelum membaca datanya, mari intip mekanisme di balik layar. Sebuah file .xlsx sebenarnya adalah arsip ZIP yang berisi beberapa file XML:
import zipfile
with zipfile.ZipFile("produk.xlsx") as arsip:
for nama in arsip.namelist():
print(nama)Output yang Dihasilkan:
docProps/app.xml
docProps/core.xml
xl/theme/theme1.xml
xl/worksheets/sheet1.xml
xl/worksheets/sheet2.xml
xl/styles.xml
_rels/.rels
xl/workbook.xml
xl/_rels/workbook.xml.rels
[Content_Types].xmlSetiap sheet tersimpan sebagai xl/worksheets/sheetN.xml, sedangkan xl/workbook.xml mencatat daftar sheet. Inilah alasan file Excel tidak bisa dibaca baris per baris seperti teks: datanya harus diurai dari struktur XML di dalam arsip — tugas yang sudah ditangani openpyxl untuk kita (arsip ZIP dibahas pada chapter Bekerja dengan File Biner).
Membaca Sheet dengan openpyxl #
Untuk membaca, muat workbook dengan mode read_only=True. Mode ini hemat memori karena tidak memuat sel format dan formula — penting untuk sheet besar:
from openpyxl import load_workbook
buku = load_workbook("produk.xlsx", read_only=True)
print(buku.sheetnames)
lembar = buku["produk"]
for baris in lembar.iter_rows(min_row=1, max_row=3, values_only=True):
print(baris)
buku.close()Output yang Dihasilkan:
['produk', 'ringkasan']
('id', 'nama', 'harga', 'kategori', 'stok')
('P001', 'Kopi Gayo', 85000, 'minuman', 120)
('P002', 'Teh Melati', 45000, 'minuman', 200)Perhatikan output baris ketiga: 85000 dan 120 sudah berupa int, bukan string — Excel menyimpan tipe data asli. Method iter_rows(values_only=True) mengembalikan tuple nilai per baris; parameter min_row dan max_row membatasi rentang. Kombinasi biasanya kita langsung bungkus menjadi list of dictionary:
from openpyxl import load_workbook
def baca_sheet(nama_berkas, nama_sheet):
buku = load_workbook(nama_berkas, read_only=True)
try:
iterator = buku[nama_sheet].iter_rows(values_only=True)
header = next(iterator)
return [dict(zip(header, baris)) for baris in iterator]
finally:
buku.close()
produk = baca_sheet("produk.xlsx", "produk")
print(produk[0])
print(f"harga bernilai {produk[0]['harga']!r}, bertipe {type(produk[0]['harga']).__name__}")Output yang Dihasilkan:
{'id': 'P001', 'nama': 'Kopi Gayo', 'harga': 85000, 'kategori': 'minuman', 'stok': 120}
harga bernilai 85000, bertipe intMari kita bedah fungsinya:
- Baris 4 (
buku = load_workbook(..., read_only=True)): workbook dibuka dalam mode hemat memori; sel dibaca sebagai aliran, bukan dimuat seluruhnya. - Baris 7 (
header = next(iterator)): baris pertama sheet diambil sebagai nama kolom, lalu iterator maju ke baris data. - Baris 8 (
dict(zip(header, baris))): fungsizip()memasangkan nama kolom dengan nilai — pola dari chapter zip() — laludict()membentuk record, sama seperti hasilcsv.DictReader. - Baris 9-10 (
finally: buku.close()): workbookread_onlymenjaga file tetap terbuka di belakang layar;close()memastikannya dilepas.
Untuk mendaftar seluruh sheet beserta ukurannya, iterasi buku.sheetnames:
from openpyxl import load_workbook
buku = load_workbook("produk.xlsx", read_only=True)
for nama_sheet in buku.sheetnames:
lembar = buku[nama_sheet]
print(f"{nama_sheet:<10} {lembar.max_row} baris x {lembar.max_column} kolom")
buku.close()Output yang Dihasilkan:
produk 11 baris x 5 kolom
ringkasan 5 baris x 2 kolomKonversi Sheet ke CSV #
Sering kali langkah paling praktis adalah mengubah sheet menjadi CSV agar bisa diproses tool lain. Pola kerjanya: baca header, lalu tulis baris demi baris:
import csv
from openpyxl import load_workbook
buku = load_workbook("produk.xlsx", read_only=True)
iterator = buku["produk"].iter_rows(values_only=True)
header = next(iterator)
with open("produk_dari_excel.csv", "w", newline="", encoding="utf-8") as keluar:
penulis = csv.writer(keluar)
penulis.writerow(header)
penulis.writerows(iterator)
buku.close()
print(open("produk_dari_excel.csv", encoding="utf-8").read())Output yang Dihasilkan:
id,nama,harga,kategori,stok
P001,Kopi Gayo,85000,minuman,120
P002,Teh Melati,45000,minuman,200
P003,Gula Aren,32000,bahan pokok,75
P004,"Beras Pandan Wangi, 5kg",78000,bahan pokok,40
P005,Keripik Singkong,15000,camilan,250
P006,Sirup Markisa,22000,minuman,60
P007,Sambal Bawang,28000,camilan,90
P008,Mie Instan Goreng,3500,bahan pokok,500
P009,Susu Kental Manis,14500,minuman,80
P012,Cangkir Keramik,55000,aksesoris,25Module csv otomatis mengutip nama "Beras Pandan Wangi, 5kg" karena mengandung koma — bukti kedua format ini bekerja sama dengan mulus.
Membaca Excel dengan pandas #
pandas meringkas seluruh langkah di atas menjadi satu panggilan, dan mengembalikan DataFrame bertipe data benar:
import pandas as pd
df = pd.read_excel("produk.xlsx", sheet_name="produk")
print(df.dtypes)
print()
print(df.groupby("kategori")["harga"].mean().round(0).sort_index())Output yang Dihasilkan:
id object
nama object
harga int64
kategori object
stok int64
dtype: object
kategori
aksesoris 55000.0
bahan pokok 37833.0
camilan 21500.0
minuman 41625.0
Name: harga, dtype: float64Parameter sheet_name menerima nama sheet; berikan None untuk membaca semua sheet sekaligus dalam dictionary {nama_sheet: DataFrame}.
Dengan CSV, JSON, dan Excel sudah kita kuasai, tiba waktunya membedah format data yang tidak pernah dirancang untuk dibaca manusia: log aplikasi.