Kembali ke blog

Cara Menghindari Pemblokiran Saat Melakukan Permintaan Massal

Membahas mekanisme deteksi otomatisasi dan teknik perlindungan dari pemblokiran saat melakukan permintaan massal: dari rotasi proxy dasar hingga simulasi perilaku manusia.

📅21 Desember 2025
```html

Perlindungan dari Pemblokiran Saat Melakukan Permintaan Massal: Teknik dan Alat

Pemblokiran akun dan alamat IP adalah masalah utama saat melakukan parsing, otomatisasi, dan operasi massal di media sosial. Sistem anti-bot modern menganalisis puluhan parameter: dari frekuensi permintaan hingga sidik jari browser. Dalam panduan ini, kita akan membahas mekanisme deteksi otomatisasi yang spesifik dan cara praktis untuk menghindarinya.

Mekanisme Deteksi Otomatisasi

Sistem perlindungan modern menggunakan analisis multi-lapis untuk mendeteksi bot. Memahami mekanisme ini sangat penting untuk memilih strategi bypass yang tepat.

Parameter Analisis Utama

Reputasi IP: Sistem anti-bot memeriksa riwayat alamat IP, afiliasi dengan pusat data, dan keberadaan dalam daftar hitam. IP dari kumpulan proxy yang dikenal lebih sering diblokir.

Frekuensi Permintaan (Request Rate): Manusia secara fisik tidak dapat mengirim 100 permintaan dalam satu menit. Sistem menganalisis tidak hanya jumlah total, tetapi juga distribusi dalam waktu — interval yang merata antara permintaan menunjukkan bot.

Pola Perilaku: Urutan tindakan, kedalaman scroll, gerakan mouse, waktu di halaman. Bot yang langsung berpindah antar tautan tanpa jeda mudah dikenali.

Sidik Jari Teknis: User-Agent, header HTTP, urutan header, sidik jari TLS, fingerprinting Canvas/WebGL. Ketidaksesuaian dalam parameter ini adalah sinyal merah bagi sistem anti-bot.

Parameter Apa yang Dianalisis Risiko Deteksi
Alamat IP Reputasi, ASN, geolokasi Tinggi
User-Agent Versi browser, OS, perangkat Sedang
TLS Fingerprint Kumpulan cipher, ekstensi Tinggi
HTTP/2 Fingerprint Urutan header, pengaturan Tinggi
Canvas/WebGL Penggambaran grafik Sedang
Perilaku Klik, scroll, waktu Tinggi

Pembatasan Laju dan Kontrol Frekuensi Permintaan

Kontrol kecepatan pengiriman permintaan adalah garis pertahanan pertama terhadap pemblokiran. Bahkan dengan rotasi proxy, parsing yang terlalu agresif dapat menyebabkan pemblokiran.

Penundaan Dinamis

Interval tetap (misalnya, tepat 2 detik antara permintaan) mudah dikenali. Gunakan penundaan acak dengan distribusi normal:

import time
import random
import numpy as np

def human_delay(min_delay=1.5, max_delay=4.0, mean=2.5, std=0.8):
    """
    Menghasilkan penundaan dengan distribusi normal
    yang meniru perilaku manusia
    """
    delay = np.random.normal(mean, std)
    # Batasi rentang
    delay = max(min_delay, min(delay, max_delay))
    
    # Tambahkan penundaan mikro untuk realisme
    delay += random.uniform(0, 0.3)
    
    time.sleep(delay)

# Penggunaan
for url in urls:
    response = session.get(url)
    human_delay(min_delay=2, max_delay=5, mean=3, std=1)

Pembatasan Laju Adaptif

Pendekatan yang lebih canggih adalah menyesuaikan kecepatan berdasarkan respons server. Jika Anda menerima kode 429 (Terlalu Banyak Permintaan) atau 503, secara otomatis kurangi kecepatan:

class AdaptiveRateLimiter:
    def __init__(self, initial_delay=2.0):
        self.current_delay = initial_delay
        self.min_delay = 1.0
        self.max_delay = 30.0
        self.error_count = 0
        
    def wait(self):
        time.sleep(self.current_delay + random.uniform(0, 0.5))
        
    def on_success(self):
        # Secara bertahap mempercepat saat permintaan berhasil
        self.current_delay = max(
            self.min_delay, 
            self.current_delay * 0.95
        )
        self.error_count = 0
        
    def on_rate_limit(self):
        # Memperlambat secara drastis saat diblokir
        self.error_count += 1
        self.current_delay = min(
            self.max_delay,
            self.current_delay * (1.5 + self.error_count * 0.5)
        )
        print(f"Batas laju tercapai. Penundaan baru: {self.current_delay:.2f}s")

# Penerapan
limiter = AdaptiveRateLimiter(initial_delay=2.0)

for url in urls:
    limiter.wait()
    response = session.get(url)
    
    if response.status_code == 429:
        limiter.on_rate_limit()
        time.sleep(60)  # Jeda sebelum mencoba lagi
    elif response.status_code == 200:
        limiter.on_success()
    else:
        # Penanganan kesalahan lainnya
        pass

Tip Praktis: Kecepatan optimal bervariasi untuk situs yang berbeda. Platform besar (Google, Facebook) toleran terhadap 5-10 permintaan per menit dari satu IP. Situs kecil mungkin sudah memblokir pada 20-30 permintaan per jam. Selalu mulai dengan konservatif dan secara bertahap tingkatkan beban sambil memantau persentase kesalahan.

Rotasi Proxy dan Manajemen Alamat IP

Menggunakan satu alamat IP untuk permintaan massal menjamin pemblokiran. Rotasi proxy mendistribusikan beban dan mengurangi risiko deteksi.

Strategi Rotasi

1. Rotasi Berdasarkan Permintaan: Mengganti IP setelah setiap atau setiap N permintaan. Cocok untuk parsing mesin pencari, di mana anonimitas setiap permintaan penting.

2. Rotasi Berdasarkan Waktu: Mengganti IP setiap 5-15 menit. Efektif untuk bekerja dengan media sosial, di mana stabilitas sesi penting.

3. Sesi Lengket: Menggunakan satu IP untuk seluruh sesi pengguna (autentikasi, urutan tindakan). Sangat penting untuk situs dengan perlindungan terhadap CSRF.

import requests
from itertools import cycle

class ProxyRotator:
    def __init__(self, proxy_list, rotation_type='request', rotation_interval=10):
        """
        rotation_type: 'request' (setiap permintaan) atau 'time' (berdasarkan waktu)
        rotation_interval: jumlah permintaan atau detik
        """
        self.proxies = cycle(proxy_list)
        self.current_proxy = next(self.proxies)
        self.rotation_type = rotation_type
        self.rotation_interval = rotation_interval
        self.request_count = 0
        self.last_rotation = time.time()
        
    def get_proxy(self):
        if self.rotation_type == 'request':
            self.request_count += 1
            if self.request_count >= self.rotation_interval:
                self.current_proxy = next(self.proxies)
                self.request_count = 0
                print(f"Rotasi ke: {self.current_proxy}")
                
        elif self.rotation_type == 'time':
            if time.time() - self.last_rotation >= self.rotation_interval:
                self.current_proxy = next(self.proxies)
                self.last_rotation = time.time()
                print(f"Rotasi ke: {self.current_proxy}")
                
        return {'http': self.current_proxy, 'https': self.current_proxy}

# Contoh penggunaan
proxy_list = [
    'http://user:pass@proxy1.example.com:8000',
    'http://user:pass@proxy2.example.com:8000',
    'http://user:pass@proxy3.example.com:8000',
]

rotator = ProxyRotator(proxy_list, rotation_type='request', rotation_interval=5)

for url in urls:
    proxies = rotator.get_proxy()
    response = requests.get(url, proxies=proxies, timeout=10)

Memilih Tipe Proxy

Tipe Proxy Tingkat Kepercayaan Kecepatan Penggunaan
Pusat Data Rendah Tinggi Parsing sederhana, API
Residential Tinggi Sedang Media sosial, situs yang dilindungi
Mobile Sangat Tinggi Sedang Instagram, TikTok, anti-fraud

Untuk operasi massal di media sosial dan platform dengan perlindungan serius, gunakan proxy residential. Mereka terlihat seperti koneksi rumah biasa dan jarang masuk daftar hitam. Pusat data cocok untuk sumber daya yang kurang terlindungi, di mana kecepatan penting.

Fingerprinting Browser dan Sidik Jari TLS

Bahkan dengan rotasi IP, Anda dapat dikenali melalui sidik jari teknis browser dan koneksi TLS. Parameter ini unik untuk setiap klien dan sulit untuk dipalsukan.

TLS Fingerprinting

Saat membuat koneksi HTTPS, klien mengirim ClientHello dengan kumpulan cipher dan ekstensi yang didukung. Kombinasi ini unik untuk setiap pustaka. Misalnya, Python requests menggunakan OpenSSL, yang sidik jarinya mudah dibedakan dari Chrome.

Masalah: Pustaka standar (requests, urllib, curl) memiliki sidik jari yang berbeda dari browser nyata. Layanan seperti Cloudflare, Akamai, DataDome secara aktif menggunakan TLS fingerprinting untuk memblokir bot.

Solusi: Gunakan pustaka yang meniru sidik jari TLS browser. Untuk Python, ini adalah curl_cffi, tls_client, atau playwright/puppeteer untuk emulasi browser yang lengkap.

# Instalasi: pip install curl-cffi
from curl_cffi import requests

# Meniru Chrome 110
response = requests.get(
    'https://example.com',
    impersonate="chrome110",
    proxies={'https': 'http://proxy:port'}
)

# Alternatif: tls_client
import tls_client

session = tls_client.Session(
    client_identifier="chrome_108",
    random_tls_extension_order=True
)

response = session.get('https://example.com')

HTTP/2 Fingerprinting

Selain TLS, sistem anti-bot menganalisis parameter HTTP/2: urutan header, pengaturan frame SETTINGS, prioritas aliran. Pustaka standar tidak mematuhi urutan header yang tepat dari Chrome atau Firefox.

# Urutan header yang benar untuk Chrome
headers = {
    ':method': 'GET',
    ':authority': 'example.com',
    ':scheme': 'https',
    ':path': '/',
    'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'upgrade-insecure-requests': '1',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...',
    'accept': 'text/html,application/xhtml+xml...',
    'sec-fetch-site': 'none',
    'sec-fetch-mode': 'navigate',
    'sec-fetch-user': '?1',
    'sec-fetch-dest': 'document',
    'accept-encoding': 'gzip, deflate, br',
    'accept-language': 'en-US,en;q=0.9',
}

Canvas dan WebGL Fingerprinting

Browser menggambar grafik dengan cara yang berbeda tergantung pada GPU, driver, dan OS. Situs menggunakan ini untuk membuat sidik jari perangkat yang unik. Saat menggunakan browser headless (Selenium, Puppeteer), penting untuk menyembunyikan tanda-tanda otomatisasi:

// Puppeteer: menyembunyikan mode headless
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');

puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({
    headless: true,
    args: [
        '--disable-blink-features=AutomationControlled',
        '--no-sandbox',
        '--disable-setuid-sandbox',
        `--proxy-server=${proxyUrl}`
    ]
});

const page = await browser.newPage();

// Mengganti navigator.webdriver
await page.evaluateOnNewDocument(() => {
    Object.defineProperty(navigator, 'webdriver', {
        get: () => false,
    });
});

Header, Cookies, dan Manajemen Sesi

Penanganan yang benar terhadap header HTTP dan cookies sangat penting untuk meniru pengguna nyata. Kesalahan dalam parameter ini adalah penyebab umum pemblokiran.

Header Wajib

Setidaknya, header yang diperlukan untuk meniru browser Chrome:

import requests

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'en-US,en;q=0.9',
    'Accept-Encoding': 'gzip, deflate, br',
    'DNT': '1',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-User': '?1',
    'Cache-Control': 'max-age=0',
}

session = requests.Session()
session.headers.update(headers)

Manajemen Cookies

Banyak situs mengatur tracking cookies saat kunjungan pertama dan memeriksa keberadaannya pada permintaan berikutnya. Ketidakhadiran cookies atau ketidakcocokan adalah tanda bot.

import requests
import pickle

class SessionManager:
    def __init__(self, session_file='session.pkl'):
        self.session_file = session_file
        self.session = requests.Session()
        self.load_session()
        
    def load_session(self):
        """Memuat sesi yang disimpan"""
        try:
            with open(self.session_file, 'rb') as f:
                cookies = pickle.load(f)
                self.session.cookies.update(cookies)
        except FileNotFoundError:
            pass
            
    def save_session(self):
        """Menyimpan cookies untuk penggunaan kembali"""
        with open(self.session_file, 'wb') as f:
            pickle.dump(self.session.cookies, f)
            
    def request(self, url, **kwargs):
        response = self.session.get(url, **kwargs)
        self.save_session()
        return response

# Penggunaan
manager = SessionManager('instagram_session.pkl')
response = manager.request('https://www.instagram.com/explore/')

Penting: Saat melakukan rotasi proxy, jangan lupa untuk mengatur ulang cookies jika mereka terikat pada IP tertentu. Ketidaksesuaian antara IP dan cookies (misalnya, cookies dengan geolokasi AS dan IP dari Jerman) akan menimbulkan kecurigaan.

Referer dan Origin

Header Referer dan Origin menunjukkan dari mana pengguna berasal. Ketidakhadiran atau nilai yang tidak benar adalah sinyal merah.

# Urutan yang benar: beranda → kategori → produk
session = requests.Session()

# Langkah 1: mengunjungi beranda
response = session.get('https://example.com/')

# Langkah 2: berpindah ke kategori
response = session.get(
    'https://example.com/category/electronics',
    headers={'Referer': 'https://example.com/'}
)

# Langkah 3: melihat produk
response = session.get(
    'https://example.com/product/12345',
    headers={'Referer': 'https://example.com/category/electronics'}
)

Simulasi Perilaku Manusia

Parameter teknis hanyalah setengah dari masalah. Sistem anti-bot modern menganalisis pola perilaku: bagaimana pengguna berinteraksi dengan halaman, berapa lama mereka tinggal, bagaimana gerakan mouse.

Scroll dan Gerakan Mouse

Saat menggunakan Selenium atau Puppeteer, tambahkan gerakan mouse acak dan scroll halaman:

from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import random
import time

def human_like_mouse_move(driver):
    """Gerakan mouse acak di halaman"""
    action = ActionChains(driver)
    
    for _ in range(random.randint(3, 7)):
        x = random.randint(0, 1000)
        y = random.randint(0, 800)
        action.move_by_offset(x, y)
        action.pause(random.uniform(0.1, 0.3))
    
    action.perform()

def human_like_scroll(driver):
    """Simulasi scroll yang alami"""
    total_height = driver.execute_script("return document.body.scrollHeight")
    current_position = 0
    
    while current_position < total_height:
        # Langkah scroll acak
        scroll_step = random.randint(100, 400)
        current_position += scroll_step
        
        driver.execute_script(f"window.scrollTo(0, {current_position});")
        
        # Jeda dengan variasi
        time.sleep(random.uniform(0.5, 1.5))
        
        # Terkadang scroll sedikit kembali (seperti yang dilakukan orang)
        if random.random() < 0.2:
            back_scroll = random.randint(50, 150)
            current_position -= back_scroll
            driver.execute_script(f"window.scrollTo(0, {current_position});")
            time.sleep(random.uniform(0.3, 0.8))

# Penggunaan
driver = webdriver.Chrome()
driver.get('https://example.com')

human_like_mouse_move(driver)
time.sleep(random.uniform(2, 4))
human_like_scroll(driver)

Waktu di Halaman

Pengguna nyata menghabiskan waktu di halaman: membaca konten, melihat gambar. Bot yang langsung berpindah antar tautan mudah dikenali.

def realistic_page_view(driver, url, min_time=5, max_time=15):
    """
    Melihat halaman secara realistis dengan aktivitas
    """
    driver.get(url)
    
    # Penundaan awal (memuat dan "membaca")
    time.sleep(random.uniform(2, 4))
    
    # Scroll
    human_like_scroll(driver)
    
    # Aktivitas tambahan
    total_time = random.uniform(min_time, max_time)
    elapsed = 0
    
    while elapsed < total_time:
        action_choice = random.choice(['scroll', 'mouse_move', 'pause'])
        
        if action_choice == 'scroll':
            # Scroll kecil ke atas/bawah
            scroll_amount = random.randint(-200, 300)
            driver.execute_script(f"window.scrollBy(0, {scroll_amount});")
            pause = random.uniform(1, 3)
            
        elif action_choice == 'mouse_move':
            human_like_mouse_move(driver)
            pause = random.uniform(0.5, 2)
            
        else:  # pause
            pause = random.uniform(2, 5)
        
        time.sleep(pause)
        elapsed += pause

Pola Navigasi

Hindari pola mencurigakan: langsung berpindah ke halaman dalam, mengabaikan halaman beranda, menjelajahi semua elemen secara berurutan tanpa melewatkan.

Praktik Baik:

  • Mulailah dari halaman beranda atau bagian populer
  • Gunakan navigasi internal situs, bukan URL langsung
  • Terkadang kembali atau berpindah ke bagian lain
  • Variasikan kedalaman tampilan: tidak selalu sampai akhir
  • Tambahkan "kesalahan": berpindah ke tautan yang tidak ada, kembali

Menghindari Cloudflare, DataDome, dan Perlindungan Lainnya

Sistem anti-bot khusus memerlukan pendekatan yang komprehensif. Mereka menggunakan tantangan JavaScript, CAPTCHA, analisis perilaku waktu nyata.

Cloudflare

Cloudflare menggunakan beberapa lapisan perlindungan: Pemeriksaan Integritas Browser, Tantangan JavaScript, CAPTCHA. Untuk menghindari perlindungan dasar, cukup dengan sidik jari TLS yang benar dan menjalankan JavaScript:

# Opsi 1: cloudscraper (solusi otomatis untuk tantangan JS)
import cloudscraper

scraper = cloudscraper.create_scraper(
    browser={
        'browser': 'chrome',
        'platform': 'windows',
        'desktop': True
    }
)

response = scraper.get('https://protected-site.com')

# Opsi 2: undetected-chromedriver (untuk kasus yang lebih rumit)
import undetected_chromedriver as uc

options = uc.ChromeOptions()
options.add_argument('--proxy-server=http://proxy:port')

driver = uc.Chrome(options=options)
driver.get('https://protected-site.com')

# Menunggu tantangan selesai
time.sleep(5)

# Mendapatkan cookies untuk requests
cookies = driver.get_cookies()
session = requests.Session()
for cookie in cookies:
    session.cookies.set(cookie['name'], cookie['value'])

DataDome

DataDome menganalisis perilaku pengguna dalam waktu nyata: gerakan mouse, tulisan di keyboard, waktu. Untuk menghindari, diperlukan browser lengkap dengan simulasi aktivitas:

from playwright.sync_api import sync_playwright
import random

def bypass_datadome(url, proxy=None):
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False,  # DataDome mendeteksi headless
            proxy={'server': proxy} if proxy else None
        )
        
        context = browser.new_context(
            viewport={'width': 1920, 'height': 1080},
            user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64)...'
        )
        
        page = context.new_page()
        
        # Injeksi skrip untuk menyembunyikan otomatisasi
        page.add_init_script("""
            Object.defineProperty(navigator, 'webdriver', {get: () => false});
            window.chrome = {runtime: {}};
        """)
        
        page.goto(url)
        
        # Simulasi perilaku manusia
        time.sleep(random.uniform(2, 4))
        
        # Gerakan mouse acak
        for _ in range(random.randint(5, 10)):
            page.mouse.move(
                random.randint(100, 1800),
                random.randint(100, 1000)
            )
            time.sleep(random.uniform(0.1, 0.3))
        
        # Scroll
        page.evaluate(f"window.scrollTo(0, {random.randint(300, 800)})")
        time.sleep(random.uniform(1, 2))
        
        content = page.content()
        browser.close()
        
        return content

CAPTCHA

Untuk menyelesaikan CAPTCHA secara otomatis, gunakan layanan pengenalan (2captcha, Anti-Captcha) atau strategi penghindaran:

  • Kurangi frekuensi permintaan ke tingkat yang tidak memicu CAPTCHA
  • Gunakan IP residential bersih dengan reputasi baik
  • Bekerja melalui akun yang terautentikasi (mereka memiliki ambang CAPTCHA yang lebih tinggi)
  • Sebarkan beban berdasarkan waktu (hindari jam sibuk)

Pemantauan dan Penanganan Pemblokiran

Bahkan dengan praktik terbaik, pemblokiran tidak dapat dihindari. Penting untuk segera mendeteksi dan menangani dengan benar.

Indikator Pemblokiran

Sinyal Deskripsi Tindakan
HTTP 429 Terlalu Banyak Permintaan Tingkatkan penundaan, ganti IP
HTTP 403 Terlarang (pemblokiran IP) Ganti proxy, periksa fingerprint
CAPTCHA Verifikasi diperlukan Selesaikan atau kurangi aktivitas
Respons Kosong Konten tidak dimuat Periksa JavaScript, cookies
Pengalihan ke /blocked Pemblokiran yang jelas Perubahan strategi secara menyeluruh

Sistem Percobaan Ulang

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

def create_session_with_retries():
    """
    Sesi dengan percobaan otomatis dan penanganan kesalahan
    """
    session = requests.Session()
    
    retry_strategy = Retry(
        total=5,
        backoff_factor=2,  # 2, 4, 8, 16, 32 detik
        status_forcelist=[429, 500, 502, 503, 504],
        method_whitelist=["GET", "POST"]
    )
    
    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    
    return session

def safe_request(url, session, max_attempts=3):
    """
    Permintaan dengan penanganan pemblokiran
    """
    for attempt in range(max_attempts):
        try:
            response = session.get(url, timeout=15)
            
            # Memeriksa pemblokiran
            if response.status_code == 403:
                print(f"IP diblokir. Mengganti proxy...")
                # Logika mengganti proxy
                continue
                
            elif response.status_code == 429:
                wait_time = int(response.headers.get('Retry-After', 60))
                print(f"Dibatasi laju. Menunggu {wait_time}s...")
                time.sleep(wait_time)
                continue
                
            elif 'captcha' in response.text.lower():
                print("CAPTCHA terdeteksi")
                # Logika menyelesaikan CAPTCHA atau melewatkan
                return None
                
            return response
            
        except requests.exceptions.Timeout:
            print(f"Waktu habis pada percobaan {attempt + 1}")
            time.sleep(5 * (attempt + 1))
            
        except requests.exceptions.ProxyError:
            print("Kesalahan proxy. Mengganti...")
            # Mengganti proxy
            continue
            
    return None

Logging dan Analitik

Pantau metrik untuk mengoptimalkan strategi:

import logging
from collections import defaultdict
from datetime import datetime

class ScraperMetrics:
    def __init__(self):
        self.stats = {
            'total_requests': 0,
            'successful': 0,
            'rate_limited': 0,
            'blocked': 0,
            'captcha': 0,
            'errors': 0,
            'proxy_failures': defaultdict(int)
        }
        
    def log_request(self, status, proxy=None):
        self.stats['total_requests'] += 1
        
        if status == 200:
            self.stats['successful'] += 1
        elif status == 429:
            self.stats['rate_limited'] += 1
        elif status == 403:
            self.stats['blocked'] += 1
            if proxy:
                self.stats['proxy_failures'][proxy] += 1
                
    def get_success_rate(self):
        if self.stats['total_requests'] == 0:
            return 0
        return (self.stats['successful'] / self.stats['total_requests']) * 100
        
    def print_report(self):
        print(f"\n=== Laporan Scraping ===")
        print(f"Total permintaan: {self.stats['total_requests']}")
        print(f"Tingkat keberhasilan: {self.get_success_rate():.2f}%")
        print(f"Dibatasi laju: {self.stats['rate_limited']}")
        print(f"Diblokir: {self.stats['blocked']}")
        print(f"CAPTCHA: {self.stats['captcha']}")
        
        if self.stats['proxy_failures']:
            print(f"\nProxy yang bermasalah:")
            for proxy, count in sorted(
                self.stats['proxy_failures'].items(), 
                key=lambda x: x[1], 
                reverse=True
            )[:5]:
                print(f"  {proxy}: {count} kegagalan")

# Penggunaan
metrics = ScraperMetrics()

for url in urls:
    response = safe_request(url, session)
    if response:
        metrics.log_request(response.status_code, current_proxy)
    
metrics.print_report()

Indikator Optimal: Tingkat keberhasilan di atas 95% adalah hasil yang sangat baik. 80-95% dapat diterima, tetapi ada yang perlu diperbaiki. Di bawah 80% — tinjau strategi: mungkin terlalu agresif dalam pembatasan laju, proxy yang buruk, atau masalah dengan fingerprinting.

Kesimpulan

Perlindungan dari pemblokiran saat melakukan permintaan massal memerlukan pemahaman yang mendalam tentang mekanisme deteksi dan penerapan strategi yang tepat. Dengan mengikuti teknik dan alat yang dijelaskan dalam panduan ini, Anda dapat mengurangi risiko pemblokiran dan meningkatkan keberhasilan otomatisasi Anda.

```