Cara Memperbaiki Kesalahan Timeout Saat Bekerja Melalui Proxy
Permintaan macet, skrip jatuh dengan kesalahan TimeoutError, data tidak diterima. Situasi yang familiar? Kesalahan timeout melalui proxy — salah satu masalah paling umum saat parsing dan otomasi. Mari kita analisis penyebabnya dan berikan solusi konkret.
Mengapa Kesalahan Timeout Terjadi
Timeout — bukan satu masalah, tetapi gejala. Sebelum mengobati, kita perlu memahami penyebabnya:
Server proxy yang lambat. Server yang kelebihan beban atau proxy yang jauh secara geografis menambah penundaan pada setiap permintaan. Jika timeout Anda 10 detik, tetapi proxy merespons dalam 12 detik — terjadi kesalahan.
Pemblokiran di sisi situs target. Situs dapat dengan sengaja "menggantung" permintaan yang mencurigakan alih-alih menolak secara eksplisit. Ini adalah taktik melawan bot — membiarkan koneksi terbuka tanpa batas.
Masalah DNS. Proxy harus menyelesaikan domain. Jika server DNS proxy lambat atau tidak dapat diakses — permintaan macet pada tahap koneksi.
Konfigurasi timeout yang salah. Satu timeout umum untuk semuanya — kesalahan umum. Connect timeout dan read timeout — hal yang berbeda, dan harus dikonfigurasi secara terpisah.
Masalah jaringan. Kehilangan paket, koneksi proxy yang tidak stabil, masalah perutean — semuanya menyebabkan timeout.
Jenis Timeout dan Konfigurasinya
Sebagian besar perpustakaan HTTP mendukung beberapa jenis timeout. Memahami perbedaan di antara mereka — kunci untuk konfigurasi yang benar.
Connect timeout
Waktu untuk membangun koneksi TCP dengan proxy dan server target. Jika proxy tidak dapat diakses atau server tidak merespons — timeout ini akan terpicu. Nilai yang direkomendasikan: 5-10 detik.
Read timeout
Waktu menunggu data setelah koneksi dibangun. Server terhubung, tetapi diam — read timeout akan terpicu. Untuk halaman biasa: 15-30 detik. Untuk API berat: 60+ detik.
Total timeout
Waktu total untuk seluruh permintaan dari awal hingga akhir. Perlindungan dari koneksi yang macet. Biasanya: connect + read + cadangan.
Contoh konfigurasi di Python dengan perpustakaan requests:
import requests
proxies = {
"http": "http://user:pass@proxy.example.com:8080",
"https": "http://user:pass@proxy.example.com:8080"
}
# Tuple: (connect_timeout, read_timeout)
timeout = (10, 30)
try:
response = requests.get(
"https://target-site.com/api/data",
proxies=proxies,
timeout=timeout
)
except requests.exceptions.ConnectTimeout:
print("Gagal terhubung ke proxy atau server")
except requests.exceptions.ReadTimeout:
print("Server tidak mengirim data tepat waktu")
Untuk aiohttp (Python asinkron):
import aiohttp
import asyncio
async def fetch_with_timeout():
timeout = aiohttp.ClientTimeout(
total=60, # Total timeout
connect=10, # Untuk koneksi
sock_read=30 # Untuk membaca data
)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(
"https://target-site.com/api/data",
proxy="http://user:pass@proxy.example.com:8080"
) as response:
return await response.text()
Logika Retry: Pendekatan yang Benar
Timeout — tidak selalu kesalahan fatal. Sering kali permintaan berulang berhasil. Tetapi retry harus dilakukan dengan bijak.
Penundaan Eksponensial
Jangan terus-menerus mengirim permintaan berulang ke server tanpa jeda. Gunakan exponential backoff: setiap percobaan berikutnya — dengan penundaan yang meningkat.
import requests
import time
import random
def fetch_with_retry(url, proxies, max_retries=3):
"""Permintaan dengan retry dan penundaan eksponensial"""
for attempt in range(max_retries):
try:
response = requests.get(
url,
proxies=proxies,
timeout=(10, 30)
)
response.raise_for_status()
return response
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError) as e:
if attempt == max_retries - 1:
raise # Percobaan terakhir — lempar kesalahan
# Penundaan eksponensial: 1s, 2s, 4s...
# + jitter acak untuk menghindari gelombang permintaan
delay = (2 ** attempt) + random.uniform(0, 1)
print(f"Percobaan {attempt + 1} gagal: {e}")
print(f"Coba lagi dalam {delay:.1f} detik...")
time.sleep(delay)
Perpustakaan tenacity
Untuk kode production, lebih nyaman menggunakan solusi siap pakai:
from tenacity import retry, stop_after_attempt, wait_exponential
from tenacity import retry_if_exception_type
import requests
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=1, max=10),
retry=retry_if_exception_type((
requests.exceptions.Timeout,
requests.exceptions.ConnectionError
))
)
def fetch_data(url, proxies):
response = requests.get(url, proxies=proxies, timeout=(10, 30))
response.raise_for_status()
return response.json()
Rotasi Proxy Saat Timeout
Jika satu proxy terus memberikan timeout — masalahnya ada pada proxy tersebut. Solusi logis: beralih ke yang lain.
import requests
from collections import deque
from dataclasses import dataclass, field
from typing import Optional
import time
@dataclass
class ProxyManager:
"""Manajer proxy dengan pelacakan upaya yang gagal"""
proxies: list
max_failures: int = 3
cooldown_seconds: int = 300
_failures: dict = field(default_factory=dict)
_cooldown_until: dict = field(default_factory=dict)
def get_proxy(self) -> Optional[str]:
"""Dapatkan proxy yang berfungsi"""
current_time = time.time()
for proxy in self.proxies:
# Lewati proxy dalam cooldown
if self._cooldown_until.get(proxy, 0) > current_time:
continue
return proxy
return None # Semua proxy dalam cooldown
def report_failure(self, proxy: str):
"""Laporkan permintaan yang gagal"""
self._failures[proxy] = self._failures.get(proxy, 0) + 1
if self._failures[proxy] >= self.max_failures:
# Masukkan proxy ke cooldown
self._cooldown_until[proxy] = time.time() + self.cooldown_seconds
self._failures[proxy] = 0
print(f"Proxy {proxy} dimasukkan ke cooldown")
def report_success(self, proxy: str):
"""Reset penghitung kesalahan saat berhasil"""
self._failures[proxy] = 0
def fetch_with_rotation(url, proxy_manager, max_attempts=5):
"""Permintaan dengan pergantian proxy otomatis saat kesalahan"""
for attempt in range(max_attempts):
proxy = proxy_manager.get_proxy()
if not proxy:
raise Exception("Tidak ada proxy yang tersedia")
proxies = {"http": proxy, "https": proxy}
try:
response = requests.get(url, proxies=proxies, timeout=(10, 30))
response.raise_for_status()
proxy_manager.report_success(proxy)
return response
except (requests.exceptions.Timeout,
requests.exceptions.ConnectionError):
proxy_manager.report_failure(proxy)
print(f"Timeout melalui {proxy}, mencoba yang lain...")
continue
raise Exception(f"Gagal mendapatkan data setelah {max_attempts} percobaan")
Saat menggunakan proxy residensial dengan rotasi otomatis, logika ini disederhanakan — penyedia secara otomatis mengganti IP pada setiap permintaan atau sesuai interval yang ditentukan.
Permintaan Asinkron dengan Kontrol Timeout
Saat parsing massal, permintaan sinkron tidak efisien. Pendekatan asinkron memungkinkan memproses ratusan URL secara paralel, tetapi memerlukan penanganan timeout yang hati-hati.
import aiohttp
import asyncio
from typing import List, Tuple
async def fetch_one(
session: aiohttp.ClientSession,
url: str,
semaphore: asyncio.Semaphore
) -> Tuple[str, str | None, str | None]:
"""Memuat satu URL dengan penanganan timeout"""
async with semaphore: # Batasi paralelitas
try:
async with session.get(url) as response:
content = await response.text()
return (url, content, None)
except asyncio.TimeoutError:
return (url, None, "timeout")
except aiohttp.ClientError as e:
return (url, None, str(e))
async def fetch_all(
urls: List[str],
proxy: str,
max_concurrent: int = 10
) -> List[Tuple[str, str | None, str | None]]:
"""Memuat massal dengan kontrol timeout dan paralelitas"""
timeout = aiohttp.ClientTimeout(total=45, connect=10, sock_read=30)
semaphore = asyncio.Semaphore(max_concurrent)
connector = aiohttp.TCPConnector(
limit=max_concurrent,
limit_per_host=5 # Tidak lebih dari 5 koneksi per host
)
async with aiohttp.ClientSession(
timeout=timeout,
connector=connector
) as session:
# Atur proxy untuk semua permintaan
tasks = [
fetch_one(session, url, semaphore)
for url in urls
]
results = await asyncio.gather(*tasks)
# Statistik
success = sum(1 for _, content, _ in results if content)
timeouts = sum(1 for _, _, error in results if error == "timeout")
print(f"Berhasil: {success}, Timeout: {timeouts}")
return results
# Penggunaan
async def main():
urls = [f"https://example.com/page/{i}" for i in range(100)]
results = await fetch_all(
urls,
proxy="http://user:pass@proxy.example.com:8080",
max_concurrent=10
)
asyncio.run(main())
Penting: Jangan atur paralelitas terlalu tinggi. 50-100 permintaan simultan melalui satu proxy — sudah banyak. Lebih baik 10-20 dengan beberapa proxy.
Diagnostik: Cara Menemukan Penyebabnya
Sebelum mengubah pengaturan, tentukan sumber masalahnya.
Langkah 1: Periksa proxy secara langsung
# Tes sederhana melalui curl dengan pengukuran waktu
curl -x http://user:pass@proxy:8080 \
-w "Connect: %{time_connect}s\nTotal: %{time_total}s\n" \
-o /dev/null -s \
https://httpbin.org/get
Jika time_connect lebih dari 5 detik — masalah dengan proxy atau jaringan ke sana.
Langkah 2: Bandingkan dengan permintaan langsung
import requests
import time
def measure_request(url, proxies=None):
start = time.time()
try:
r = requests.get(url, proxies=proxies, timeout=30)
elapsed = time.time() - start
return f"OK: {elapsed:.2f}s, status: {r.status_code}"
except Exception as e:
elapsed = time.time() - start
return f"FAIL: {elapsed:.2f}s, error: {type(e).__name__}"
url = "https://target-site.com"
proxy = {"http": "http://proxy:8080", "https": "http://proxy:8080"}
print("Langsung:", measure_request(url))
print("Melalui proxy:", measure_request(url, proxy))
Langkah 3: Periksa jenis proxy yang berbeda
Timeout dapat bergantung pada jenis proxy:
| Jenis Proxy | Penundaan Khas | Timeout yang Direkomendasikan |
|---|---|---|
| Datacenter | 50-200 ms | Connect: 5s, Read: 15s |
| Residential | 200-800 ms | Connect: 10s, Read: 30s |
| Mobile | 300-1500 ms | Connect: 15s, Read: 45s |
Langkah 4: Catat detail
import logging
import requests
from requests.adapters import HTTPAdapter
# Aktifkan debug logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("urllib3").setLevel(logging.DEBUG)
# Sekarang Anda akan melihat semua tahap permintaan:
# - Resolusi DNS
# - Pembentukan koneksi
# - Pengiriman permintaan
# - Penerimaan respons
Daftar Periksa Solusi Kesalahan Timeout
Algoritma singkat tindakan saat timeout terjadi:
- Tentukan jenis timeout — connect atau read? Ini adalah masalah yang berbeda.
- Periksa proxy secara terpisah — apakah itu bekerja sama sekali? Berapa penundaannya?
- Tingkatkan timeout — mungkin nilainya terlalu agresif untuk jenis proxy Anda.
- Tambahkan retry dengan backoff — timeout tunggal normal, yang penting adalah ketahanan.
- Atur rotasi — secara otomatis beralih ke proxy lain saat masalah.
- Batasi paralelitas — terlalu banyak permintaan simultan membebani proxy.
- Periksa situs target — mungkin itu memblokir atau throttle permintaan Anda.
Kesimpulan
Kesalahan timeout melalui proxy — masalah yang dapat diselesaikan. Dalam kebanyakan kasus, cukup mengonfigurasi timeout dengan benar sesuai jenis proxy, menambahkan logika retry, dan menerapkan rotasi saat kegagalan. Untuk tugas dengan persyaratan stabilitas tinggi, gunakan proxy residensial dengan rotasi otomatis — pelajari lebih lanjut di proxycove.com.