Web Scraping dengan Requests dan BeautifulSoup: Proyek Harga Marketplace
TL;DR
Web scraping adalah cara ngambil data dari halaman web dengan program, dan di Python kombinasi paling umum itu Requests buat ngunduh halamannya plus BeautifulSoup buat nyari elemen yang kamu mau. Alurnya empat langkah: cek robots.txt, unduh HTML, pilih elemen pakai CSS selector, lalu simpan ke DataFrame. Kasih jeda antar request dan pasang User-Agent yang jelas supaya server nggak keberatan.
Web scraping adalah cara ngambil data dari halaman web pakai program. Di Python, Requests yang ngunduh halamannya, BeautifulSoup yang nyari elemen yang kamu butuhin.
Kasus paling sering buat analis di Indonesia: ngumpulin harga produk sejenis dari marketplace, lalu bandingin sama harga sendiri. Kerjaan yang kalau manual makan dua jam seminggu, dan biasanya berhenti setelah minggu ketiga.
Di bawah ini alur lengkapnya, dari cek izin sampai file CSV yang siap dianalisis.
Apa itu web scraping dan kapan boleh dipakai?
Web scraping adalah proses ngambil data dari tampilan halaman web secara otomatis, lalu ngubahnya jadi tabel. Ini pilihan kedua setelah API. Kalau situsnya punya API resmi, pakai API karena datanya lebih stabil dan izinnya jelas. Scraping baru masuk waktu datanya cuma ada di halaman web biasa.
Sebelum nulis satu baris kode pun, buka dua hal: file robots.txt situsnya dan halaman syarat layanan. Robots.txt ngasih tau bagian mana yang boleh diambil program.
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url("https://books.toscrape.com/robots.txt")
rp.read()
print(rp.can_fetch("NgulikDataBot", "https://books.toscrape.com/catalogue/page-1.html"))
# True
Buat latihan di artikel ini aku pakai books.toscrape.com, situs yang emang dibikin khusus buat belajar scraping. Batas hukum dan etikanya aku bahas terpisah di artikel etika dan legalitas scraping di Indonesia.
Apa yang perlu diinstall?
pip install requests beautifulsoup4 pandas lxml
Empat paket, tugasnya beda-beda. Requests ngunduh halaman. BeautifulSoup4 baca HTML-nya. pandas nyimpen hasilnya jadi tabel. lxml itu parser tambahan yang lebih cepat, sifatnya opsional.
Dokumentasi resmi BeautifulSoup ada di crummy.com, dan itu rujukan paling lengkap buat semua metode pencarian elemennya.
Gimana cara ambil satu halaman dan baca isinya?
Alurnya tiga langkah: unduh halaman pakai requests.get, masukin teksnya ke BeautifulSoup, lalu pilih elemen pakai CSS selector. Selalu pasang User-Agent yang nyebut nama dan kontak kamu supaya pemilik situs tau siapa yang lagi ngambil data.
import requests
from bs4 import BeautifulSoup
HEADERS = {
"User-Agent": "NgulikDataBot/1.0 (+https://ngulikdata.com)"
}
url = "https://books.toscrape.com/catalogue/page-1.html"
r = requests.get(url, headers=HEADERS, timeout=15)
r.raise_for_status()
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text, "html.parser")
print(soup.title.get_text(strip=True))
Baris r.encoding = r.apparent_encoding itu penyelamat buat situs Indonesia. Tanpa itu, nama produk yang ada huruf beraksen sering berubah jadi karakter aneh.
Gimana cara nemuin selector yang bener?
Buka halamannya di browser, klik kanan di elemen yang kamu mau, pilih Inspect. Lihat nama tag dan class-nya, lalu tulis CSS selector yang paling pendek tapi masih spesifik. Selector pendek lebih tahan waktu desain situsnya diubah sedikit.
Di books.toscrape.com, tiap produk dibungkus article dengan class product_pod:
for card in soup.select("article.product_pod")[:3]:
judul = card.h3.a["title"]
harga = card.select_one("p.price_color").get_text(strip=True)
stok = card.select_one("p.instock.availability").get_text(strip=True)
print(judul, "|", harga, "|", stok)
A Light in the Attic | £51.77 | In stock
Tipping the Velvet | £53.74 | In stock
Soumission | £50.10 | In stock
Dua metode yang bakal kamu pakai terus:
select()balikin semua elemen yang cocok sebagai list.select_one()balikin satu elemen pertama, atau None kalau nggak ketemu.
Pakai select_one() lalu cek None-nya, jangan langsung .get_text(). Satu produk yang kebetulan nggak punya harga bisa bikin seluruh loop berhenti.
Gimana cara ambil banyak halaman dengan aman?
Bungkus proses satu halaman jadi fungsi, lalu panggil berulang sambil kasih jeda. Jeda satu sampai tiga detik itu batas wajar buat situs kecil, dan bikin script kamu nggak keblokir di tengah jalan.
import time
import pandas as pd
def scrape_halaman(nomor):
url = f"https://books.toscrape.com/catalogue/page-{nomor}.html"
r = requests.get(url, headers=HEADERS, timeout=15)
if r.status_code == 404:
return None
r.raise_for_status()
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text, "html.parser")
hasil = []
for card in soup.select("article.product_pod"):
harga_el = card.select_one("p.price_color")
hasil.append({
"judul": card.h3.a["title"],
"harga_teks": harga_el.get_text(strip=True) if harga_el else None,
"rating": card.select_one("p.star-rating")["class"][1],
"halaman": nomor,
})
return hasil
semua = []
for n in range(1, 51):
batch = scrape_halaman(n)
if batch is None:
break
semua.extend(batch)
time.sleep(2)
df = pd.DataFrame(semua)
print(len(df), "produk")
Tiga pengaman di kode itu: batas 50 halaman, deteksi 404 buat berhenti otomatis, dan time.sleep(2) di tiap putaran.
Gimana cara ngubah harga teks jadi angka?
Harga di halaman web selalu berupa teks, lengkap sama simbol mata uang dan pemisah ribuan. Bersihin dulu sebelum dihitung, kalau nggak semua agregasi kamu bakal salah atau langsung error.
import re
def ke_angka(teks):
if not teks:
return None
bersih = re.sub(r"[^0-9,.]", "", teks)
bersih = bersih.replace(".", "").replace(",", ".")
try:
return float(bersih)
except ValueError:
return None
print(ke_angka("Rp1.250.000")) # 1250000.0
print(ke_angka("Rp45.900")) # 45900.0
Fungsi di atas dipasang buat format Indonesia, titik sebagai pemisah ribuan dan koma sebagai desimal. Kalau kamu scraping situs berbahasa Inggris, aturannya kebalik.
Habis itu, cek dulu sebelum dianalisis:
print(df["harga"].isna().sum(), "harga gagal diparsing")
print(df["harga"].describe())
Contoh kasus: toko_berkah bandingin harga sama pasar
Toko Berkah jual 34 SKU aksesoris HP. Harga jualnya diset sekali di awal tahun dan belum pernah dicek ulang sama harga pasar.
Aku ambil harga produk sejenis dari halaman katalog publik, 3 halaman per kata kunci, jeda 2 detik. Total 41 menit buat 118 halaman, sekali jalan.
pasar = pd.read_csv("harga_pasar.csv")
median_pasar = (
pasar.groupby("kategori")["harga"]
.median()
.rename("median_pasar")
.reset_index()
)
toko = pd.read_csv("harga_toko_berkah.csv")
banding = toko.merge(median_pasar, on="kategori", how="left")
banding["selisih_pct"] = (
(banding["harga_jual"] - banding["median_pasar"]) / banding["median_pasar"] * 100
).round(1)
print(banding.sort_values("selisih_pct", ascending=False).head(8))
Hasil di dataset latihan ngulikdata: 9 dari 34 SKU dijual lebih dari 18 persen di atas median pasar. Yang paling jauh, kabel USB-C, dijual Rp42.000 sementara median pasarnya Rp29.500, selisih 42 persen.
Sisi lainnya juga muncul: 5 SKU dijual di bawah median pasar, dan salah satunya charger 20W yang laku 640 unit sebulan. Naikin Rp3.000 per unit di SKU itu doang nambah sekitar Rp1,9 juta sebulan tanpa ganti apa pun.
Median dipakai, bukan rata-rata, soalnya ada beberapa penjual yang masang harga aneh dan bikin rata-rata melenceng. Cara mikirin pencilan begini aku bahas di glossary outlier.
Kalau halamannya kosong waktu di-scrape
Banyak situs modern ngisi kontennya pakai JavaScript setelah HTML awal dimuat. Requests cuma ngambil HTML awal itu, jadi hasilnya kelihatan kosong.
Cara ngeceknya: klik kanan lalu View Page Source, bukan Inspect Element. View Page Source nampilin persis yang diterima Requests.
Kalau datanya nggak ada di situ, ada dua jalan:
- Buka tab Network di browser, refresh halamannya, cari request yang balikin JSON. Panggil endpoint itu langsung pakai requests, jauh lebih ringan daripada scraping HTML. Caranya sama kayak di panduan ambil data REST API.
- Kalau nggak ketemu juga, baru pakai browser otomatis kayak Playwright. Lebih berat dan lebih lambat, jadi jadikan pilihan terakhir.
Kesalahan umum waktu scraping
| Kesalahan | Akibatnya | Perbaikannya |
|---|---|---|
| Nggak kasih jeda antar request | IP diblokir, data cuma separuh | time.sleep(2) di tiap putaran |
| Selector kepanjangan | Rusak begitu desain berubah | Pegang class semantik atau atribut data |
Langsung .get_text() tanpa cek None | Loop berhenti di tengah | Cek hasil select_one() dulu |
| Lupa set encoding | Huruf berubah jadi simbol aneh | r.encoding = r.apparent_encoding |
| Simpan harga sebagai teks | Agregasi salah atau error | Bersihin jadi float sebelum disimpan |
| Nggak nyimpen tanggal ambil | Nggak bisa bandingin antar waktu | Tambahin kolom tanggal_ambil |
Nomor enam sering kelewat, dan itu justru yang bikin proyek ini berguna jangka panjang. Simpan tiap hasil scraping dengan tanggalnya, tiga bulan kemudian kamu punya data tren harga yang nggak dipunya kompetitor. Pengecekan kualitas datanya jalanin tiap kali script selesai.
FAQ
Web scraping itu legal atau nggak?
Tergantung apa yang kamu ambil dan gimana caranya. Ngambil data harga publik dengan jeda wajar dan tanpa nembus login umumnya aman. Yang bikin masalah itu ngambil data pribadi, ngelanggar syarat layanan situs, atau ngirim request begitu banyak sampai servernya terganggu. Cek robots.txt dan halaman syarat layanan sebelum mulai.
Kenapa hasil scraping saya kosong padahal di browser ada datanya?
Kemungkinan besar halaman itu ngisi kontennya pakai JavaScript setelah HTML awal dimuat, sedangkan Requests cuma ngambil HTML awalnya. Cek dengan klik kanan lalu View Page Source, bukan Inspect Element. Kalau datanya nggak ada di situ, cari endpoint API yang dipanggil halamannya lewat tab Network.
Berapa jeda yang wajar antar request?
Satu sampai tiga detik buat situs kecil, dan turutin nilai Crawl-delay kalau robots.txt nyebutin. Jeda ini bukan sekadar sopan santun, tapi juga bikin script kamu nggak kena blokir di tengah jalan. Kalau kamu butuh ribuan halaman, jalankan malam hari dan pecah jadi beberapa sesi.
Pakai html.parser atau lxml?
html.parser udah bawaan Python jadi nggak perlu install apa-apa, dan cukup buat sebagian besar halaman. lxml lebih cepat dan lebih tahan sama HTML yang rusak, tapi harus diinstall terpisah. Buat proyek di bawah seribu halaman, bedanya nggak kerasa.
Gimana kalau struktur HTML situsnya berubah?
Script kamu bakal balikin kolom kosong atau langsung error. Makanya jangan pakai selector yang kepanjangan kayak div > div > div > span. Pilih pegangan yang stabil kayak nama class semantik atau atribut data. Tambahin juga pengecekan jumlah baris di akhir, supaya kamu langsung tau waktu hasilnya anjlok.
Jadikan proyek portofolio
Empat hal yang bikin script scraping kamu layak dipakai berulang: cek robots.txt di awal, jeda di tiap request, parsing harga yang tahan format Indonesia, dan kolom tanggal ambil di tiap baris.
Jalanin seminggu berturut-turut, kamu udah punya dataset tren harga yang orisinal.
Coba mulai dari satu kategori produk yang kamu ngerti. Kalau mau tau batas amannya sebelum jalan, baca dulu rambu UU PDP untuk scraping.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Artikel terkait
Pandas groupby: Agregasi Data ala Pivot di Python (2026)
groupby ngelompokin baris berdasarkan kolom, lalu ngitung ringkasan per grup kayak total atau rata-rata. Ini pivot-nya pandas.
Pandas to_excel: Ekspor DataFrame ke Excel (2026)
to_excel nyimpen DataFrame pandas jadi file Excel .xlsx. Ini cara pakainya, dari satu sheet sampai banyak sheet, plus setelan yang bikin hasilnya rapi.
Pandas read_excel: Baca File Excel di Python
pandas read_excel baca file Excel jadi DataFrame, bisa pilih sheet tertentu dan lompatin baris judul yang berantakan. Ini cara pakai plus parameter penting dengan contoh data toko.