11 KiB
Dokumentasi Scraping Harga Cabai
Dokumen ini menjelaskan proses pembaruan data harga melalui panel admin, mulai dari tombol Perbarui data di frontend sampai data hasil scraping disimpan ke MySQL.
Ringkasan
Fitur scraping mengambil data dari SISKAPERBAPO menggunakan:
requestsuntuk mengirim HTTPGETdanPOST;pandas.read_html()untuk membaca tabel HTML;lxmlsebagai parser HTML;- MySQL untuk menyimpan hasil ke tabel
price_history.
Implementasi saat ini tidak menggunakan Selenium, WebDriver, Playwright, atau browser automation.
Model LSTM tidak dilatih ulang setelah scraping. Data baru hanya disimpan sebagai data historis yang dapat digunakan oleh API saat melakukan inference.
Komponen yang terlibat
| Komponen | Lokasi | Fungsi |
|---|---|---|
| Tombol dan panel admin | ../website/src/App.jsx |
Memulai scraping, menampilkan progress, dan memuat ulang data |
| Client API frontend | ../website/src/services/api.js |
Mengirim request ke backend |
| Route scraping | app/api/routes.py |
Validasi admin, membuat job, membaca status, dan menyimpan data |
| Scraper SISKAPERBAPO | app/services/siskaperbapo_scraper.py |
Mengambil dan membersihkan tabel harga |
| Konfigurasi aplikasi | app/config.py |
Menentukan URL sumber, komoditas, timeout, dan sumber data |
| Data lokasi pasar | app/data/markets.json |
Menyimpan slug, nama, dan koordinat pasar |
| Database | MySQL | Menyimpan price_history dan scraping_logs |
Alur tombol "Perbarui data"
Alur normalnya adalah:
Admin klik "Perbarui data"
↓
Frontend mengirim POST /api/v1/admin/scraping/jobs
↓
Backend memvalidasi role admin dan opsi scraping
↓
Backend membuat job_id dan menjalankan thread background
↓
Scraper mengambil tabel SISKAPERBAPO
↓
Harga dibersihkan dan dikonversi ke Rupiah
↓
Data di-insert atau di-update ke price_history
↓
Status scraping_logs diperbarui
↓
Frontend mengecek status job setiap 700 ms
↓
Dashboard admin memuat ulang data
Proses ini berbeda dari pemanggilan prediksi. Scraping hanya memperbarui data historis; artifact model di models/ tetap digunakan seperti sebelumnya.
Proses di frontend
Komponen admin berada di ../website/src/App.jsx.
Fungsi refreshPrices():
- Mengaktifkan status
refreshing; - Memanggil
dashboardService.startScraping(options); - Menerima
job_id; - Meminta status job setiap 700 ms;
- Menampilkan progress dan pesan proses;
- Menampilkan error jika status berubah menjadi
failed; - Memanggil
loadAdminData()setelah berhasil.
Request frontend didefinisikan di ../website/src/services/api.js:
startScraping: (options) =>
request('/admin/scraping/jobs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(options)
})
Contoh payload:
{
"commodities": ["rawit"],
"start_date": "",
"end_date": ""
}
Contoh untuk rentang tanggal:
{
"commodities": ["rawit"],
"start_date": "2025-10-01",
"end_date": "2025-10-07"
}
Jika tanggal tidak diisi, scraper mengambil tabel terbaru melalui metode scrape(). Jika tanggal awal dan akhir diisi, scraper menggunakan metode scrape_range().
Endpoint scraping
Semua endpoint berikut memiliki prefix /api/v1 dan membutuhkan session admin.
Memulai scraping
POST /api/v1/admin/scraping/jobs
Payload:
{
"commodities": ["rawit"],
"start_date": "2025-10-01",
"end_date": "2025-10-07",
"max_records": 1000
}
Field:
| Field | Wajib | Keterangan |
|---|---|---|
commodities |
Tidak | Array slug: rawit, merah, atau keriting. Default mengikuti SISKAPERBAPO_COMMODITIES |
start_date |
Tidak | Tanggal awal format YYYY-MM-DD |
end_date |
Tidak | Tanggal akhir format YYYY-MM-DD |
max_records |
Tidak | Batas hasil antara 1 sampai 10.000 |
Response awal menggunakan HTTP 202 karena proses berjalan di background:
{
"data": {
"id": "job-id",
"status": "running",
"progress": 0,
"message": "Mengantri scraping",
"records_inserted": 0,
"records_updated": 0
}
}
Melihat status scraping
GET /api/v1/admin/scraping/jobs/{job_id}
Status yang mungkin:
running: proses masih berlangsung;success: proses selesai;failed: proses gagal.
Field progress menunjukkan persentase proses. Field message menjelaskan tahap yang sedang berjalan.
Melihat daftar riwayat
GET /api/v1/admin/scraping/logs
Melihat isi satu riwayat
GET /api/v1/admin/scraping/logs/{log_id}/records
Menghapus satu riwayat
DELETE /api/v1/admin/scraping/logs/{log_id}
Endpoint ini menghapus log yang dipilih dan record price_history yang memiliki scraping_log_id tersebut.
Menghapus seluruh riwayat scraping
DELETE /api/v1/admin/scraping/history
Penghapusan dibatasi pada data yang bersumber dari siskaperbapo; data hasil import CSV tidak ikut dihapus.
Proses backend
Route POST /admin/scraping/jobs berada di app/api/routes.py. Route tersebut:
- Memastikan user memiliki role
admin; - Membaca payload JSON;
- Memvalidasi komoditas, tanggal, dan batas record;
- Menolak proses baru jika ada scraping atau preprocessing yang masih
running; - Membuat ID job;
- Memulai
_run_scraping_job()di thread background.
Fungsi _run_scraping_job() melakukan langkah berikut:
- Membuat row baru pada
scraping_logsdengan statusrunning; - Membuka koneksi MySQL;
- Memanggil
scrape()atauscrape_range(); - Mencocokkan slug komoditas dan pasar dengan tabel master;
- Menyimpan observasi ke
price_history; - Menghitung jumlah record baru dan record yang diperbarui;
- Mengubah status log menjadi
success; - Melakukan rollback dan mencatat error jika proses gagal.
Cara scraper mengambil data
Implementasinya berada di app/services/siskaperbapo_scraper.py.
Data terbaru dengan scrape()
Untuk setiap komoditas yang dipilih, scraper mengirim:
requests.get(
self.url,
params={self.commodity_param: commodity},
timeout=self.timeout,
headers={"User-Agent": "PanenHarga/1.0"},
)
Response HTML dibaca dengan:
pd.read_html(StringIO(response.text))
Scraper kemudian mencari:
- kolom lokasi atau pasar;
- kolom yang dapat dibaca sebagai tanggal;
- baris lima pasar target;
- harga pada setiap tanggal.
Data historis dengan scrape_range()
Untuk rentang tanggal, scraper mengakses endpoint:
/harga/tabel.nodesign
melalui HTTP POST, dengan data:
{
"tanggal": "YYYY-MM-DD",
"kabkota": "jemberkab",
"pasar": "market_id"
}
Proses dilakukan untuk setiap kombinasi:
tanggal × pasar
ID pasar yang digunakan:
| Slug | Nama | ID sumber |
|---|---|---|
pasar_tanjung |
Pasar Tanjung | 11 |
pasar_kreongan |
Pasar Kreongan | 12 |
pasar_tegal_besar |
Pasar Tegal Besar | 13 |
pasar_mangli |
Pasar Mangli | 148 |
pasar_wirolegi |
Pasar Wirolegi | 149 |
Normalisasi harga
Scraper memakai _parse_price() untuk membersihkan nilai seperti:
Rp22;22;22,5;22.000.
Kemudian _price_in_rupiah() menerapkan aturan:
price * 1000 if 0 < price < 1000 else price
Dengan demikian:
| Nilai sumber | Nilai yang disimpan |
|---|---|
22 |
22000 |
22.5 |
22500 |
22000 |
22000 |
Harga dalam price_history disimpan sebagai Rupiah penuh.
Penyimpanan dan duplikasi data
Setiap observasi memiliki bentuk:
{
"commodity": "rawit",
"market": "pasar_tanjung",
"date": "2025-10-01",
"price": 22000,
"source": "siskaperbapo"
}
Record disimpan ke price_history menggunakan pola:
INSERT INTO price_history (...)
VALUES (...)
ON DUPLICATE KEY UPDATE
price = VALUES(price),
source = VALUES(source),
scraping_log_id = VALUES(scraping_log_id)
Artinya:
- kombinasi yang belum ada menjadi record baru;
- kombinasi yang sudah ada diperbarui;
- harga tidak disimpan sebagai duplikat jika unique key database telah dikonfigurasi.
Kolom scraping_log_id menghubungkan setiap harga dengan riwayat proses scraping.
scraping_logs
Tabel scraping_logs dipakai untuk mencatat proses. Informasi yang disimpan meliputi:
- waktu mulai;
- waktu selesai;
- sumber data;
- status;
- jumlah record baru;
- jumlah record diperbarui;
- pesan error atau warning.
Status awal adalah running. Setelah proses selesai, status menjadi success atau failed.
Scraping dan preprocessing berbeda
Scraping:
Mengambil data dari SISKAPERBAPO
Preprocessing:
Memeriksa dan mengisi data kosong dari riwayat scraping
Preprocessing dijalankan melalui:
POST /api/v1/admin/preprocessing/jobs
Proses ini menggunakan interpolasi linear dan bukan bagian dari pengambilan data dari website.
Konfigurasi
Konfigurasi berada di app/config.py dan dapat diatur melalui environment variable:
$env:SISKAPERBAPO_URL="https://siskaperbapo.jatimprov.go.id/harga-komoditas"
$env:SISKAPERBAPO_COMMODITY_PARAM="komoditas"
$env:SISKAPERBAPO_TIMEOUT="30"
$env:SISKAPERBAPO_COMMODITIES="rawit"
Nilai default:
- URL:
https://siskaperbapo.jatimprov.go.id/harga-komoditas; - parameter komoditas:
komoditas; - timeout: 30 detik;
- komoditas default:
rawit.
Tahap awal menggunakan rawit untuk mengurangi risiko sumber website mengabaikan parameter komoditas. Komoditas lain sebaiknya diaktifkan setelah struktur response sumber diverifikasi.
Cara menjalankan dan memeriksa proses
Jalankan backend dari folder api:
cd C:\PrediksiHargaCabe\api
python main.py
Kemudian:
- Buka website frontend;
- Login sebagai admin;
- Buka menu Perbarui data;
- Pilih komoditas;
- Isi rentang tanggal jika ingin mengambil data historis tertentu;
- Klik Mulai scraping atau tombol Perbarui data di bagian atas;
- Amati progress;
- Periksa bagian Riwayat pembaruan;
- Buka detail record untuk memeriksa hasil yang disimpan.
Jika ingin melihat proses langsung dari database, periksa:
SELECT *
FROM scraping_logs
ORDER BY id DESC;
dan:
SELECT *
FROM price_history
WHERE source = 'siskaperbapo'
ORDER BY price_date DESC;
Penanganan error
Jika request ke sumber gagal, scraper melempar ScrapingError. Job akan berstatus failed, transaksi database di-rollback, dan pesan error dicatat di scraping_logs.
Penyebab yang umum:
- website SISKAPERBAPO tidak dapat diakses;
- struktur tabel HTML berubah;
- parameter komoditas tidak sesuai;
- koneksi MySQL gagal;
- tabel master komoditas atau pasar belum tersedia;
- response tidak berisi data yang cocok.
Jika proses gagal, periksa:
- status dan pesan pada panel admin;
- kolom error di
scraping_logs; - URL dan konfigurasi SISKAPERBAPO;
- koneksi MySQL;
- struktur HTML response sumber.
Catatan penting
- Scraping hanya dapat dijalankan oleh admin.
- Hanya satu scraping atau preprocessing yang boleh berjalan pada satu waktu di proses aplikasi.
- Data baru tidak otomatis melatih ulang model.
- Tidak ada Selenium pada implementasi saat ini.
- Perubahan struktur HTML SISKAPERBAPO dapat membuat parser gagal.
- Data kosong yang dihasilkan scraping ditangani pada tahap preprocessing terpisah.