← Torna al blog

Esclusione dei proxy scadenti nel pool del parser: metriche di errore e rotazione in Python

Analizziamo quali metriche di errore segnalano la degradazione del proxy nel pool del parser e come costruire un sistema automatico di scarto e rotazione degli IP.

📅19 ottobre 2026

Se il parser di Wildberries o Ozon ha improvvisamente iniziato a restituire pagine vuote, timeout e errori 403, il problema non è sempre nel sito stesso. Nella maggior parte dei casi, parte degli indirizzi IP nel pool di proxy è già "scaduta": sono stati bloccati, rallentati o messi sotto CAPTCHA. Analizziamo quali metriche distinguono un proxy funzionante da uno morto e come automatizzare il filtraggio senza controllo manuale.

Perché il pool di proxy degrada nel tempo

Qualsiasi pool di proxy, anche acquistato da un fornitore affidabile, perde nel tempo parte degli indirizzi funzionanti. Le ragioni sono diverse: i sistemi antifrode di Wildberries, Ozon e Avito accumulano etichette reputazionali sugli IP, parte degli indirizzi finisce nelle liste nere dopo richieste massicce da altri clienti del fornitore (se il proxy è condiviso), gli intervalli di data center vengono bloccati a pacchetti per sottoreti, e gli IP residenziali a volte semplicemente escono dal pool perché l'utente reale ha disattivato il dispositivo.

Se non si pulisce il pool, la quota di proxy "morti" cresce in modo non lineare: inizialmente il 5%, dopo una settimana di parsing attivo — il 20-30%. Il parser inizia a spendere risorse per tentativi ripetuti tramite IP non funzionanti, aumenta il tempo medio di raccolta di una pagina, e nei log si accumulano errori che mascherano problemi reali (ad esempio, modifiche nel layout del sito). Senza un sistema di filtraggio, si tiene un pool gonfiato "per precauzione" o si ricevono regolarmente fallimenti nella raccolta dei dati.

L'obiettivo è non solo registrare il fatto dell'errore, ma classificarlo: sovraccarico temporaneo del server, ban di un IP specifico, blocco regionale o problema lato parser (intestazioni errate, cookie obsoleti). A seconda del tipo di errore, si decide se eliminare il proxy dal pool per sempre, metterlo in "quarantena" per un po' o semplicemente ripetere la richiesta.

Metriche chiave di errore per il filtraggio

Per distinguere un guasto temporaneo da un proxy realmente morto, è necessario raccogliere diverse metriche per ogni IP, invece di basarsi su un singolo stato HTTP. Ecco un insieme di base che vale la pena registrare ad ogni richiesta:

Metrica Cosa segnala Azione
HTTP 403 / 429 Ban o rate-limit sull'IP Quarantena 30-60 minuti, in caso di ripetizione — eliminazione
Timeout di connessione Proxy sovraccarico o morto 3 timeout consecutivi — eliminazione dal pool
CAPTCHA nella risposta IP sospettato dall'antibot Ridurre la frequenza delle richieste o sostituire l'IP
Tempo di risposta > 5 sec Degradazione della velocità lato proxy Ridurre la priorità nel pool
Dimensione della risposta sotto la media Restituita una pagina di blocco Controllo aggiuntivo del contenuto prima dell'accettazione
Quota di richieste riuscite (success rate) Valutazione integrale della qualità dell'IP Sotto il 70% per 50 richieste — eliminazione

È importante non confondere i codici di errore del sito con gli errori di rete. Ad esempio, 404 non è un problema del proxy, ma un prodotto assente su Wildberries. Ma se lo stesso IP riceve regolarmente 403 dove altri IP ricevono 200, questo è un chiaro segnale di un ban mirato di quell'indirizzo.

Algoritmo di punteggio dei proxy

Invece di un binario "funzionante / non funzionante", è più comodo calcolare per ogni IP un punteggio da 0 a 100, che diminuisce gradualmente in caso di errori e si ripristina in caso di richieste riuscite. Questo consente di non eliminare il proxy dopo un singolo guasto casuale (il server del marketplace potrebbe semplicemente essersi bloccato per un secondo), ma di rimuovere rapidamente quegli IP che restituiscono sistematicamente dati spazzatura.

La logica è semplice: una richiesta riuscita con codice 200 e contenuto valido aggiunge +2 al punteggio (ma non oltre 100). Timeout — meno 15. Codice 403/429 — meno 25. CAPTCHA nella risposta — meno 20. Se il punteggio scende sotto una soglia (ad esempio, 30), il proxy va in quarantena per un tempo fisso, e se dopo il ritorno dalla quarantena il punteggio scende di nuovo sotto 10 — l'IP viene eliminato dal pool definitivamente e registrato per l'analisi.

Questo approccio è simile al sistema di reputazione nei browser anti-detect come Dolphin Anty o AdsPower, dove gli account hanno "salute" in base all'attività. La stessa meccanica funziona bene anche per gli indirizzi IP nel parsing.

Codice: ProxyPool con filtraggio automatico in Python

Di seguito è riportato un framework funzionante per la classe di gestione del pool di proxy tenendo conto delle metriche di errore. Può essere collegato a qualsiasi parser su requests, httpx o Scrapy, sostituendo la logica di ottenimento dei proxy con la chiamata pool.get_proxy().

import time
import random
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class ProxyStats:
    address: str
    score: int = 100
    quarantine_until: float = 0.0
    total_requests: int = 0
    total_success: int = 0
    consecutive_timeouts: int = 0
    is_dead: bool = False

    @property
    def success_rate(self) -> float:
        if self.total_requests == 0:
            return 1.0
        return self.total_success / self.total_requests


class ProxyPool:
    def __init__(self, proxies: list[str],
                 min_score: int = 30,
                 dead_score: int = 10,
                 quarantine_seconds: int = 1800):
        self.stats = {p: ProxyStats(address=p) for p in proxies}
        self.min_score = min_score
        self.dead_score = dead_score
        self.quarantine_seconds = quarantine_seconds

    def _is_available(self, stats: ProxyStats) -> bool:
        if stats.is_dead:
            return False
        if stats.quarantine_until > time.time():
            return False
        return True

    def get_proxy(self) -> Optional[str]:
        available = [s for s in self.stats.values() if self._is_available(s)]
        if not available:
            return None
        # ordiniamo per punteggio, prendiamo casualmente dal top-30% per non colpire un solo IP
        available.sort(key=lambda s: s.score, reverse=True)
        top_pool = available[:max(1, len(available) // 3)]
        return random.choice(top_pool).address

    def report_success(self, proxy: str):
        s = self.stats[proxy]
        s.total_requests += 1
        s.total_success += 1
        s.consecutive_timeouts = 0
        s.score = min(100, s.score + 2)

    def report_timeout(self, proxy: str):
        s = self.stats[proxy]
        s.total_requests += 1
        s.consecutive_timeouts += 1
        s.score -= 15
        if s.consecutive_timeouts >= 3:
            self._quarantine(s)
        self._check_dead(s)

    def report_blocked(self, proxy: str, captcha: bool = False):
        s = self.stats[proxy]
        s.total_requests += 1
        s.score -= 20 if captcha else 25
        self._quarantine(s)
        self._check_dead(s)

    def _quarantine(self, s: ProxyStats):
        if s.score < self.min_score:
            s.quarantine_until = time.time() + self.quarantine_seconds

    def _check_dead(self, s: ProxyStats):
        if s.score <= self.dead_score:
            s.is_dead = True

    def stats_report(self) -> list[dict]:
        return [
            {
                "proxy": s.address,
                "score": s.score,
                "success_rate": round(s.success_rate, 2),
                "dead": s.is_dead,
            }
            for s in self.stats.values()
        ]

In un parser reale, le chiamate report_success, report_timeout e report_blocked vengono inserite direttamente nel gestore della risposta dopo ogni richiesta HTTP. Ecco un esempio di integrazione con requests:

import requests

def fetch(url: str, pool: ProxyPool):
    proxy = pool.get_proxy()
    if not proxy:
        raise RuntimeError("Nessun proxy disponibile nel pool")

    proxies = {"http": proxy, "https": proxy}
    try:
        resp = requests.get(url, proxies=proxies, timeout=8)
    except requests.exceptions.Timeout:
        pool.report_timeout(proxy)
        return None

    if resp.status_code in (403, 429):
        pool.report_blocked(proxy)
        return None

    if "captcha" in resp.text.lower():
        pool.report_blocked(proxy, captcha=True)
        return None

    pool.report_success(proxy)
    return resp

Questo approccio allevia il carico dal monitoraggio manuale: si impostano una volta le soglie e poi il pool "guarisce" da solo, espellendo gli indirizzi morti e riportando in attività quelli che erano temporaneamente inattivi.

Valori soglia per il ban degli IP

Non esistono numeri universali: le soglie dipendono dal sito e dall'aggressività del suo antibot. Ma ci sono punti di riferimento che funzionano bene nella pratica per il parsing di Wildberries, Ozon e Avito:

  • Success rate sotto il 70% su un campione di 30-50 richieste — motivo per eliminare l'IP, anche se il punteggio attuale non è ancora critico.
  • 3 timeout consecutivi — quasi sempre significa che il proxy è fisicamente non disponibile o sovraccarico, non ha senso tenerlo nel pool attivo.
  • CAPTCHA più di 1 volta ogni 20 richieste — l'IP è già sotto sospetto da parte dell'antibot, è meglio ridurre la frequenza delle richieste o sostituire l'indirizzo.
  • Tempo medio di risposta superiore a 2x dalla mediana del pool — segnale di degradazione, non critico, ma motivo per ridurre la priorità nella scelta.

La quarantena di 30-60 minuti è solitamente sufficiente per i proxy di data center e residenziali affinché l'antibot "dimentichi" l'attività sospetta temporanea. Per i proxy mobili la situazione è diversa: lì l'IP cambia quando l'operatore cambia cella, quindi la quarantena può essere più breve — 10-15 minuti, poiché dopo la rotazione è fisicamente già un altro indirizzo.

Healthcheck e rotazione in background

Oltre al filtraggio reattivo (in base agli errori nel flusso principale di parsing), è utile eseguire un healthcheck in background — un processo separato che ogni pochi minuti verifica i proxy con richieste leggere su un URL neutro (ad esempio, httpbin.org/ip) e aggiorna le statistiche del pool prima che il proxy venga utilizzato in una richiesta reale a Wildberries o Ozon.

import threading

def healthcheck_loop(pool: ProxyPool, interval: int = 300):
    while True:
        for proxy in list(pool.stats.keys()):
            try:
                r = requests.get(
                    "https://httpbin.org/ip",
                    proxies={"http": proxy, "https": proxy},
                    timeout=5
                )
                if r.status_code == 200:
                    pool.report_success(proxy)
                else:
                    pool.report_blocked(proxy)
            except requests.exceptions.RequestException:
                pool.report_timeout(proxy)
        time.sleep(interval)

threading.Thread(target=healthcheck_loop, args=(pool,), daemon=True).start()

Questo processo in background è particolarmente importante se il pool è grande (centinaia di IP) e il parsing non avviene in modo continuo, ma a lotti: senza healthcheck, parte dei proxy semplicemente riesce a "scadere" tra i lanci, e i primi minuti di parsing vengono spesi per il filtraggio dal vivo, invece di lavorare subito con indirizzi verificati.

Quale tipo di proxy scegliere per il parsing dei marketplace

La scelta del tipo di proxy influisce direttamente su quanto spesso sarà necessario eseguire il filtraggio e quanto aggressivi saranno i limiti. Per un parsing stabile di Wildberries e Ozon, dove l'antibot analizza i modelli comportamentali e la reputazione della sottorete, i proxy residenziali si comportano meglio — i loro IP appartengono a veri fornitori di internet domestico, e l'antibot li blocca significativamente meno frequentemente a pacchetti per sottorete.

Per fonti più semplici e meno protette (ad esempio, raccolta di dati pubblici da Avito o monitoraggio dei prezzi su vetrine non complesse) spesso sono sufficienti i proxy di data center — sono più veloci e più economici, il che è critico per grandi volumi di richieste, ma vengono anche bloccati un po' più frequentemente, quindi i limiti di filtraggio per loro dovrebbero essere più severi.

Tipo di proxy Velocità di filtraggio Quando utilizzare
Data center Alta, le sottoreti vengono bloccate a pacchetti Vetrine semplici, grandi volumi, bassa protezione
Residenziali Media, gli IP vengono bloccati in modo mirato Wildberries, Ozon, siti con antibot
Mobili Bassa, l'IP cambia automaticamente dall'operatore Fonti altamente protette, parsing ad alta frequenza

Nella pratica, molti team utilizzano un pool ibrido: la maggior parte delle richieste passa attraverso IP residenziali, mentre in caso di ban massicci si collegano proxy mobili come riserva "di emergenza", poiché il loro cambio di IP avviene in modo naturale tramite la rotazione della rete cellulare e non richiede intervento manuale.

Conclusione

Un pool di proxy senza un sistema di filtraggio degrada inevitabilmente, e più a lungo funziona senza pulizia, più risorse il parser spende inutilmente su IP morti. Un modello di punteggio che tiene conto dei codici HTTP, dei timeout, dei CAPTCHA e del success rate consente di identificare automaticamente gli indirizzi problematici e mantenere il pool in uno stato funzionante senza controllo manuale. Il codice di questo articolo può essere adattato a qualsiasi stack — requests, httpx, Scrapy o un proprio parser asincrono.

Se stai solo raccogliendo un pool per il parsing dei marketplace e vuoi ridurre la frequenza dei ban fin dall'inizio, ti consigliamo di considerare i proxy residenziali — offrono un success rate significativamente più elevato su piattaforme protette come Wildberries e Ozon rispetto agli indirizzi di data center comuni.