Tutorial

Web Scraping yang Ethical: Panduan Lengkap Extract Data dari Website Tanpa Masalah

Web Scraping yang Ethical: Panduan Lengkap Extract Data dari Website Tanpa Masalah

Lo pasti pernah butuh data dari website yang gak punya API. Harga produk dari marketplace, artikel dari blog, data dari portal pemerintah — semua itu sering kali cuma bisa diakses lewat web scraping. Tapi banyak orang scraping dengan cara yang salah: blast request tanpa delay, ignore robots.txt, bikin server target down. Hasilnya? IP diblokir, permintaan data ditolak, bahkan kena masalah hukum.

Web scraping adalah skill penting buat research, automation, dan data pipeline. Artikel ini bakal jelasin cara yang benar: efficient, ethical, dan scalable — berdasarkan praktik terbaik dari komunitas. Lo bakal dapet framework thinking yang bisa lo apply ke website manapun, bukan cuma tutorial copy-paste.

Legalitas Web Scraping

Pertanyaan pertama: apakah web scraping legal? Jawabannya: tergantung. Dan ini bukan jawaban yang memuaskan, jadi gw bakal jelasin lebih detail.

  • Legal: Scrape data yang publicly available, tanpa bypass authentication, untuk personal/academic use, ikuti robots.txt. Contoh: scrape harga dari toko online yang terbuka untuk publik. HiQ v. LinkedIn (2022) menetapkan bahwa scraping data public gak melanggar CFAA di AS.
  • Borderline: Scrape data public untuk commercial use (tergantung jurisdiction). Di EU, GDPR bisa apply kalau data yang di-scrape mengandung PII. Kalau lo scrape data yang mengandung nama, email, atau nomor telepon — lo perlu legal basis untuk processing.
  • Ilegal: Bypass login/authentication, scrape data yang dilindungi copyright, melanggar Terms of Service secara eksplisit. Contoh: scrape data dari platform yang ToS-nya secara eksplisit melarang scraping.

Rule of thumb: kalau data bisa diakses tanpa login dan lo gak bikin server down, kemungkinan besar legal. Tapi selalu cek robots.txt dan ToS dulu. Dan kalau lo ragu, konsultasi lawyer — serius, $200 konsultasi bisa hemat $200K masalah hukum.

Toolkit: Pilih yang Tepat

ToolBest ForLearning CurveSpeedRAM Usage
requests + BeautifulSoupStatic HTML pagesLowFast~10MB
PlaywrightJS-rendered pagesMediumMedium~30MB/tab
ScrapyLarge-scale crawlingHighVery Fast~50MB
SeleniumLegacy browser automationMediumSlow~100MB/tab
Crawl4AIAI-powered extractionMediumMedium~200MB

Gw paling sering pakai combination: Playwright untuk rendering (karena banyak situs modern pakai JavaScript), Scrapy untuk scale (ketika perlu scrape ratusan halaman). Pilihan tergantung use case — gak ada satu tool yang cocok untuk semua.

Contoh 1: Static Scraping dengan BeautifulSoup

Untuk website statik (server-side rendered), requests + BeautifulSoup sudah cukup. Ini pattern yang umum dipake — lengkap dengan error handling dan ethical scraping practices:

import requests
from bs4 import BeautifulSoup
import time
import random

class EthicalScraper:
    def __init__(self, base_url, delay=2.0):
        self.base_url = base_url
        self.delay = delay
        self.session = requests.Session()
        self.session.headers.update({
            'User-Agent': 'MyBot/1.0 (+https://mysite.com/bot-info)',
            'Accept-Language': 'en-US,en;q=0.9',
            'Accept-Encoding': 'gzip, deflate',
        })
    
    def fetch(self, url):
        # Jitter delay: 1.5x - 2.5x base delay
        jitter = self.delay * (1.5 + random.random())
        time.sleep(jitter)
        
        try:
            resp = self.session.get(url, timeout=30)
            resp.raise_for_status()
            return resp.text
        except requests.RequestException as e:
            print(f"Error fetching {url}: {e}")
            return None
    
    def parse_products(self, html):
        soup = BeautifulSoup(html, 'html.parser')
        products = []
        for item in soup.select('.product-card'):
            products.append({
                'name': item.select_one('.title').text.strip(),
                'price': item.select_one('.price').text.strip(),
                'url': item.select_one('a')['href'],
            })
        return products

Yang penting di sini:

  • User-Agent yang deskriptif: Bukan anonymous — kasih tahu siapa lo. Banyak server yang block anonymous user agents. Kalau lo bisa kasih URL ke bot info page, lebih baik lagi.
  • Jitter delay: Bukan fixed delay — tambahin randomness. Fixed delay keliatan seperti bot. Random delay lebih natural dan harder to detect.
  • Timeout: Jangan pernah scrape tanpa timeout. 30 detik cukup untuk kebanyakan website. Kalau timeout, skip dan log untuk retry nanti.
  • Session reuse: Pakai requests.Session() untuk reuse TCP connection. Lebih cepat dan lebih polite ke server.

Contoh 2: JavaScript-Rendered Pages dengan Playwright

Banyak website modern render content pakai JavaScript. BeautifulSoup gak bisa handle ini — lo butuh browser engine. Playwright adalah pilihan terbaik karena cepat, reliable, dan support multiple browsers (Chromium, Firefox, WebKit).

import asyncio
from playwright.async_api import async_playwright

async def scrape_js_site(url):
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page()
        
        # Blokir resource yang gak diperlukan (hemat bandwidth 60-80%)
        await page.route("**/*.{png,jpg,jpeg,gif,svg,css,font,woff,woff2}", 
                        lambda route: route.abort())
        
        await page.goto(url, wait_until='networkidle')
        await page.wait_for_selector('.product-list', timeout=10000)
        
        # Infinite scroll handling
        prev_height = 0
        while True:
            height = await page.evaluate('document.body.scrollHeight')
            if height == prev_height:
                break
            prev_height = height
            await page.evaluate('window.scrollTo(0, document.body.scrollHeight)')
            await page.wait_for_timeout(1500)
        
        products = await page.evaluate('''() => {
            return Array.from(
                document.querySelectorAll('.product-item')
            ).map(el => ({
                name: el.querySelector('.name')?.textContent?.trim(),
                price: el.querySelector('.price')?.textContent?.trim(),
                rating: parseFloat(
                    el.querySelector('.rating')?.dataset?.value || 0
                ),
            }));
        }''')
        
        await browser.close()
        return products

results = asyncio.run(scrape_js_site('https://example.com/products'))

Beberapa tips Playwright yang gw pelajari dari production usage:

  • Block CSS/fonts/images: Hemat 60-80% bandwidth. Lo cuma butuh HTML + data, bukan tampilan visual.
  • Infinite scroll: Banyak website modern pakai infinite scroll. Handle dengan loop yang compare height sebelum dan sesudah scroll.
  • wait_until='networkidle': Tunggu sampai gak ada network request lagi. Lebih reliable dari menunggu selector tertentu.
  • Headless mode: Selalu pakai headless di production. Headed mode cuma untuk debugging.

Playwright headless mode pakai ~30MB RAM per tab. Kalau lo scrape 10 pages concurrent, butuh ~300MB RAM. Di VPS 2GB, ini masih aman — tapi monitor memory usage dan kurangi concurrency kalau perlu. Gw biasanya pakai concurrency 3-5 untuk VPS 2GB, dan 10-15 untuk VPS 4GB.

Contoh 3: Large-Scale dengan Scrapy

Ketika perlu scrape ribuan halaman, Scrapy adalah jawabannya. Built-in support untuk concurrent requests, retry, middleware, dan pipeline. Learning curve-nya steep, tapi investasi waktu worth it untuk large-scale projects.

import scrapy

class ProductSpider(scrapy.Spider):
    name = 'products'
    start_urls = ['https://example.com/sitemap.xml']
    custom_settings = {
        'CONCURRENT_REQUESTS': 4,
        'DOWNLOAD_DELAY': 2,
        'RANDOMIZE_DOWNLOAD_DELAY': True,
        'ROBOTSTXT_OBEY': True,
        'RETRY_TIMES': 3,
        'HTTPCACHE_ENABLED': True,
        'LOG_LEVEL': 'INFO',
    }
    
    def parse(self, response):
        for url in response.xpath('//url/loc/text()').getall():
            yield response.follow(url, self.parse_product)
    
    def parse_product(self, response):
        yield {
            'name': response.css('.title::text').get('').strip(),
            'price': response.css('.price::text').get('').strip(),
            'category': response.css('.category::text').get('').strip(),
            'url': response.url,
        }

Scrapy memang steep learning curve, tapi setelah paham, dia是最 efficient tool untuk large-scale scraping. Built-in features yang paling gw appreciate:

  • Auto-throttle: Automatically adjust speed berdasarkan server response. Kalau server mulai lambat, Scrapy otomatis slow down.
  • HTTP cache: Cache responses untuk avoid re-fetch. Critical untuk development dan debugging — lo gak perlu re-scrape halaman yang sama.
  • Pipeline: Process, validate, dan store data secara berurutan. Lo bisa bikin pipeline untuk cleaning, deduplication, dan storage.
  • Middleware: Proxy rotation, user-agent rotation, retry logic. Lo bisa rotate IP addresses untuk avoid rate limiting.

Error Handling dan Resilience

Web scraping yang robust harus handle berbagai error: timeout, rate limiting, captchas, dan perubahan HTML structure. Ini pattern yang umum dipake — tenacity library untuk retry dengan exponential backoff:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=2, max=30)
)
def scrape_with_retry(url, scraper):
    html = scraper.fetch(url)
    if html is None:
        raise Exception("Failed to fetch")
    
    data = scraper.parse(html)
    if not data:
        raise Exception("No data found")
    
    return data

Pattern exponential backoff adalah kunci: kalau server bilang "slow down", lo tunggu 2 detik, lalu 4, lalu 8, dst. Ini jauh lebih baik dari retry langsung yang cuma bikin server makin overwhelmed. Dan max 3 attempts — kalau gagal 3 kali, kemungkinan besar ada masalah fundamental, bukan transient error.

Data Storage dan Processing

Hasil scraping harus di-store dengan benar. Pilihan gw tergantung skala:

  • CSV/JSON: Untuk dataset kecil (kurang dari 100K rows). Simple, portable, bisa di-open di Excel.
  • SQLite: Untuk dataset medium. Query-able, single file, zero config. Perfect untuk personal projects.
  • PostgreSQL + Parquet: Untuk pipeline production. Query-able + compressed archive. Cocok untuk analytics workload.
import sqlite3

def store_to_sqlite(data, db_path='scraped_data.db'):
    conn = sqlite3.connect(db_path)
    conn.execute('''CREATE TABLE IF NOT EXISTS products (
        id INTEGER PRIMARY KEY,
        name TEXT,
        price TEXT,
        url TEXT UNIQUE,
        scraped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )''')
    
    for item in data:
        try:
            conn.execute(
                'INSERT OR IGNORE INTO products '
                '(name, price, url) VALUES (?, ?, ?)',
                (item['name'], item['price'], item['url'])
            )
        except sqlite3.Error as e:
            print(f"DB error: {e}")
    
    conn.commit()
    conn.close()
    print(f"Stored {len(data)} items to {db_path}")

Monitoring Pipeline

Scraping pipeline yang gak di-monitor = pipeline yang sooner atau later broken. Gw setup monitoring sederhana yang alert ke Telegram kalau ada masalah:

#!/bin/bash
# /usr/local/bin/scraper-monitor.sh
ITEMS_TODAY=$(sqlite3 /data/scraped.db   "SELECT COUNT(*) FROM products WHERE date(scraped_at) = date('now')")

if [ "$ITEMS_TODAY" -lt 10 ]; then
    curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage"       -d "chat_id=$CHAT_ID&text=Scraper WARNING: Only $ITEMS_TODAY items today"
fi

Alert kalau jumlah items hari ini kurang dari threshold. Ini detect kasus seperti website yang block IP kita, HTML structure yang berubah, atau selector yang invalid. Gw biasanya set threshold berdasarkan rata-rata 7 hari terakhir — kalau today items kurang dari 50% average, alert triggers.

Checklist Sebelum Scraping

ItemActionWhy
robots.txtBaca dan patuhiLegal requirement + ethical
ToS websiteCek scraping policyHindari masalah hukum
API availabilityCek ada API atau tidakAPI lebih baik dari scraping
Rate limitingSet delay 1-2 detik + jitterJangan bikin server down
User-AgentSet identitas botTransparency, bukan anonymous
Error handlingRetry + timeout + loggingRobust pipeline
Data retentionHapus data yang gak diperlukanPrivacy compliance

Web scraping adalah skill yang valuable — tapi harus dilakukan dengan benar. Respect server target, handle errors gracefully, dan selalu punya fallback plan. Untuk automation pipeline yang lebih lanjut, cek juga artikel tentang CI/CD pipeline self-hosted dan logging monitoring.

💬 Komentar (0)

Belum ada komentar. Jadilah yang pertama! 💬

Komentar akan muncul setelah moderasi.