September 12, 2026

AI News Blog System.

Cerita bikin portal berita sendiri tapi malas menulis beritanya — jadinya worker Python yang scraping, LLM yang menulis ulang, dan Laravel yang menjaga datanya.

Aku pengin punya website berita sendiri. Masalahnya cuma satu: aku malas menulis beritanya.

Portal berita itu butuh artikel baru terus-menerus, dan menulis (atau sekadar menyalin lalu merapikan) berita setiap hari jelas bukan hal yang akan aku lakukan secara konsisten. Jadi pertanyaannya berubah: bagaimana kalau yang nulis bukan aku? Dari situ lahir project ini — portal berita yang isinya dicari, ditulis ulang, dan diterbitkan sendiri oleh mesin.


Kenapa Nggak Pakai CMS + Plugin Auto-Post Saja?

Bisa saja. Tapi:

  • Aku ingin paham alurnya. Dari mengambil berita di sumber, membersihkan HTML, menulis ulang dengan LLM, sampai tampil di halaman depan.
  • Scraper yang langsung menulis ke database itu berantakan. Logika validasi, slug, dan cache jadi tercecer di dua tempat, dan data gampang rusak.
  • Biaya nol. Aku ingin semuanya jalan dengan layanan LLM gratis.

Rancangan

Sejak awal aku pisahkan jadi dua sistem yang hanya bicara lewat REST API:

Python worker (cron tiap jam)
  1. GET  /api/scrape-config   ambil daftar sumber + keyword aktif
  2. scrape (RSS/web) → filter keyword → dedup (source_hash)
  3. download + resize thumbnail
  4. rewrite via LLM (gratis, dengan fallback antar provider)
  5. POST /api/media           upload thumbnail
  6. POST /api/articles        auto-publish
       └─ LLM gagal → simpan sebagai draft

Laravel (portal):
  publish → event ArticlePublished → notifikasi Telegram
  1. Laravel adalah satu-satunya pemilik database: frontend publik, admin panel, dan API.
  2. Python worker adalah batch job tanpa state — ambil konfigurasi, kerjakan, kirim hasil, selesai.
  3. Keduanya didesain bisa jalan di server berbeda, dihubungkan lewat HTTPS + token Laravel Sanctum.

Langkah 1 — Laravel Sebagai Satu-Satunya Pintu

Aturan paling penting di project ini: Python tidak boleh menyentuh database. Semua lewat API, jadi validasi, pembuatan slug, event, dan cache tetap terpusat di Laravel.

Token untuk worker dibuat lewat artisan, dengan ability yang dibatasi:

php artisan worker:token <nama>
# abilities: scrape-config:read, media:write, articles:write

POST /api/articles dibuat idempotent. Setiap artikel punya source_hash unik; kalau hash-nya sudah ada, API mengembalikan artikel lama (duplicate: true) alih-alih error. Artinya worker boleh dijalankan ulang kapan saja tanpa takut berita dobel.


Langkah 2 — Worker yang Config-Driven

Aku nggak mau setiap menambah sumber berita harus mengubah kode Python. Jadi daftar situs, keyword, dan CSS selector disimpan di tabel scrape_sources dan dikelola dari admin panel. Worker tinggal mengambilnya lewat /api/scrape-config.

Soal cara mengambil berita:

  • RSS dulu. Lebih stabil dan lebih “sopan” dibanding scraping HTML.
  • HTML scraping hanya untuk situs yang tidak punya feed.
  • Isi utama artikel diambil dengan trafilatura, lalu dinormalisasi jadi Markdown yang bersih.
  • robots.txt situs sumber tetap dihormati.

Belakangan aku rapikan lagi alurnya: daftar artikel diambil dulu, hash-nya dicek ke portal, baru isi lengkapnya di-scrape — jadi berita yang sudah pernah masuk tidak perlu diambil ulang. Scraping-nya juga dibuat paralel.


Langkah 3 — Yang Nulis: LLM Gratisan

Inilah bagian “malas nulis”-nya. Setiap artikel ditulis ulang oleh LLM, memakai layanan gratis: Groq, Gemini, OpenRouter, atau Ollama lokal.

Karena layanan gratis punya kuota dan kadang down, aku bikin fallback antar provider — kalau satu gagal, pindah ke yang berikutnya. Konfigurasi LLM (key, model, prioritas) akhirnya juga pindah ke dashboard portal, lengkap dengan status tiap provider, jadi bisa diatur tanpa menyentuh .env worker.

Dan yang paling penting, rem darurat: kalau semua LLM gagal, artikel disimpan sebagai draft, bukan diterbitkan. Lebih baik tidak terbit daripada terbit dengan teks rusak.


Langkah 4 — Supaya Terasa Seperti Portal Berita Beneran

Kalau mesinnya sudah jalan, sisanya tinggal membuat portal yang layak dibaca:

  • Frontend dengan hero, grid, kategori dan sub-kategori, berita terkait, tombol share, dan slot iklan.
  • Widget sidebar: cuaca (Open-Meteo), jadwal sholat (Aladhan), trending, terpopuler, dan komentar terbaru — masing-masing menyembunyikan diri kalau API-nya gagal.
  • Komentar masuk sebagai pending dan harus dimoderasi dulu.
  • Admin panel ala WordPress, termasuk halaman pengaturan yang meng-override config saat runtime.
  • Notifikasi Telegram setiap ada artikel terbit.

Yang Aku Pelajari

  • Batas arsitektur itu penyelamat. Karena API adalah satu-satunya jalur tulis, worker bisa tetap sederhana dan aman dijalankan berulang kali.
  • Idempotensi harus didesain, bukan ditambal. source_hash unik membuat masalah duplikat hilang sejak awal.
  • LLM gratis itu tidak bisa diandalkan sendirian. Fallback dan rem darurat bukan fitur tambahan, tapi syarat.
  • Mengambil “isi” dari HTML itu susah. Halaman berita penuh menu, iklan, dan widget; mengekstrak artikelnya saja butuh alat yang tepat.

Keterbatasan & Catatan

  • LLM bisa berhalusinasi. Artikel hasil tulis ulang tetap bisa salah fakta. Karena itu source_url selalu disimpan sebagai atribusi — mitigasi, bukan jaminan.
  • Bergantung pada kuota layanan gratis, sehingga jumlah artikel yang bisa diproses ikut dibatasi.
  • Scraper itu rapuh: kalau struktur situs sumber berubah, selector-nya perlu disesuaikan (untungnya cukup dari admin panel).

Ringkasan teknis project-nya ada di halaman AI News Blog System .

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.

Comments