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:

AspekCSVExcel (.xlsx)
Isi fileteks polosbinary (ZIP berisi XML)
Sheetsatu sajabanyak sheet per file
Tipe datasemua stringangka, tanggal, teks asli
Koma di dalam nilaiperlu tanda kutipaman karena sel terpisah
Formula dan formattidak adatersimpan 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:

python
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:

text
Tersimpan: produk.xlsx

Method 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:

python
import zipfile

with zipfile.ZipFile("produk.xlsx") as arsip:
    for nama in arsip.namelist():
        print(nama)

Output yang Dihasilkan:

text
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].xml

Setiap 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:

python
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:

text
['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:

python
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:

text
{'id': 'P001', 'nama': 'Kopi Gayo', 'harga': 85000, 'kategori': 'minuman', 'stok': 120}
harga bernilai 85000, bertipe int

Mari kita bedah fungsinya:

  1. Baris 4 (buku = load_workbook(..., read_only=True)): workbook dibuka dalam mode hemat memori; sel dibaca sebagai aliran, bukan dimuat seluruhnya.
  2. Baris 7 (header = next(iterator)): baris pertama sheet diambil sebagai nama kolom, lalu iterator maju ke baris data.
  3. Baris 8 (dict(zip(header, baris))): fungsi zip() memasangkan nama kolom dengan nilai — pola dari chapter zip() — lalu dict() membentuk record, sama seperti hasil csv.DictReader.
  4. Baris 9-10 (finally: buku.close()): workbook read_only menjaga file tetap terbuka di belakang layar; close() memastikannya dilepas.

Untuk mendaftar seluruh sheet beserta ukurannya, iterasi buku.sheetnames:

python
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:

text
produk     11 baris x 5 kolom
ringkasan  5 baris x 2 kolom

Konversi 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:

python
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:

text
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,25

Module 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:

python
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:

text
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: float64

Parameter 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.