TIF_E41253368/api/SCRAPING.md

11 KiB
Raw Blame History

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:

  • requests untuk mengirim HTTP GET dan POST;
  • pandas.read_html() untuk membaca tabel HTML;
  • lxml sebagai 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():

  1. Mengaktifkan status refreshing;
  2. Memanggil dashboardService.startScraping(options);
  3. Menerima job_id;
  4. Meminta status job setiap 700 ms;
  5. Menampilkan progress dan pesan proses;
  6. Menampilkan error jika status berubah menjadi failed;
  7. 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:

  1. Memastikan user memiliki role admin;
  2. Membaca payload JSON;
  3. Memvalidasi komoditas, tanggal, dan batas record;
  4. Menolak proses baru jika ada scraping atau preprocessing yang masih running;
  5. Membuat ID job;
  6. Memulai _run_scraping_job() di thread background.

Fungsi _run_scraping_job() melakukan langkah berikut:

  1. Membuat row baru pada scraping_logs dengan status running;
  2. Membuka koneksi MySQL;
  3. Memanggil scrape() atau scrape_range();
  4. Mencocokkan slug komoditas dan pasar dengan tabel master;
  5. Menyimpan observasi ke price_history;
  6. Menghitung jumlah record baru dan record yang diperbarui;
  7. Mengubah status log menjadi success;
  8. 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:

  1. Buka website frontend;
  2. Login sebagai admin;
  3. Buka menu Perbarui data;
  4. Pilih komoditas;
  5. Isi rentang tanggal jika ingin mengambil data historis tertentu;
  6. Klik Mulai scraping atau tombol Perbarui data di bagian atas;
  7. Amati progress;
  8. Periksa bagian Riwayat pembaruan;
  9. 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:

  1. status dan pesan pada panel admin;
  2. kolom error di scraping_logs;
  3. URL dan konfigurasi SISKAPERBAPO;
  4. koneksi MySQL;
  5. 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.