Scrapy adalah salah satu framework Python yang paling kuat untuk web scraping, tetapi tanpa pengaturan proxy yang tepat, pengambil data Anda akan diblokir hanya dalam beberapa menit setelah mulai beroperasi. Dalam panduan ini, saya akan menunjukkan semua cara untuk mengintegrasikan proxy ke dalam Scrapy: dari pengaturan sederhana hingga metode rotasi IP yang lebih canggih dengan penanganan kesalahan otomatis.
Materi ini didasarkan pada pengalaman nyata dalam pengambilan data dari platform e-commerce besar dan situs yang dilindungi. Anda akan mendapatkan contoh kode siap pakai yang dapat langsung digunakan dalam proyek Anda.
Mengapa Scrapy tanpa proxy mendapatkan pemblokiran
Situs modern menggunakan perlindungan multi-lapis terhadap pengambilan data. Bahkan jika Anda telah mengatur User-Agent dan jeda antara permintaan, alamat IP Anda menunjukkan otomatisasi berdasarkan beberapa tanda:
- Frekuensi permintaan: satu IP melakukan 100+ permintaan per menit — tanda jelas bot
- Pola perilaku: penelusuran halaman secara berurutan tanpa transisi acak
- Ketiadaan JavaScript: Scrapy tidak menjalankan JS, yang mudah terdeteksi
- Geolokasi: akses dari pusat data alih-alih jaringan rumah
Hasilnya — pemblokiran berdasarkan IP selama beberapa jam atau hari. Perlindungan yang sangat agresif digunakan oleh marketplace (Amazon, Wildberries, Ozon), media sosial, dan situs dengan Cloudflare. Proxy menyelesaikan masalah ini dengan mendistribusikan permintaan di antara banyak alamat IP.
Penting: Bahkan dengan proxy, Anda perlu mematuhi batas kecepatan. Kecepatan yang disarankan: 1-3 permintaan per detik untuk satu IP. Untuk pengambilan data berkecepatan tinggi, gunakan kumpulan 50+ proxy dengan rotasi.
Pengaturan dasar proxy di Scrapy
Cara paling sederhana adalah dengan menyebutkan proxy langsung di pengaturan spider. Metode ini cocok untuk pengujian atau pengambilan data dalam jumlah kecil dengan satu server proxy.
Metode 1: Melalui meta di Request
import scrapy
class MySpider(scrapy.Spider):
name = 'example'
start_urls = ['https://example.com']
def start_requests(self):
proxy = 'http://username:password@proxy.example.com:8080'
for url in self.start_urls:
yield scrapy.Request(
url=url,
callback=self.parse,
meta={'proxy': proxy}
)
def parse(self, response):
# Logika pengambilan data Anda
self.log(f'Scraped {response.url} via {response.meta["proxy"]}')
Format proxy tergantung pada protokol dan metode autentikasi:
http://proxy.example.com:8080— tanpa autentikasihttp://user:pass@proxy.example.com:8080— dengan login/passwordsocks5://user:pass@proxy.example.com:1080— proxy SOCKS5
Metode 2: Pengaturan global di settings.py
# settings.py
# HTTP proxy untuk semua permintaan
HTTPPROXY_ENABLED = True
HTTPPROXY_AUTH_ENCODING = 'utf-8'
# Pengaturan melalui variabel lingkungan
HTTP_PROXY = 'http://username:password@proxy.example.com:8080'
HTTPS_PROXY = 'http://username:password@proxy.example.com:8080'
Metode ini nyaman untuk pengujian cepat, tetapi tidak cocok untuk produksi: tidak ada rotasi IP, jika proxy gagal, seluruh pengambil data berhenti, tidak mungkin menggunakan proxy yang berbeda untuk situs yang berbeda.
Membuat Proxy Middleware kustom
Untuk pengambilan data produksi, diperlukan middleware sendiri yang akan mengelola kumpulan proxy, menangani kesalahan, dan secara otomatis merotasi IP. Berikut adalah implementasi dasar:
# middlewares.py
import random
from scrapy import signals
from scrapy.exceptions import NotConfigured
class RandomProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
@classmethod
def from_crawler(cls, crawler):
# Memuat daftar proxy dari pengaturan
proxy_list = crawler.settings.getlist('PROXY_LIST')
if not proxy_list:
raise NotConfigured('PROXY_LIST tidak dikonfigurasi')
return cls(proxy_list)
def process_request(self, request, spider):
# Memilih proxy acak dari kumpulan
proxy = random.choice(self.proxy_list)
request.meta['proxy'] = proxy
spider.logger.info(f'Menggunakan proxy: {proxy}')
def process_exception(self, request, exception, spider):
# Jika terjadi kesalahan, coba proxy lain
proxy = random.choice(self.proxy_list)
request.meta['proxy'] = proxy
spider.logger.warning(
f'Kesalahan proxy, beralih ke: {proxy}'
)
return request
Sekarang kita atur penggunaan middleware di settings.py:
# settings.py
# Daftar proxy (bisa dimuat dari file atau API)
PROXY_LIST = [
'http://user1:pass1@proxy1.example.com:8080',
'http://user2:pass2@proxy2.example.com:8080',
'http://user3:pass3@proxy3.example.com:8080',
# ... tambahkan 50+ proxy untuk rotasi yang efektif
]
# Menghubungkan middleware
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.RandomProxyMiddleware': 350,
'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 400,
}
# Upaya ulang saat terjadi kesalahan
RETRY_TIMES = 3
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429]
Rotasi proxy: tiga metode yang berfungsi
Pemilihan proxy secara acak (seperti pada contoh di atas) adalah metode yang paling sederhana, tetapi bukan yang paling efektif. Mari kita lihat tiga strategi rotasi untuk berbagai skenario.
Metode 1: Round-robin (rotasi berurutan)
Proxy dipilih secara berurutan. Cocok untuk distribusi beban yang merata:
class RoundRobinProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
self.current_index = 0
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def process_request(self, request, spider):
# Mengambil proxy berikutnya secara berurutan
proxy = self.proxy_list[self.current_index]
self.current_index = (self.current_index + 1) % len(self.proxy_list)
request.meta['proxy'] = proxy
Metode 2: Rotasi cerdas dengan blacklist
Melacak proxy yang bermasalah dan sementara mengecualikannya dari rotasi:
import time
from collections import defaultdict
class SmartProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
self.proxy_errors = defaultdict(int)
self.blacklist = set()
self.blacklist_timeout = 300 # 5 menit
self.blacklist_time = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def get_working_proxies(self):
# Menghapus proxy dari blacklist yang telah melewati timeout
current_time = time.time()
expired = [
proxy for proxy, ban_time in self.blacklist_time.items()
if current_time - ban_time > self.blacklist_timeout
]
for proxy in expired:
self.blacklist.discard(proxy)
self.proxy_errors[proxy] = 0
# Mengembalikan proxy yang berfungsi
return [p for p in self.proxy_list if p not in self.blacklist]
def process_request(self, request, spider):
working_proxies = self.get_working_proxies()
if not working_proxies:
spider.logger.error('Semua proxy telah diblacklist!')
return
proxy = random.choice(working_proxies)
request.meta['proxy'] = proxy
def process_response(self, request, response, spider):
# Jika mendapatkan pemblokiran — tambahkan ke blacklist
if response.status in [403, 429, 503]:
proxy = request.meta.get('proxy')
self.proxy_errors[proxy] += 1
if self.proxy_errors[proxy] >= 3:
self.blacklist.add(proxy)
self.blacklist_time[proxy] = time.time()
spider.logger.warning(
f'Proxy {proxy} diblacklist selama {self.blacklist_timeout}s'
)
return response
Metode 3: Rotasi melalui API penyedia
Banyak penyedia proxy (termasuk proxy residensial) menyediakan endpoint rotasi — satu URL yang secara otomatis mengganti IP pada setiap permintaan:
# settings.py
# Endpoint tunggal dengan rotasi otomatis
ROTATING_PROXY = 'http://username:password@rotating.proxy.com:8080'
# Middleware sederhana
class RotatingProxyMiddleware:
def __init__(self, proxy):
self.proxy = proxy
@classmethod
def from_crawler(cls, crawler):
proxy = crawler.settings.get('ROTATING_PROXY')
return cls(proxy)
def process_request(self, request, spider):
# Satu URL, tetapi setiap permintaan menggunakan IP baru
request.meta['proxy'] = self.proxy
Ini adalah metode yang paling nyaman untuk produksi: tidak perlu mengelola kumpulan proxy, penyedia sendiri yang memantau kualitas IP dan mengganti yang bermasalah. Sangat efektif bekerja dengan proxy residensial, di mana kumpulan IP dapat mencapai jutaan alamat.
Autentikasi: login/password vs IP whitelist
Penyedia proxy menawarkan dua metode autentikasi. Pilihan ini mempengaruhi kecepatan koneksi dan kenyamanan pengaturan.
Autentikasi User:Pass
Login dan password dikirim dalam URL proxy. Scrapy secara otomatis mengonversinya menjadi header HTTP Proxy-Authorization:
proxy = 'http://username:password@proxy.example.com:8080'
request.meta['proxy'] = proxy
# Scrapy secara otomatis akan menambahkan header:
# Proxy-Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Kelebihan: berfungsi dari IP mana pun, mudah untuk mengganti proxy dalam kode.
Kekurangan: sedikit overhead pada setiap permintaan (~50-100ms), kredensial dalam bentuk terbuka di kode.
Autentikasi IP Whitelist
Anda menambahkan IP server Anda ke whitelist di penyedia, autentikasi tidak diperlukan:
proxy = 'http://proxy.example.com:8080' # tanpa login/password
request.meta['proxy'] = proxy
Kelebihan: lebih cepat 50-100ms, lebih aman (tidak ada kredensial dalam kode).
Kekurangan: hanya berfungsi dari IP tertentu, perlu memperbarui whitelist saat mengganti server.
Rekomendasi untuk produksi:
Gunakan IP whitelist untuk pengambilan data dari server khusus (AWS, Google Cloud, Hetzner). Untuk pengembangan dan pengujian dari mesin lokal — gunakan autentikasi user:pass.
Penanganan kesalahan dan pergantian IP otomatis
Bahkan dengan proxy berkualitas, akan ada kesalahan: timeout, penolakan koneksi, pemblokiran. Penanganan kesalahan yang tepat sangat penting untuk operasi pengambil data yang stabil.
Penanganan status HTTP
class ProxyMiddleware:
def process_response(self, request, response, spider):
# Kode di mana perlu mengganti proxy dan mencoba lagi
ban_codes = [403, 407, 429, 503]
if response.status in ban_codes:
proxy = request.meta.get('proxy')
spider.logger.warning(
f'Mendapatkan {response.status} dari {proxy}, mencoba lagi...'
)
# Tandai untuk retry dengan proxy baru
request.meta['dont_retry'] = False
request.meta['proxy'] = self.get_new_proxy()
return request
return response
Penanganan pengecualian jaringan
from twisted.internet.error import TimeoutError, ConnectionRefusedError
from scrapy.exceptions import IgnoreRequest
class ProxyMiddleware:
def process_exception(self, request, exception, spider):
# Kesalahan koneksi ke proxy
proxy_errors = (
TimeoutError,
ConnectionRefusedError,
ConnectionLost,
)
if isinstance(exception, proxy_errors):
proxy = request.meta.get('proxy')
spider.logger.error(
f'Proxy {proxy} gagal terhubung: {exception}'
)
# Ganti proxy dan coba lagi
request.meta['proxy'] = self.get_new_proxy()
return request
# Untuk kesalahan lainnya, gunakan penanganan standar
return None
Deteksi pemblokiran berdasarkan konten
Beberapa situs mengembalikan HTTP 200, tetapi menunjukkan captcha atau halaman pemblokiran:
class ProxyMiddleware:
def process_response(self, request, response, spider):
# Tanda-tanda pemblokiran dalam konten
ban_indicators = [
'captcha',
'akses ditolak',
'diblokir',
'lalu lintas yang tidak biasa',
'pemeriksaan robot',
]
body_text = response.text.lower()
if any(indicator in body_text for indicator in ban_indicators):
spider.logger.warning(
f'Halaman pemblokiran terdeteksi dari {request.meta.get("proxy")}'
)
# Ganti proxy dan ulangi
request.meta['proxy'] = self.get_new_proxy()
return request
return response
Jenis proxy mana yang harus dipilih untuk Scrapy
Pemilihan jenis proxy tergantung pada situs target, anggaran, dan kecepatan pengambilan data yang diperlukan. Berikut adalah perbandingan opsi utama:
| Jenis proxy | Kecepatan | Biaya | Kapan digunakan |
|---|---|---|---|
| Proxy pusat data | Tinggi (50-200ms) | Rendah ($1-3/IP) | Situs sederhana tanpa perlindungan, API, alat internal |
| Proxy residensial | Sedang (300-800ms) | Sedang ($5-15/GB) | E-commerce, media sosial, situs dengan Cloudflare, geotargeting |
| Proxy seluler | Rendah (500-1500ms) | Tinggi ($50-150/IP) | Aplikasi seluler, Instagram, TikTok, perlindungan maksimal |
Rekomendasi pemilihan
Untuk pengambilan data dari marketplace (Amazon, Wildberries, Ozon, AliExpress) — hanya proxy residensial. Situs-situs ini agresif memblokir pusat data. Diperlukan rotasi dan geotargeting (misalnya, IP Rusia untuk Wildberries).
Untuk pengambilan data dari situs berita, blog, forum — proxy pusat data cocok. Perlindungan minimal, kecepatan dan biaya lalu lintas yang rendah sangat penting.
Untuk pengambilan data dari situs dengan Cloudflare — proxy residensial wajib. Pusat data Cloudflare terdeteksi hampir seketika. Tambahkan pustaka cloudscraper ke Scrapy untuk menghindari tantangan JS.
Untuk pengambilan data dari Google Search, alat SEO — proxy residensial dengan geotargeting. Google menunjukkan hasil yang berbeda untuk negara dan kota yang berbeda.
Tip: Mulailah dengan kumpulan 10 proxy residensial untuk pengujian. Jika Anda mendapatkan pemblokiran — tingkatkan kumpulan hingga 50-100 IP. Untuk pengambilan data berkecepatan tinggi (1000+ permintaan/menit) gunakan endpoint rotasi dengan kumpulan 10,000+ IP.
Teknik lanjutan: sesi dan sticky IP
Saat mengambil data dari beberapa situs, perlu untuk mempertahankan satu IP selama seluruh sesi (autentikasi, keranjang belanja, formulir multi-langkah). Berikut adalah cara mengimplementasikan sesi sticky di Scrapy.
Sticky IP untuk satu domain
from urllib.parse import urlparse
class StickyProxyMiddleware:
def __init__(self, proxy_list):
self.proxy_list = proxy_list
# Kamus: domain -> proxy
self.domain_proxy_map = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
return cls(proxy_list)
def process_request(self, request, spider):
# Mengambil domain dari URL
domain = urlparse(request.url).netloc
# Jika untuk domain ini sudah ada proxy — gunakan itu
if domain in self.domain_proxy_map:
proxy = self.domain_proxy_map[domain]
else:
# Jika tidak, pilih yang baru dan ingat
proxy = random.choice(self.proxy_list)
self.domain_proxy_map[domain] = proxy
spider.logger.info(f'Ditugaskan {proxy} ke {domain}')
request.meta['proxy'] = proxy
Sticky IP dengan timeout sesi
Versi yang lebih canggih: proxy terikat pada domain untuk waktu tertentu (misalnya, 10 menit), kemudian diganti:
import time
from urllib.parse import urlparse
class SessionProxyMiddleware:
def __init__(self, proxy_list, session_timeout=600):
self.proxy_list = proxy_list
self.session_timeout = session_timeout # 10 menit
# Kamus: domain -> (proxy, waktu pembuatan)
self.sessions = {}
@classmethod
def from_crawler(cls, crawler):
proxy_list = crawler.settings.getlist('PROXY_LIST')
timeout = crawler.settings.getint('PROXY_SESSION_TIMEOUT', 600)
return cls(proxy_list, timeout)
def get_proxy_for_domain(self, domain):
current_time = time.time()
# Memeriksa apakah ada sesi aktif
if domain in self.sessions:
proxy, created_at = self.sessions[domain]
# Jika sesi belum kedaluwarsa — gunakan proxy yang sama
if current_time - created_at < self.session_timeout:
return proxy
# Membuat sesi baru dengan proxy baru
new_proxy = random.choice(self.proxy_list)
self.sessions[domain] = (new_proxy, current_time)
return new_proxy
def process_request(self, request, spider):
domain = urlparse(request.url).netloc
proxy = self.get_proxy_for_domain(domain)
request.meta['proxy'] = proxy
Integrasi dengan Cookie Middleware
Untuk sesi yang lengkap, perlu untuk menyinkronkan proxy dan cookies. Scrapy menyimpan cookies secara terpisah untuk setiap domain, tetapi saat mengganti proxy, perlu untuk menghapus cookies:
# settings.py
# Mengaktifkan cookie middleware
COOKIES_ENABLED = True
COOKIES_DEBUG = False
# Middleware untuk menyinkronkan proxy dan cookies
class ProxyCookieMiddleware:
def process_request(self, request, spider):
# Mendapatkan proxy saat ini
current_proxy = request.meta.get('proxy')
# Jika proxy telah berubah — hapus cookies
previous_proxy = request.meta.get('previous_proxy')
if previous_proxy and previous_proxy != current_proxy:
# Hapus cookies untuk domain ini
jar = spider.crawler.engine.downloader.middleware.middlewares[0].jars
domain = urlparse(request.url).netloc
if domain in jar:
jar[domain].clear()
spider.logger.info(f'Menghapus cookies untuk {domain}')
request.meta['previous_proxy'] = current_proxy
Kesimpulan
Pengaturan proxy yang tepat di Scrapy adalah dasar untuk pengambilan data yang stabil tanpa pemblokiran. Kami telah membahas semua aspek kunci: dari integrasi dasar hingga teknik lanjutan untuk rotasi dan pengelolaan sesi.
Kesimpulan utama:
- Untuk produksi, gunakan middleware kustom dengan rotasi cerdas dan blacklist untuk IP yang bermasalah
- Tangani semua jenis kesalahan: status HTTP, pengecualian jaringan, pemblokiran berdasarkan konten
- Pilih jenis proxy sesuai tugas: pusat data untuk situs sederhana, residensial untuk yang dilindungi
- Untuk situs dengan autentikasi, gunakan sesi sticky dengan mengikat proxy ke domain
- Mulailah dengan kumpulan 10-50 proxy, tingkatkan saat beban meningkat
Jika Anda berencana untuk mengambil data dari situs yang dilindungi (marketplace, media sosial, situs dengan Cloudflare), saya sarankan untuk menggunakan proxy residensial — mereka memberikan anonimitas maksimum dan risiko pemblokiran yang minimal. Untuk pengambilan data berkecepatan tinggi, pilih penyedia dengan endpoint rotasi dan kumpulan dari 10,000 alamat IP.
Semua contoh kode dari artikel ini telah diuji pada Scrapy 2.x dan siap digunakan dalam produksi. Sesuaikan dengan kebutuhan Anda dan tingkatkan seiring pertumbuhan proyek.