%%{init: {"flowchart": {"diagramPadding": 40}}}%%
flowchart TD
A(["Kirim permintaan"]) --> B{"Status?"}
B -->|"2xx / 4xx"| C(["Selesai: pakai atau laporkan"])
B -->|"5xx atau jaringan putus"| D{"Percobaan habis?"}
D -->|"belum"| E["Tunggu, lalu gandakan jeda"]
E --> A
D -->|"sudah"| F(["Menyerah dengan pesan jelas"])
Bab 21 — Bekerja dengan API: Meminta Data dari Komputer Lain
Seri I2 — Data dan Internet
Semua skrip bab ini ada di folder kode/bab21/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip).
Seri I1 membuat kode Anda rapi. Sekarang kode itu perlu bahan, dan banyak bahan ada di komputer lain: prakiraan cuaca di server badan cuaca, harga di server pasar. Bayangkan sebuah restoran. Anda (program) duduk di meja, pelayan (API) menerima pesanan, dan dapur (server) memasak. Anda tidak perlu masuk dapur; cukup memesan dengan cara yang sudah ditentukan, lalu menerima piring berisi jawabannya. API (Application Programming Interface) adalah “daftar menu dan aturan memesan” itu.

HTTP dan JSON dalam lima menit
Percakapan itu memakai bahasa bernama HTTP. Sebuah permintaan berisi: metode (GET = “tolong berikan”), alamat, dan kadang tambahan seperti parameter atau token. Sebuah jawaban berisi: kode status (angka tiga digit), header (keterangan), dan isi. Isi dari API biasanya berbentuk JSON, yaitu teks yang susunannya mirip dict dan list Python (Bab 9).
| Kode | Artinya | Tindakan Anda |
|---|---|---|
| 200 | Berhasil | Baca isinya |
| 400 / 404 | Permintaan keliru / alamat tidak ada | Periksa alamat dan parameter; mengulang tidak membantu |
| 401 | Perlu token yang benar | Periksa token |
| 500-an (mis. 503) | Server bermasalah atau sibuk | Boleh coba ulang nanti |
Server tiruan: dapur milik kita sendiri
Agar contoh buku ini selalu menghasilkan keluaran yang sama dan tidak membebani server orang lain, kita membuat server API tiruan di komputer sendiri. File pembantunya server_tiruan.py (ada di folder bab, bukan skrip contoh) memakai http.server bawaan Python. Ia menyala di alamat lokal 127.0.0.1 pada port acak, melayani beberapa alamat dengan data fiktif, lalu dimatikan saat blok with selesai. Bagi requests, ia tampak seperti API sungguhan.
Permintaan pertama dengan requests
requests adalah pustaka pihak ketiga yang paling dikenal untuk HTTP (sudah ada di environment proyek ini; di komputer Anda: pip install requests).
01_permintaan_dasar.py
# Bab 21 - Permintaan HTTP pertama dengan requests
import requests
from server_tiruan import ServerTiruan
with ServerTiruan() as server: # menyalakan server tiruan di komputer sendiri
jawaban = requests.get(f"{server.alamat}/cuaca?lokasi=bogor&hari=2", timeout=5)
print("Kode status :", jawaban.status_code)
print("Jenis isi :", jawaban.headers["Content-Type"])
print("Teks mentah :", jawaban.text)
# keluar dari blok `with`: server dimatikan lagiKode status : 200
Jenis isi : application/json; charset=utf-8
Teks mentah : {"lokasi": "bogor", "satuan": {"suhu_maks": "C", "hujan_mm": "mm"}, "harian": {"tanggal": ["2026-01-01", "2026-01-02"], "suhu_maks": [30.1, 31.4], "hujan_mm": [12.5, 0.0]}}
Kode 200 berarti berhasil. Content-Type memberi tahu bahwa isinya JSON. “Teks mentah” itu dikirim server sebagai satu baris panjang; berikutnya kita ubah menjadi data Python.
JSON dan parameter
02_json_dan_parameter.py
# Bab 21 - JSON dan parameter (params)
import json
import requests
from server_tiruan import ServerTiruan
with ServerTiruan() as server:
# params= menyusun bagian "?kunci=nilai" dengan benar (termasuk penyandian karakter)
jawaban = requests.get(f"{server.alamat}/cuaca", params={"lokasi": "bandung", "hari": 3}, timeout=5)
print("Alamat lengkap:", jawaban.url.replace(server.alamat, "<server>"))
data = jawaban.json() # JSON -> dict/list Python
print("Tipe data :", type(data).__name__)
print("Lokasi :", data["lokasi"])
print("Tanggal :", data["harian"]["tanggal"])
print("Hujan (mm) :", data["harian"]["hujan_mm"])
print("Total hujan :", round(sum(data["harian"]["hujan_mm"]), 1), "mm")
# arah sebaliknya: Python -> teks JSON
print(json.dumps({"lokasi": "bandung", "hujan_total_mm": 19.2}, ensure_ascii=False, indent=2))Alamat lengkap: <server>/cuaca?lokasi=bandung&hari=3
Tipe data : dict
Lokasi : bandung
Tanggal : ['2026-01-01', '2026-01-02', '2026-01-03']
Hujan (mm) : [5.0, 0.0, 14.2]
Total hujan : 19.2 mm
{
"lokasi": "bandung",
"hujan_total_mm": 19.2
}
params={...}menyusun bagian?lokasi=bandung&hari=3dengan benar. Jangan merangkai alamat dengan tangan..json()mengubah jawaban menjadi dict/list. Tiga hari hujan Bandung: 5,0 + 0,0 + 14,2 = 19,2 mm.json.dumps(...)arah sebaliknya: dari Python menjadi teks JSON (indent=2merapikan).
httpx: saudara requests
httpx mirip requests tetapi punya Client: objek yang mengingat alamat dasar dan pengaturan sehingga tidak diulang di tiap panggilan (juga mendukung mode asinkron yang dibahas di seri lanjut).
03_httpx.py
# Bab 21 - httpx: saudara requests, dengan Client yang bisa dipakai ulang
import httpx
from server_tiruan import ServerTiruan
with ServerTiruan() as server:
# Client menyimpan alamat dasar dan pengaturan, jadi tidak diulang di tiap panggilan
with httpx.Client(base_url=server.alamat, timeout=5.0) as klien:
for lokasi in ["bogor", "bandung", "malang"]:
jawaban = klien.get("/cuaca", params={"lokasi": lokasi, "hari": 5})
data = jawaban.json()
hujan = data["harian"]["hujan_mm"]
print(f"{lokasi:8} status {jawaban.status_code} total hujan {sum(hujan):5.1f} mm")bogor status 200 total hujan 49.2 mm
bandung status 200 total hujan 51.5 mm
malang status 200 total hujan 28.7 mm
Tiga lokasi, total hujan lima hari masing-masing 49,2; 51,5; dan 28,7 mm (data fiktif). Untuk pemula, pilih salah satu dulu; ilmunya hampir sama.
Ketika keadaan tidak mulus
Jaringan itu tidak pasti: alamat salah, server mati, koneksi putus. Program yang baik mengantisipasinya.
Kode status dan raise_for_status()
05_galat_status.py
# Bab 21 - Kode status galat dan raise_for_status()
import requests
from server_tiruan import ServerTiruan
with ServerTiruan() as server:
for alamat in ["/cuaca?lokasi=bogor&hari=1", "/cuaca?lokasi=atlantis", "/cuaca", "/rahasia"]:
jawaban = requests.get(server.alamat + alamat, timeout=5)
print(f"{alamat:28} -> {jawaban.status_code}")
# raise_for_status() mengubah kode 4xx/5xx menjadi pengecualian yang bisa ditangkap
try:
requests.get(f"{server.alamat}/cuaca?lokasi=atlantis", timeout=5).raise_for_status()
except requests.HTTPError as galat:
print("Tertangkap HTTPError:", galat.response.status_code, galat.response.json())/cuaca?lokasi=bogor&hari=1 -> 200
/cuaca?lokasi=atlantis -> 404
/cuaca -> 400
/rahasia -> 401
Tertangkap HTTPError: 404 {'galat': 'lokasi tidak dikenal'}
requests tidak menganggap 404 sebagai galat; itu hanya jawaban dengan kode berbeda. raise_for_status() mengubah kode 4xx/5xx menjadi pengecualian HTTPError yang bisa Anda try/except (Bab 12).
Coba ulang dengan jeda
Galat 5xx sering sementara. Strateginya: coba lagi dengan jeda yang makin panjang (exponential backoff), tetapi hanya untuk galat yang memang layak diulang, dan berhenti setelah beberapa kali.
07_coba_ulang.py
# Bab 21 - Coba ulang dengan jeda yang makin panjang
import time
import requests
from server_tiruan import ServerTiruan
def ambil_dengan_ulang(alamat, maks_coba=4, jeda_awal=0.05):
jeda = jeda_awal
for percobaan in range(1, maks_coba + 1):
try:
jawaban = requests.get(alamat, timeout=5)
if jawaban.status_code < 500: # 2xx/4xx: tidak ada gunanya mengulang
return jawaban
print(f" percobaan {percobaan}: status {jawaban.status_code}, tunggu {jeda:.2f} detik")
except (requests.ConnectionError, requests.Timeout):
print(f" percobaan {percobaan}: jaringan bermasalah, tunggu {jeda:.2f} detik")
time.sleep(jeda)
jeda = jeda * 2 # jeda berlipat: 0,05 -> 0,10 -> 0,20 ...
raise RuntimeError("menyerah setelah beberapa kali mencoba")
with ServerTiruan() as server: # /sibuk gagal dua kali (503), baru berhasil
hasil = ambil_dengan_ulang(f"{server.alamat}/sibuk")
print("Berhasil:", hasil.json()) percobaan 1: status 503, tunggu 0.05 detik
percobaan 2: status 503, tunggu 0.10 detik
Berhasil: {'pesan': 'berhasil', 'percobaan_ke': 3}
Server tiruan sengaja gagal dua kali (503), jadi percobaan ke-3 berhasil. Jeda berlipat dua tiap kali: 0,05 lalu 0,10 detik (di dunia nyata awali dengan 1 detik atau lebih agar tidak menghujani server).
Satu panggilan ke API nyata: Open-Meteo
Open-Meteo menyediakan prakiraan cuaca lewat API terbuka. Hasil pemeriksaan syaratnya pada 2026-10-10 (open-meteo.com/en/terms):
- Penggunaan gratis hanya untuk non-komersial dan tanpa kunci; batasnya kurang dari 10.000 panggilan per hari, 5.000 per jam, dan 600 per menit.
- Memakai API gratis berarti menerima lisensi CC BY 4.0, yaitu wajib mencantumkan atribusi. Itu sebabnya skrip mencetak “Data cuaca oleh Open-Meteo.com”.
- Situs ini bukan aplikasi komersial; pembaca yang membuat produk berbayar harus membaca syarat itu sendiri.
08_api_nyata.py
# Bab 21 - Satu panggilan ke API nyata: Open-Meteo (tanpa kunci)
import requests
alamat = "https://api.open-meteo.com/v1/forecast"
parameter = {
"latitude": -6.6, # sekitar Bogor
"longitude": 106.8,
"daily": "temperature_2m_max,precipitation_sum",
"timezone": "Asia/Jakarta",
"forecast_days": 3,
}
jawaban = requests.get(alamat, params=parameter, timeout=10)
jawaban.raise_for_status()
data = jawaban.json()
print("Status :", jawaban.status_code)
print("Satuan :", data["daily_units"])
for tgl, suhu, hujan in zip(data["daily"]["time"], data["daily"]["temperature_2m_max"],
data["daily"]["precipitation_sum"]):
print(f"{tgl} suhu maks {suhu} C hujan {hujan} mm")
print("Data cuaca oleh Open-Meteo.com (lisensi CC BY 4.0)")Status : 200
Satuan : {'time': 'iso8601', 'temperature_2m_max': '°C', 'precipitation_sum': 'mm'}
2026-10-10 suhu maks 33.8 C hujan 1.0 mm
2026-10-11 suhu maks 32.9 C hujan 5.2 mm
2026-10-12 suhu maks 32.4 C hujan 6.6 mm
Data cuaca oleh Open-Meteo.com (lisensi CC BY 4.0)
Kode ini tidak dijalankan ulang saat buku dibuat karena butuh internet dan hasilnya berubah setiap hari. Keluaran di atas diambil sendiri pada 2026-10-10 lalu disimpan. Bila Anda menjalankannya, tanggal dan angkanya akan berbeda; itu wajar.
Soal harga komoditas: pada 2026-10-10 saya tidak menemukan API harga komoditas pertanian yang terverifikasi gratis dan tanpa kunci. Daftar 71 sumber data API World Bank tidak memuat sumber harga komoditas, dan layanan harga komoditas yang muncul di pencarian umumnya meminta pendaftaran atau kunci (syaratnya tidak saya uji). Karena itu harga di buku ini berasal dari server tiruan (fiktif). Untuk proyek sungguhan, periksa sumber resmi seperti kementerian atau bursa komoditas, dan baca syarat penggunaannya.
Proyek Bersambung I2: mengambil data cuaca dan harga
Langkah 1 proyek Seri I2: mengambil cuaca tiga lokasi dan harga empat komoditas dari “API” kita, dengan fungsi kecil yang berhenti jelas bila ada galat, lalu menyimpannya sebagai JSON mentah di folder _tmp.
09_proyek_ambil_data.py
# Bab 21 - Proyek bersambung I2, langkah 1: mengambil data cuaca dan harga
import json
from pathlib import Path
import requests
from server_tiruan import ServerTiruan
tmp = Path(__file__).parent / "_tmp"
tmp.mkdir(exist_ok=True)
def ambil_json(alamat, **param):
"""Mengambil JSON; berhenti dengan pesan jelas bila ada galat."""
jawaban = requests.get(alamat, params=param, timeout=5)
jawaban.raise_for_status()
return jawaban.json()
with ServerTiruan() as server:
cuaca = {}
for lokasi in ["bogor", "bandung", "malang"]:
cuaca[lokasi] = ambil_json(f"{server.alamat}/cuaca", lokasi=lokasi)["harian"]
harga = ambil_json(f"{server.alamat}/harga")["data"]
(tmp / "cuaca_mentah.json").write_text(json.dumps(cuaca, ensure_ascii=False, indent=1), encoding="utf-8")
(tmp / "harga_mentah.json").write_text(json.dumps(harga, ensure_ascii=False, indent=1), encoding="utf-8")
print("Lokasi cuaca :", list(cuaca))
print("Hari per lokasi:", len(cuaca["bogor"]["tanggal"]))
print("Baris harga :", len(harga), "| contoh:", harga[0])
print("Tersimpan di folder _tmp: cuaca_mentah.json, harga_mentah.json")Lokasi cuaca : ['bogor', 'bandung', 'malang']
Hari per lokasi: 5
Baris harga : 20 | contoh: {'tanggal': '2026-01-01', 'komoditas': 'padi', 'harga_per_kg': 6200}
Tersimpan di folder _tmp: cuaca_mentah.json, harga_mentah.json
Cuaca tiga lokasi, lima hari tiap lokasi, dan 20 baris harga (4 komoditas x 5 hari). Data mentah ini akan dibersihkan (Bab 22), digabung (Bab 23), disimpan (Bab 24), dan digambar (Bab 25). Agar tiap skrip berdiri sendiri, bab-bab berikutnya menyalin data yang sama langsung di kodenya.
Latihan
- Ubah
02_json_dan_parameter.pyagar mengambilmalangdan mencetak hari dengan hujan terbanyak. - Di
05_galat_status.py, tambahkan alamat/tidak-ada. Kode status apa yang muncul? - Ubah
timeoutdi06_timeout.pymenjadi 2 detik. Apa yang berubah, dan mengapa? - Tantangan: tulis fungsi
ambil_aman(alamat)yang memakai coba ulang untuk 5xx, tetapi langsung berhenti (tanpa mengulang) pada404, dan mengembalikanNonebila akhirnya gagal.
Ringkasan Bab
- API = cara resmi meminta data dari program lain; HTTP = bahasanya; JSON = bentuk isinya.
requests.get(alamat, params=..., timeout=...);.json()mengubah jawaban menjadi data Python.httpx.Clientsejenis dengan pengaturan bersama.- Kode status: 2xx berhasil, 4xx salah di pihak kita, 5xx salah di server.
raise_for_status()mengubah galat menjadi pengecualian. - Selalu pakai timeout; coba ulang hanya galat sementara, dengan jeda berlipat.
- Token dibaca dari variabel lingkungan, bukan ditulis di kode.
- Open-Meteo: gratis non-komersial, tanpa kunci, wajib atribusi (CC BY 4.0). Harga komoditas gratis tanpa kunci: tidak ditemukan (belum terverifikasi).
Selanjutnya, di Bab 22: data yang datang tidak selalu rapi, misalnya catatan lapangan yang diketik manusia. Kita belajar regex untuk menggali dan membersihkan teks.