Ketika parser berpindah dari satu server ke kluster yang terdiri dari beberapa pekerja, muncul masalah baru — beberapa mesin mulai menggunakan kumpulan proxy yang sama secara bersamaan. Tanpa koordinasi, ini menyebabkan duplikasi IP dalam permintaan, melebihi batas untuk satu alamat, dan larangan massal. Dalam artikel ini, kita akan membahas cara yang tepat untuk membagi proxy antara server, skema distribusi mana yang bekerja paling baik, dan bagaimana menerapkannya dalam praktik dengan kode Python dan Redis.
Mengapa proxy bertabrakan dalam parser terdistribusi
Mari kita bayangkan situasi yang umum: Anda memiliki kumpulan 500 proxy dan tiga server, masing-masing menjalankan 50 pekerja paralel untuk parsing Wildberries, Ozon, atau Avito. Jika setiap server secara independen mengambil proxy dari daftar umum tanpa koordinasi, dua pekerja di mesin yang berbeda dapat secara bersamaan mengirimkan permintaan dari IP yang sama ke domain yang sama. Untuk sistem anti-bot, ini terlihat seperti frekuensi permintaan yang tidak normal tinggi dari satu alamat — dan IP tersebut akan diblokir dalam hitungan menit.
Masalah kedua adalah distribusi beban yang tidak merata. Jika proxy hanya dibagi "semaunya", satu server dapat mengambil IP yang paling cepat dan terbaru, sementara yang lain mendapatkan sisa dengan reputasi yang sudah rusak. Akibatnya, kecepatan parsing di server akan berbeda jauh, dan persentase kesalahan juga.
Masalah ketiga adalah kehilangan status. Jika proxy diblokir di satu server, server lainnya tidak akan mengetahuinya dan akan terus menggunakannya sampai mereka juga mendapatkan larangan. Tanpa pencatatan status proxy yang terpusat, setiap server "membuka Amerika" lagi, membuang permintaan dengan sia-sia.
Solusi untuk ketiga masalah ini adalah memperkenalkan mekanisme koordinasi tunggal: baik melalui penyimpanan status terpusat (Redis, PostgreSQL, Consul), atau melalui pembagian kumpulan yang ditentukan sebelumnya, tanpa tumpang tindih rentang antara server.
Arsitektur parser terdistribusi
Sebelum membagi proxy, perlu untuk menentukan arsitektur umum kluster. Ada dua pendekatan dasar.
Skema statis (shared-nothing). Kumpulan proxy dibagi antara server sebelumnya, sekali pada saat mulai. Setiap server menerima segmen yang tidak tumpang tindih dan bekerja secara mandiri, tanpa merujuk ke server lain. Kelebihan — kesederhanaan maksimum dan ketahanan terhadap kegagalan: jatuhnya satu server tidak mempengaruhi yang lainnya. Kekurangan — jika setengah dari proxy diblokir di satu server, ia tidak dapat "meminjam" IP baru dari tetangga.
Skema dinamis (shared-state). Semua server merujuk ke penyimpanan umum (biasanya Redis), yang menyimpan status setiap proxy dan mengeluarkannya berdasarkan permintaan dengan penguncian. Kelebihan — pemanfaatan kumpulan yang merata dan reaksi instan terhadap larangan. Kekurangan — penyimpanan menjadi titik kegagalan tunggal, memerlukan logika terpisah untuk situasi ketika tidak tersedia.
Dalam praktiknya, untuk parsing marketplace dan media sosial, sering dipilih hibrida: broker di Redis mengeluarkan proxy dengan penguncian untuk waktu singkat (TTL), dan ketika Redis tidak tersedia, server sementara beralih ke kumpulan fallback lokal. Pendekatan ini menggabungkan keandalan skema statis dengan fleksibilitas skema dinamis.
Metode pembagian kumpulan proxy antara server
Mari kita lihat algoritma konkret yang dapat diterapkan untuk membagi kumpulan.
Pembagian sederhana berdasarkan modulus (round-robin sharding). Proxy dinomori dari 0 hingga N-1, server dengan nomor i mengambil semua proxy yang index % jumlah_server == i. Sederhana, tetapi buruk dalam menghadapi penambahan atau penghapusan server — ketika jumlah mesin berubah, hampir seluruh kumpulan didistribusikan ulang.
Hashing konsisten. Setiap server dan setiap proxy diberikan posisi di cincin hash (biasanya berdasarkan hash alamat IP atau ID server). Proxy terikat ke server terdekat di cincin. Saat menambahkan atau menghapus server, hanya sebagian kecil proxy yang didistribusikan ulang, bukan seluruh kumpulan — ini adalah keuntungan kunci untuk kluster yang berkembang secara dinamis (misalnya, di Kubernetes dengan penskalaan otomatis).
Broker terpusat dengan penguncian. Alih-alih mengikat proxy secara ketat ke server, semua server meminta proxy "bebas" dari layanan umum (Redis, RabbitMQ), yang mengeluarkannya dengan penguncian sementara. Setelah digunakan, proxy dikembalikan ke kumpulan. Ini adalah opsi paling fleksibel, terutama jika beban di server tidak merata.
Pembagian berdasarkan domain atau tugas. Jika parser bekerja dengan beberapa sumber sekaligus (misalnya, Wildberries, Ozon, dan Avito secara paralel), ada baiknya membagi tidak hanya proxy antara server, tetapi juga proxy antara sumber — agar IP yang sama tidak terlihat di beberapa platform sekaligus. Ini mengurangi risiko IP masuk ke daftar hitam umum dari sistem anti-fraud, jika platform-platform tersebut saling bertukar data.
Tabel: perbandingan metode sharding proxy
| Metode | Kompleksitas | Ketahanan terhadap penskalaan | Kapan diterapkan |
|---|---|---|---|
| Pembagian berdasarkan modulus | Rendah | Buruk | Jumlah server tetap |
| Hashing konsisten | Sedang | Baik | Penskalaan otomatis kluster |
| Broker terpusat | Sedang-tinggi | Luar biasa | Beban tidak merata, lalu lintas tinggi |
| Pembagian berdasarkan domain | Rendah | Sedang | Parsing beberapa platform sekaligus |
Implementasi broker proxy di Redis
Opsi paling praktis untuk sebagian besar parser terdistribusi adalah broker terpusat di Redis. Redis cocok karena cepat, mendukung operasi atomik dan TTL "dari kotak". Ide dasarnya sederhana: semua proxy yang tersedia disimpan dalam Redis Set, dan saat mengeluarkan proxy untuk pekerja tertentu, proxy tersebut dikunci untuk waktu tertentu melalui kunci dengan TTL.
Di bawah ini adalah contoh kerja broker dalam Python dengan pustaka redis-py. Ini mengimplementasikan pengeluaran proxy bebas dengan penguncian, pembebasan setelah digunakan, dan penandaan proxy sebagai yang diblokir.
import redis
import time
import random
r = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
PROXY_POOL_KEY = "proxy:pool" # himpunan semua proxy yang bekerja
LOCK_PREFIX = "proxy:lock:" # prefix penguncian untuk proxy tertentu
BANNED_PREFIX = "proxy:banned:" # prefix penandaan larangan
LOCK_TTL = 30 # detik, berapa lama kita mengunci proxy
BAN_TTL = 3600 # detik, berapa lama kita menganggap proxy diblokir
def load_pool(proxy_list):
"""Memuat daftar proxy ke Redis sekali saat kluster dimulai."""
if proxy_list:
r.sadd(PROXY_POOL_KEY, *proxy_list)
def get_free_proxy(server_id, max_attempts=20):
"""Setiap server memanggil fungsi ini untuk mendapatkan proxy bebas."""
candidates = list(r.smembers(PROXY_POOL_KEY))
random.shuffle(candidates)
for proxy in candidates[:max_attempts]:
if r.exists(BANNED_PREFIX + proxy):
continue
lock_key = LOCK_PREFIX + proxy
# pengaturan penguncian atomik, jika proxy bebas
acquired = r.set(lock_key, server_id, nx=True, ex=LOCK_TTL)
if acquired:
return proxy
return None # tidak ada proxy bebas, perlu fallback
def release_proxy(proxy):
"""Membebaskan proxy setelah permintaan berhasil."""
r.delete(LOCK_PREFIX + proxy)
def mark_banned(proxy):
"""Menandai proxy sebagai diblokir selama BAN_TTL detik, menghapus penguncian."""
r.set(BANNED_PREFIX + proxy, 1, ex=BAN_TTL)
r.delete(LOCK_PREFIX + proxy)
r.srem(PROXY_POOL_KEY, proxy) # sementara menghapus dari kumpulan
def return_to_pool_if_recovered():
"""Tugas berkala: mengembalikan proxy yang masa larangannya telah berakhir."""
for proxy in r.scan_iter(match=BANNED_PREFIX + "*"):
clean_proxy = proxy.replace(BANNED_PREFIX, "")
if not r.exists(BANNED_PREFIX + clean_proxy):
r.sadd(PROXY_POOL_KEY, clean_proxy)
Poin kunci di sini adalah penggunaan SET ... NX EX di Redis. Perintah ini atomik: jika dua server mencoba mengunci proxy yang sama secara bersamaan, hanya satu dari mereka yang akan berhasil, yang lainnya akan ditolak dan mencari kandidat berikutnya dari daftar. Inilah yang menghilangkan konflik antara server saat mengakses kumpulan umum secara paralel.
Contoh penggunaan broker dalam pekerja parser:
import requests
def fetch_with_proxy(url, server_id):
proxy = get_free_proxy(server_id)
if not proxy:
raise RuntimeError("Tidak ada proxy bebas dalam kumpulan")
proxies = {"http": f"http://{proxy}", "https": f"http://{proxy}"}
try:
response = requests.get(url, proxies=proxies, timeout=10)
if response.status_code in (403, 429):
mark_banned(proxy)
else:
release_proxy(proxy)
return response
except requests.exceptions.RequestException:
mark_banned(proxy)
raise
Skema ini mudah diskalakan — Anda dapat menambahkan 10 atau 50 server, mereka akan merujuk ke instansi Redis yang sama, dan tidak akan ada konflik IP karena penguncian bersifat atomik di tingkat penyimpanan, bukan di tingkat server individu.
Sesi lengket dan pengikatan proxy ke tugas
Untuk beberapa tugas, pengeluaran "proxy bebas mana saja" tidak cukup. Jika parser meniru perilaku pengguna — misalnya, secara berurutan melihat kartu produk, menambahkannya ke keranjang, berpindah halaman katalog — penting agar semua permintaan dalam satu sesi berasal dari IP yang sama. Jika tidak, situs akan melihat perubahan geolokasi atau perangkat yang tiba-tiba di tengah sesi dan memblokirnya sebagai mencurigakan.
Untuk ini, diperkenalkan konsep sesi lengket: proxy diikat tidak hanya ke satu permintaan, tetapi ke tugas tertentu atau sesi pengguna selama masa hidupnya. Di Redis, ini diimplementasikan melalui kunci terpisah dengan format session:{task_id} -> proxy dengan TTL yang sama dengan durasi sesi yang diharapkan.
SESSION_PREFIX = "proxy:session:"
SESSION_TTL = 600 # 10 menit untuk sesi
def get_sticky_proxy(task_id, server_id):
existing = r.get(SESSION_PREFIX + task_id)
if existing:
return existing
proxy = get_free_proxy(server_id)
if proxy:
r.set(SESSION_PREFIX + task_id, proxy, ex=SESSION_TTL)
return proxy
Untuk tugas dengan sesi panjang — misalnya, menjelajahi katalog Wildberries dengan meniru perilaku pembeli yang sebenarnya — sangat penting untuk menggunakan proxy yang berperilaku semirip mungkin dengan pengguna biasa. Di sini, proxy residensial sangat cocok, karena mereka terikat pada IP rumah yang nyata dan jarang menimbulkan kecurigaan dari sistem anti-bot marketplace dibandingkan dengan alamat dari pusat data.
Penanganan larangan dan rotasi dalam kluster
Dalam sistem terdistribusi, penting agar informasi tentang larangan proxy segera tersedia untuk semua server, bukan hanya untuk yang menerima larangan tersebut. Dalam contoh di atas, ini ditangani oleh fungsi mark_banned — begitu satu server mencatat 403 atau 429 dari situs target, proxy dihapus dari kumpulan umum di Redis, dan server lainnya secara otomatis berhenti menerima proxy tersebut.
Untuk menentukan larangan, sebaiknya menganalisis tidak hanya kode HTTP respons, tetapi juga konten halaman — beberapa situs mengembalikan 200 OK, tetapi dengan captcha atau placeholder alih-alih konten yang sebenarnya. Logika deteksi larangan biasanya terlihat seperti ini:
- Kode HTTP 403, 429, 503 — tanda jelas pemblokiran atau batasan laju.
- Pengalihan ke halaman captcha atau otorisasi — memeriksa URL setelah permintaan.
- Pemangkasan ukuran respons yang tajam atau tidak adanya pemilih CSS yang diharapkan — tanda placeholder.
- Timeout koneksi — sering berarti bahwa proxy tidak lagi merespons sama sekali.
Setelah mencatat larangan, sebaiknya tidak hanya menghapus proxy dari kumpulan selamanya, tetapi menempatkannya dalam "karantina" dengan TTL (seperti dalam contoh — BAN_TTL), setelah itu proxy secara otomatis kembali ke kumpulan. Banyak larangan di marketplace dan media sosial bersifat sementara dan dicabut setelah 30-60 menit.
Jika larangan terjadi terlalu sering bahkan dengan distribusi beban yang merata, biasanya ini adalah sinyal bahwa kumpulan proxy itu sendiri memiliki kualitas rendah atau ukuran yang tidak memadai untuk volume permintaan yang diperlukan. Dalam kasus seperti itu, memperluas kumpulan dengan proxy pusat data untuk tugas yang kurang sensitif (misalnya, memeriksa ketersediaan halaman) dan menggunakan IP residensial yang lebih mahal hanya untuk sesi kritis membantu mengurangi persentase larangan secara keseluruhan tanpa meningkatkan biaya untuk seluruh lalu lintas.
Pemantauan kesehatan kumpulan proxy
Dalam kluster yang terdiri dari beberapa server, mudah untuk kehilangan gambaran umum: berapa banyak proxy yang aktif saat ini, berapa banyak yang diblokir, berapa kecepatan respons rata-rata dari masing-masing. Tanpa pemantauan, sulit untuk memahami mengapa parsing melambat — apakah satu server jatuh, atau apakah proxy yang hidup telah habis.
Setidaknya, kumpulan metrik yang perlu dikumpulkan di Redis atau basis data terpisah:
- Jumlah proxy aktif dalam kumpulan saat ini.
- Jumlah proxy yang diblokir dan waktu rata-rata hingga pemulihan mereka.
- Waktu respons rata-rata untuk setiap proxy selama N permintaan terakhir.
- Persentase permintaan yang berhasil (success rate) untuk server dan untuk kumpulan secara keseluruhan.
- Pembagian beban — berapa banyak permintaan yang diproses oleh setiap server selama periode tertentu.
Untuk visualisasi, nyaman untuk mengumpulkan metrik ini di Prometheus melalui eksportir sederhana, yang setiap menit membaca data dari Redis dan mengeluarkannya dalam format yang dipahami oleh Grafana. Ini memungkinkan untuk dengan cepat melihat penurunan success rate untuk geolokasi tertentu atau jenis proxy dan segera menambahkan IP baru ke kumpulan, sebelum parser berhenti sepenuhnya.
Secara terpisah, perlu untuk memantau puncak larangan saat meluncurkan server baru dalam kluster — jika saat penskalaan dari 3 menjadi 6 server, persentase larangan meningkat tajam, kemungkinan besar masalahnya ada pada sharding: server baru mulai tumpang tindih rentang proxy dengan yang lama. Ini adalah tanda pertama bahwa perlu beralih dari pembagian berdasarkan modulus ke hashing konsisten.
Daftar periksa untuk memulai parser terdistribusi
Sebelum meluncurkan parser di beberapa server, periksa poin-poin berikut:
- Mekanisme koordinasi kumpulan telah dipilih: pembagian statis, hashing konsisten, atau broker terpusat di Redis.
- Penguncian atomik proxy telah diimplementasikan, mencegah penggunaan satu IP oleh dua server secara bersamaan.
- TTL untuk penguncian dan larangan telah diatur, agar proxy tidak "terjebak" dalam keadaan diblokir selamanya saat pekerja jatuh.
- Untuk tugas yang memerlukan emulasi sesi pengguna, pengikatan sticky-proxy telah diatur untuk seluruh durasi tugas.
- Deteksi larangan telah diatur tidak hanya berdasarkan kode HTTP, tetapi juga berdasarkan konten respons.
- Pemantauan success rate, proxy aktif, dan proxy yang diblokir di seluruh kluster telah diaktifkan.
- Kumpulan proxy telah dibagi berdasarkan jenis tugas: IP pusat data — untuk permintaan yang kurang sensitif, proxy residensial atau proxy seluler — untuk sesi di mana kesamaan maksimum dengan pengguna nyata penting.
- Beban pada penyimpanan status (Redis) telah diperiksa — dengan banyak server dan frekuensi permintaan yang tinggi, mungkin diperlukan kluster Redis daripada satu instansi.
Kesimpulan
Alasan utama konflik saat memindahkan parser ke beberapa server adalah kurangnya sumber kebenaran tunggal tentang status kumpulan proxy. Begitu broker terpusat dengan penguncian atomik (di Redis atau penyimpanan serupa) muncul, masalah duplikasi IP dan desinkronisasi larangan menghilang dengan sendirinya. Selanjutnya, tinggal memilih strategi sharding sesuai dengan skala kluster Anda: untuk jumlah server tetap, pembagian sederhana sudah cukup, sementara untuk infrastruktur yang tumbuh secara dinamis, lebih baik langsung menerapkan hashing konsisten.
Jika parser terdistribusi Anda bekerja dengan marketplace atau media sosial, di mana kredibilitas lalu lintas pada tingkat sesi individu penting, sebaiknya bagi kumpulan berdasarkan jenis tugas: untuk pemindaian katalog massal, proxy pusat data yang murah cocok, sementara untuk sesi yang meniru perilaku pengguna — proxy residensial dengan IP rumah nyata, yang secara signifikan mengurangi persentase larangan dalam sistem yang sensitif terhadap penipuan.