Melacak harga supermarket dengan Playwright
(sakisv.net)- Pada Desember 2022, di tengah inflasi tinggi, dibangun pipeline pricewatcher.gr untuk melacak perubahan harga harian di tiga supermarket terbesar di Yunani
- Karena daftar produk berada di balik rendering JavaScript dan infinite scroll, data diekstrak dari DOM dengan mengendalikan browser memakai Playwright, bukan
curlataurequests.get() - Karena lingkungan eksekusi lambat jika hanya mengandalkan laptop lama dan AWS EC2 mahal, pendekatannya diubah menjadi membuat server Hetzner hanya saat dibutuhkan, menjalankan 3 scraper secara paralel, lalu mematikannya
- Untuk supermarket yang Akamai-nya memblokir IP non-residensial, digunakan Tailscale exit node agar lalu lintas keluar lewat IP rumah, dan kegagalan dideteksi dengan notifikasi email, validasi ketat, serta heuristik jumlah produk
- Waktu eksekusi dikurangi dengan server 8vCPU dan memblokir permintaan gambar, dan untuk 31 eksekusi terakhir biayanya sekitar €4.94 untuk server Hetzner, €0.09 untuk IPv4, dan €0.00 untuk penyimpanan Cloudflare R2
Scraping situs supermarket berbasis JavaScript
- Pada Desember 2022, di tengah inflasi tinggi, dibangun pricewatcher.gr untuk melacak perubahan harga di tiga supermarket terbesar di Yunani
- Ketiga e-shop supermarket sama-sama menggunakan rendering JavaScript, dan beberapa area memuat produk tambahan saat di-scroll seperti infinite scroll di media sosial
- Karena data produk tidak bisa diambil hanya dengan
curlataurequests.get(), digunakan Playwright - Playwright mengendalikan browser secara terprogram dan menangani tugas berikut lewat API
- membuka tab baru dan berpindah ke URL
- memeriksa DOM
- mengambil detail elemen
- mencegat dan memeriksa request
- Playwright mendukung Chromium, Safari, Firefox, dan bisa digunakan dari Node, Java, .NET, serta Python
- Scraper mencari elemen
load morepada infinite scroll, terus melakukan scroll, lalu mengecualikan produk yang bertanda habis stok dari daftarli.product-item - Pada akhirnya, dari tiap produk diurai nama produk, harga, foto, tautan dan sebagainya, lalu pekerjaan yang sama diulang untuk kategori produk berikutnya
Memilih lingkungan eksekusi harian
- Di M1 MacBook Pro, memproses satu supermarket secara penuh memerlukan 50 menit hingga 2 jam 30 menit, dan bahkan saat 3 scraper dijalankan paralel tidak ada perbedaan yang mencolok
- Untuk pengembangan dan pengujian, laptop sudah cukup, tetapi dibutuhkan lingkungan permanen untuk eksekusi harian
-
Menjalankan hanya dengan laptop lama
- Percobaan pertama memakai laptop lama keluaran 2013
- Spesifikasinya prosesor seri M dual-core 2.20GHz dan RAM 4GB, yang kemudian ditingkatkan menjadi 12GB
- Namun bahkan supermarket yang “cepat” tetap memerlukan lebih dari 2 jam, jadi performanya jauh dari harapan
-
Menjalankan di cloud
- Di AWS sempat dipertimbangkan instance EC2 kelas 4-core dan 8GB RAM, tetapi biayanya terlalu berat untuk proyek sampingan
- Pada saat penulisan,
c5a.xlargedieu-north-1berharga $0.1640 per jam, atau sekitar $118.08 per bulan dan $1,416.96 per tahun - Server setara
cpx31dari Hetzner berharga $17.22 (€15.72) per bulan, atau $206.64 per tahun, sekitar 7 kali lebih murah daripada AWS - Pada akhirnya, lingkungan eksekusi dipilih menggunakan Hetzner
Pipeline harian yang disusun dengan Concourse
- Laptop lama tidak lagi menjalankan scraping secara langsung, melainkan berperan sebagai server CI yang mendelegasikan pekerjaan ke server Hetzner
- Alat CI yang digunakan adalah Concourse
- Concourse memperkenalkan dirinya sebagai “a continuous thing-doer”
- Dengan model pipeline deklaratif dan pengelolaan versi input, Concourse menargetkan build yang dapat direproduksi
- Pipeline berjalan setiap malam dengan urutan berikut
- membuat server scraping
- menjalankan pekerjaan scraping untuk 3 supermarket secara paralel
- setelah semua pekerjaan selesai, mematikan server untuk menghemat biaya
- meneruskan output mentah dari masing-masing scraper ke pekerjaan transformasi
- memuat data hasil transformasi ke pricewatcher.gr
- jika tahap mana pun gagal, mengirim notifikasi email
Mengatasi pembatasan IP dan Tailscale exit node
- Supermarket yang dipakai untuk pengujian bekerja normal, tetapi satu supermarket lain berada di balik Akamai dan memiliki aturan firewall yang memblokir request dari IP non-residensial
- Struktur yang dibutuhkan justru kebalikan dari VPN biasa: membuat request seolah-olah keluar dari IP rumah yang sebenarnya
- Dengan Tailscale, beberapa perangkat digabungkan seakan berada di jaringan yang sama
- Di Tailscale, jika satu perangkat ditetapkan sebagai exit node, request dari perangkat lain bisa diarahkan keluar melalui node tersebut
- Laptop lama juga merangkap sebagai exit node untuk lalu lintas scraping
- Variabel lain yang tetap ada adalah ISP yang digunakan memakai CGNAT, sehingga IP publik tidak terikat ke satu individu tertentu dan dibagi dengan pelanggan ISP lain
Jenis kegagalan dan cara mendeteksinya
- Konfigurasi ini telah dijalankan selama satu setengah tahun dan secara keseluruhan bekerja cukup andal
- Proyek scraping pada dasarnya tidak bisa lepas dari perubahan yang dilakukan pengembang situs target
- Kegagalan secara besar terbagi menjadi dua jenis
-
Perubahan yang merusak
- Ini terjadi ketika perubahan situs langsung membuat scraper gagal
- Contohnya seperti berikut
- muncul survei sehingga perlu menekan tombol satu kali lagi
- tata letak berubah total sehingga scraper perlu direfaktor besar-besaran
-
Perubahan yang tidak langsung merusak
- Yang lebih rumit adalah saat scraper tetap berjalan normal, tetapi interpretasi datanya menjadi salah
- Misalnya, jika cara penulisan harga berubah dan bagian desimal dipisah ke `` maka keripik kentang seharga €1.99 bisa terurai sebagai €199
- Untuk menangkap perubahan seperti ini, tahap transformasi disusun agar memvalidasi input seketat mungkin
- Karena dijalankan setiap hari, ada waktu untuk memeriksa masalah, tetapi jika rusak saat sedang liburan hal itu tetap menjadi sumber kecemasan
Optimasi waktu eksekusi dan stabilitas
- Arsitektur keseluruhan hampir tidak berubah sejak awal, tetapi beberapa bagian diubah untuk meningkatkan keandalan dan mengurangi pekerjaan manual
- Peningkatan yang diterapkan mencakup hal-hal berikut
- notifikasi email saat gagal
- heuristik yang mengirim peringatan jika jumlah produk untuk supermarket tertentu terlalu banyak atau terlalu sedikit
- timeout
- retry tanpa memulai lagi dari awal
- Hambatan terbesar adalah waktu eksekusi scraping
- semakin lama prosesnya, semakin tinggi biayanya
- saat harus mencoba ulang dari awal setelah gagal, ketidaknyamanannya juga meningkat
-
Menggunakan server yang lebih besar
- Server diubah dari 4vCPU dan 16GB RAM menjadi 8vCPU dan 16GB RAM
- Waktu eksekusi turun sekitar 20%, dan performanya menjadi mirip dengan yang didapat di MBP
- Karena server scraping hanya dipakai sekitar 2 jam, selisih harganya nyaris bisa diabaikan
-
Mengambil lebih sedikit
- Permintaan gambar diblokir dengan
page.routedari Playwright - Request
.png,.jpgdi-abort agar gambar tidak diambil saat produk dimuat - Cara ini mempercepat scraping, sekaligus sedikit mengurangi bandwidth dan biaya di sisi situs target
- Permintaan gambar diblokir dengan
Biaya berdasarkan 31 kali eksekusi
- Berdasarkan tagihan Hetzner terbaru, biayanya sebagai berikut
- 31 server yang dibuat: €4.94
- 31 alamat IPv4 yang diterima server tersebut: €0.09
- Data hasil scraping disimpan di Cloudflare R2
- Karena belum melewati free tier 10GB dari Cloudflare R2, biaya penyimpanannya adalah €0.00
- Seluruh pipeline bekerja dengan menggabungkan Playwright, Hetzner, Concourse, Tailscale, dan Cloudflare R2 untuk melacak perubahan harga supermarket setiap hari
1 komentar
Pendapat di Hacker News
Sejak awal tahun ini saya melakukan hal serupa untuk New Zealand. Saya mengumpulkan data dengan Playwright/Typescript dan menyimpannya sebagai file Parquet di cloud storage, tapi belum menampilkannya di layar
Sebagian besar pekerjaan tersita untuk melewati layanan reverse proxy seperti Akamai dan Cloudflare. Saat pertama membuatnya, saya kira belum ada yang melakukannya, tetapi sekarang saya tahu setidaknya ada 3 startup di NZ yang melakukan hal yang sama. Tampaknya inflasi memicu cukup banyak inovasi di sini
Polanya sesuai dugaan. Supermarket memakai taktik biasa untuk membuat harga serumit mungkin, dan dengan perubahan harga berbentuk “gerigi” mereka memilah orang yang kekurangan waktu dan orang yang kekurangan uang. Mereka juga sering memisahkan pelanggan yang loyal pada merek dan pelanggan yang sensitif harga; kalau ada 3 merek cokelat populer, tiap minggu hanya salah satunya yang dijual dengan harga wajar
Bagian “memisahkan pelanggan yang loyal pada merek dan pelanggan yang sensitif harga. Kalau ada 3 merek cokelat populer, tiap minggu hanya salah satunya yang dijual dengan harga wajar” terasa sangat menarik
Di AU banyak scraper seperti ini muncul lalu menghilang, biasanya karena diblokir oleh supermarket besar. Ini siklus berulang antara kegunaan dan “kenapa tidak ada yang seperti ini?”, padahal sebenarnya sudah beberapa kali ada
Membuat crawler dan infrastruktur pendukungnya tidak sampai 20 jam
Selain itu, ini lebih berguna karena hanya ada satu marketplace yang memiliki semua harga dari berbagai toko
Artikel yang bagus. Saya mengalami masalah serupa di situs perbandingan harga lensa kontak yang saya jalankan, https://lenspricer.com/, dan situs ini beroperasi di sekitar 30 negara. Saya setuju bahwa ketika situs web mengubah HTML, itu benar-benar merepotkan
Salah satu hambatan terbesar di awal adalah mencocokkan produk yang sama di lebih dari 100 situs web. Nama produk tampaknya seharusnya unik, tetapi semua orang menuliskannya sedikit berbeda dengan gaya masing-masing. Sebagian besar bisa ditangani dengan regex, tetapi cukup banyak yang harus dipetakan secara manual, dan sebagian memakai AI, tetapi semuanya saya verifikasi sendiri
Membuat scraper dan infrastrukturnya relatif mudah. Yang sulit adalah memelihara semua scraper, lalu saat sebuah produk menghilang dari situs, menentukan apakah itu karena bug scraper, pemblokiran, perubahan situs, atau situs sedang maintenance saat proses crawling
Ini proyek yang menyenangkan, tetapi kadang sulit dan banyak masalahnya menyebalkan untuk diperbaiki
Harganya berbeda-beda, dan pertanggungannya pun biasanya diproses lewat pengembalian via pos sekitar 30%
Jumlah tokonya juga terlihat sangat sedikit, jadi mungkin saja semua tautannya adalah tautan sponsor. Di idealo.de saya juga menemukan harga yang lebih rendah
Costco cukup terkenal soal ini; hampir semua elektronik yang dijual di tokonya dan banyak produk lain memakai SKU khusus. Sering kali konfigurasi produknya juga sedikit berbeda
Saya membuat situs web serupa yang cukup menarik perhatian di kota saya. Saya meng-scrape data dari aplikasi dan situs web, lalu memakai satu server Linode RAM 2GB dengan 5 IPv4 dan 1000 IPv6 gratis
Semua produk dikumpulkan dengan interval maksimal 40 menit, rata-ratanya sekitar 25 menit. Saya memakai curl-impersonate dan sebisa mungkin meng-scrape JSON. 90% pasar menyediakan harga lewat panggilan Ajax, dan 10% sisanya mudah mem-parsing HTML dengan regex
Bisa dilihat di https://www.economizafloripa.com.br
https://www.economizafloripa.com.br/?q=parceria-comercial
Setelah melihat halaman itu, proyek ini terasa berubah dari “alat berguna bagi orang-orang untuk mengambil kembali kendali dari perusahaan penjual kebutuhan pokok” menjadi “upaya cari uang satu lagi”. Tentu itu terserah Anda, tetapi setelah membaca halaman utama, saya mengharapkan motivasi yang lebih etis
Tulisan yang bagus.
Menurut saya, paling baik memisahkan scraping dan parsing ke proses terpisah. Kalau JSON atau HTML mentah disimpan, kita bisa kapan saja kembali, memperbaiki parser, lalu menerapkannya lagi.
Sebagai bagian dari proyek magister saya, saya membuat sistem dan situs web serupa untuk Belanda: https://www.superprijsvergelijker.nl/
Sebagian besar scraping di proyek saya dilakukan dengan mengirim panggilan HTTP sederhana ke JSON API. Beberapa situs web memakai instance Playwright untuk mendapatkan cookie sesi yang valid serta melewati anti-bot dan CAPTCHA. Crawler/scraper, parser, dan API lainnya saya buat dengan Haskell dan jalankan di AWS ECS. Situs webnya memakai NextJS.
Tantangan utama yang terus saya coba pecahkan adalah menghubungkan produk dari berbagai supermarket agar harganya bisa ditampilkan dalam satu layar. Contohnya ada di sini: https://www.superprijsvergelijker.nl/supermarkt-aanbieding/6...
Kalau setidaknya ada satu nomor barcode yang benar untuk produk, biasanya ini bekerja dengan baik.
Menurut saya, dua supermarket besar di Australia bisa saja menciptakan struktur duopoli dengan penetapan harga antikompetitif hanya dengan menjalankan algoritme AI analisis harga di kedua sisi. Pada akhirnya, algoritme kemungkinan akan bergerak ke arah kerja sama demi memaksimalkan laba.
Ini bisa dilakukan secara legal hanya dengan harga yang diperoleh secara publik, dan secara ilegal jika mereka berbagi data biaya pasokan atau laba per produk. Hasilnya sepertinya akan mirip.
Dua AI yang sudah dilatih akan melakukan maksimalisasi laba dengan cara yang aneh lewat analisis regresi multidimensi, mungkin bahkan berdimensi sangat tinggi, dan konsumen akhirnya menanggung laba yang dimaksimalkan dari perusahaan-perusahaan yang di permukaan tampak sebagai pesaing. Kalau data harga bisa diperoleh seperti ini, tidak banyak lagi yang dibutuhkan untuk menjalankan dua implementasi machine learning yang berfokus pada duopoli.
Logikanya, jika semua harga terbuka, konsumen justru akan membayar harga yang lebih tinggi. Supermarket akan menyelaraskan harga ke titik tempat semua pihak memperoleh laba maksimum.
Supermarket di sini sudah bertahun-tahun mempekerjakan “pemburu harga”, yaitu orang-orang yang pergi ke toko pesaing dan mencatat harga semua barang.
Di Norwegia, sering terlihat supermarket A memberi diskon untuk barang tertentu pada suatu minggu, lalu minggu berikutnya atau minggu setelahnya supermarket B memberi diskon serupa untuk menarik pelanggan.
Untuk menangkap perubahan seperti ini, sepertinya bisa ditambahkan pemeriksaan otomatis. Misalnya, jika pemeriksaan kewajaran gagal, perubahan harga/produk tidak disinkronkan.
Bisa dibuat kriteria bahwa tiap harga tidak boleh berubah lebih dari 100%, dan jumlah produk aktif tidak boleh berubah lebih dari 20%.
Sebagai gantinya, heuristik seperti ini saya pakai untuk memeriksa apakah scraping berhasil. Misalnya, memeriksa apakah jumlah produk yang di-scrape hari ini berada dalam kisaran sekitar 10% dari rata-rata 7 hari terakhir.
Yang sulit bukan scraping itu sendiri, melainkan melewati mekanisme pemblokiran yang makin canggih.
Harus terus merotasi proxy residensial, memakai yang reputasinya tinggi, dan memastikan pola scraping data tidak terlihat. Beberapa supermarket tidak menampilkan network request di tab Network, jadi tidak bisa sekadar mengambil respons API.
Bahkan ketika mencoba melakukan serangan man-in-the-middle pada aplikasi mobile untuk melihat network request dan datanya, Anda akan diblokir jika penyamarannya tidak benar.
Saya sudah mencobanya, tetapi memutuskan itu tidak sepadan karena biaya dan pekerjaan pengembangan yang berkelanjutan. Nyatanya, sebagian layanan perbandingan harga supermarket cukup memakai tenaga kerja bergaji rendah untuk melakukan scraping.
Ini proyek sampingan, jadi kalau tuntutan waktunya menjadi terlalu sering, saya akan berhenti saja dan membuka semua kode serta datanya.
Tapi saya penasaran bagaimana mungkin network request tidak muncul di tab Network.
Bagian tersulit bagi saya adalah menghubungkan dan membandingkan produk antar-supermarket.
Akan bagus jika ada transparansi harga untuk barang. Proses seperti ini bisa jauh lebih mudah dilacak berdasarkan toko dan wilayah.
Misalnya, kita bisa membandingkan harga oat milk sebagai alternatif susu berdasarkan kode pos dan toko kelontong. Selain itu, “shrinkflation”, yaitu harga yang sama tetapi ukuran produk mengecil, juga bisa dilacak.
Dalam konteks itu, sepertinya harga memang dilacak, tetapi saya penasaran apakah biaya per gram atau per ounce juga diperiksa. Produsen atau toko bisa mempertahankan harga tetapi memberi konsumen jumlah yang lebih sedikit; saya penasaran apakah alat ini bisa menangkapnya.
Meski begitu, ini fitur yang sangat mungkin ditambahkan, dan jika ada shrinkflation, titik terjadinya bisa ditampilkan.
Di pasar Swedia, kami sudah melakukan ini selama lebih dari 8 tahun. Ada situs web bernama https://www.matspar.se/, dan pelanggan bisa melihat semua produk dari toko online utama, membandingkan harga, lalu memasukkan produk yang diinginkan ke keranjang
Pada akhirnya, mereka membandingkan total keranjang termasuk biaya pengiriman, lalu mengekspor keranjang ke toko yang diinginkan untuk melakukan pemesanan
Saya adalah salah satu pendiri sekaligus CTO saat ini, jadi selama ini sudah banyak melakukan scraping dan pemeliharaan. Setiap hari kami mengambil lebih dari 30 juta harga
Berdasarkan data publik, pendapatan perusahaan sekitar 400 ribu dolar AS dan jumlah karyawan 6 orang: https://www.allabolag.se/5590076351/matspar-i-sverige-ab
Saat pindah ke daerah baru, saya memang melacak harga, tetapi sekarang rasanya jauh lebih mudah berbelanja di 2 pasar atau toko besar yang selalu mempertahankan harga rendah
Kalau di Eropa, mungkin Aldi/Lidl
Kalau di AS, bisa jadi Costco/Trader Joe's
Untuk online ada CamelCamelCamel/Amazon. Bukan untuk makanan, melainkan produk kesehatan/kecantikan/sebagian elektronik
Jika bisa membeli langsung dari produsen, kadang itu lebih baik. Misalnya, saya membeli merek sabun tertentu yang saya sukai dalam jumlah besar dari situs grosir, dan harganya kurang dari setengah harga ritel. Sampo juga jauh lebih murah dibeli langsung per galon dibanding dari toko ritel mana pun
Trader Joe's kualitasnya lebih tinggi, tetapi umumnya lebih mahal