2 poin oleh GN⁺ 2024-08-07 | 1 komentar | Bagikan ke WhatsApp
  • 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 curl atau requests.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 curl atau requests.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 more pada infinite scroll, terus melakukan scroll, lalu mengecualikan produk yang bertanda habis stok dari daftar li.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.xlarge di eu-north-1 berharga $0.1640 per jam, atau sekitar $118.08 per bulan dan $1,416.96 per tahun
    • Server setara cpx31 dari 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.route dari Playwright
    • Request .png, .jpg di-abort agar gambar tidak diambil saat produk dimuat
    • Cara ini mempercepat scraping, sekaligus sedikit mengurangi bandwidth dan biaya di sisi situs target

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

 
GN⁺ 2024-08-07
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

    • Saya penasaran bagaimana supermarket menyalahgunakan segmentasi pelanggan lewat pembaruan harga. Saya juga ingin tahu bagaimana biasanya orang yang kekurangan waktu dan yang benar-benar kekurangan uang bereaksi
      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 Australia, legalitas cara seperti ini agak abu-abu. Saya kira NZ mungkin mirip
      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
    • Saya membuat https://bbdeals.in/ untuk India. Saya terutama memakainya saat membeli buah, dan ini menghemat pengeluaran sekitar 20%, jadi lumayan di masa seperti sekarang
      Membuat crawler dan infrastruktur pendukungnya tidak sampai 20 jam
    • Sebagai orang Kiwi, saya penasaran apakah proyek seperti ini atau proyek Anda sendiri bisa dipublikasikan. Saya cukup tertarik
    • Orang yang memesan pengantaran bahan makanan secara online bisa mendapat manfaat dari perbandingan harga, karena mereka bisa memesan dari beberapa toko sekaligus
      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

    • Anda melakukan hal yang memang dibutuhkan. Setiap tahun saya kerepotan karena perusahaan asuransi saat membeli barang dasar seperti lensa kontak
      Harganya berbeda-beda, dan pertanggungannya pun biasanya diproses lewat pengembalian via pos sekitar 30%
    • Saya penasaran apakah bisa memakai lensa kontak saat bekerja. Saya merasa mata lelah jika terlalu lama menatap monitor, dan ingin tahu apakah ada solusi yang Anda temukan
    • Di halaman Germany, di bawah harga tertulis “sebagian tautan mungkin merupakan tautan sponsor”, tetapi tidak ditandai tautan mana. Saya bertanya-tanya apakah ini legal
      Jumlah tokonya juga terlihat sangat sedikit, jadi mungkin saja semua tautannya adalah tautan sponsor. Di idealo.de saya juga menemukan harga yang lebih rendah
    • Di AS, setidaknya retailer besar membuat perbandingan harga menjadi sulit dengan meminta pemasok membuat SKU yang sedikit berbeda
      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 merasa kasus seperti inilah contoh penggunaan ketika LLM benar-benar bisa membantu
  • 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

    • Anda meng-scrape data aplikasi dan situs web lalu mencoba menjualnya kembali ke pelaku usaha, bahkan menyarankan agar data itu dipakai untuk pelatihan AI. Anda juga membuatnya terlihat seolah-olah tim memproses semuanya secara manual
      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
    • Saya penasaran bagaimana rotasi IPv6 bekerja dalam alur ini
  • 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.

    • Betul, saya juga melakukan persis seperti itu, dan sudah terbantu berkali-kali sampai agak malu mengakuinya. Menyimpan JSON/HTML mentah benar-benar berguna.
    • Keren. Saya memang sedang mencari yang seperti ini.
  • 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.

    • “Otoritas persaingan” Norwegia (https://konkurransetilsynet.no/norwegian-competition-authori...) sering mengkritik informasi harga pangan yang terbuka dan transparan karena alasan itu.
      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.
    • Kata yang Anda cari adalah kolusi. Hanya saja kolusinya bukan antar-manusia, melainkan lewat perangkat lunak.
  • 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%.

    • Pemeriksaan kewajaran dalam pemrograman sering diremehkan. Biaya performanya rendah, dan bisa menangkap bug lebih awal sebelum bug itu mengotori state jika dibiarkan.
    • Saya juga sempat memikirkannya, tetapi pernah melihat harga produk melonjak lebih dari 100%.
      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.

    • Akan bagus kalau hukum bisa mewajibkan data harga dibuka lewat API. Dengan begitu tidak perlu bergulat dengan mekanisme pemblokiran.
    • Untungnya, saya belum sampai ke tahap itu.
      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.
    • Apakah tidak bisa memakai OCR dengan hanya mengambil gambar daftar produk? Memang tidak ideal, tetapi tergantung caranya, mungkin sulit atau mustahil dilacak.
    • Bisa di-crowdsource lewat ekstensi browser.
  • 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.

    • Saya juga melacak harga per satuan seperti kg dan L. Saya sempat mempertimbangkan apakah angka ini yang ditampilkan dan dibuat grafiknya, atau harga yang benar-benar dibayar di kasir, tetapi saya memilih yang terakhir agar lebih “akrab” dengan harga yang dilihat orang.
      Meski begitu, ini fitur yang sangat mungkin ditambahkan, dan jika ada shrinkflation, titik terjadinya bisa ditampilkan.
    • Secara pribadi, saya benar-benar terganggu kalau toko kelontong tidak mencantumkan harga satuan pada label. Sulit membayangkan ada tujuan yang tidak merugikan pelanggan di balik itu.
    • Menarik membayangkan jika pengungkapan harga pokok barang diwajibkan. Saya ingin melihat petani menerima X, produsen menerima Y, dan toko kelontong menambahkan Z.
  • 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

    • Dari sisi bisnis, saya penasaran dengan model bisnisnya. Ingin tahu bagaimana mereka menghasilkan pendapatan dan apa tujuan jangka panjangnya
      Berdasarkan data publik, pendapatan perusahaan sekitar 400 ribu dolar AS dan jumlah karyawan 6 orang: https://www.allabolag.se/5590076351/matspar-i-sverige-ab
    • Saya penasaran apakah ada tulisan teknis tentang pendekatan scraping mereka. Ingin membaca lebih lanjut tentang kesulitan apa yang dihadapi dan bagaimana mereka menyelesaikannya
  • 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

    • Berdasarkan pengalaman saya di AS, pilihannya Costco/Walmart/Aldi
      Trader Joe's kualitasnya lebih tinggi, tetapi umumnya lebih mahal
    • ALDI juga ada di AS, tetapi tergantung wilayah. Trader Joe’s dimiliki oleh keluarga yang sama dengan ALDI, dan hingga sekitar 10 tahun lalu, sulit melihat keduanya berada di wilayah yang sama