Quali sono i limiti di velocità di scorrimento?
I limiti di tasso sono le pareti invisibili che i siti web costruiscono per controllare quanto velocemente qualsiasi singolo cliente può fare richieste. Quando si raschia un sito troppo aggressivo, si colpisce queste pareti - e le conseguenze variano da rallentamenti temporanei ai divieti IP permanenti. Capire come i limiti di velocità funzionano, come si rileva e come rimanere sotto di loro è fondamentale per la costruzione di raschietti che forniscono dati in modo affidabile.
Questa guida spiega la meccanica dietro il limite di velocità, i segnali di rilevamento siti web uso, e strategie pratiche per l'adaptive throttling che mantengono i raschiatori in esecuzione senza intoppi.
Per una visione più ampia di raschiamento con i proxy, vedere il nostro Guida completa ai proxy Web Scraping. Per evitare blocchi in generale, leggere Come Graffiare i Siti web senza Bloccarsi.
Come limitare il tasso di lavoro
I siti web implementano limiti di velocità a più strati, ciascuno con diversa granulosità di rilevamento:
Livello 1: Limiti di velocità basati su IP
L'approccio più comune. Il server traccia richieste per indirizzo IP all'interno di una finestra temporale. Superato la soglia e ricevi risposte HTTP 429 (Too Many Requests) o 503.
# Typical rate limit behavior
Request 1-50: HTTP 200 (normal)
Request 51: HTTP 429 (rate limited)
Wait 60 seconds...
Request 52: HTTP 200 (reset)Livello 2: Limiti basati su sessione/cookie
Tracce richiesta frequenza per sessione o cookie del browser. Anche se si ruotano gli IP, lo stesso token di sessione che colpisce il server veloce si attivano limiti.
Livello 3: Limiti basati su account
Per i siti che richiedono il login, i limiti sono legati all'account utente indipendentemente dall'IP. Comune sulle piattaforme API e SaaS.
Livello 4: Analisi comportamentale
Sistemi avanzati come Cloudflare, PerimeterX e Akamai analizzano i modelli comportamentali: richiedere tempistiche, flusso di navigazione, movimenti del mouse (nei contesti del browser). Questo strato è il più difficile da bypassare perché non si basa su semplici contatori.
Segnali di rilevamento del limite del tasso comune
I siti web utilizzano più segnali contemporaneamente per rilevare la demolizione automatizzata:
| Segnale | Cosa rileva | Difficoltà di Evade |
|---|---|---|
| Richieste per IP al minuto | Velocità | Facile (utilizzare i proxy) |
| Richieste per IP all'ora/giorno | Volume ottenuto | Media (rotato IPs) |
| Richiedi la regolarità dei tempi | Intervalli simili a macchine | Medio (add jitter) |
| Intestazioni mancanti/rong | Clienti non-browser | Facile (set intestazioni appropriate) |
| Modelli di URL sequenziali | Strisciamento sistemico | Medio (ordine randomito) |
| Impronte TLS | Libreria vs browser | Hard (usare browser reali) |
| esecuzione JavaScript | browser intestato | Hard (configura avanzata) |
| Eventi di Mouse/Tastiera | Comportamento Bot | Molto difficile |
Ulteriori informazioni sui meccanismi di rilevamento nella nostra guida Come i sistemi anti-bot rilevano i proxy.
Codici di risposta HTTP che limite di velocità di segnale
Sapere quali codici HTTP indicano il limite di velocità ti aiuta a costruire una corretta logica di riprovazione:
| Codice | Significato | Azione |
|---|---|---|
| 200 (con CAPTCHA) | Blocco morbido — pagina di sfida servita | Ruota IP, rallenta |
| 403 Proibita | IP o sessione bloccata | Ruotare immediatamente l'IP |
| 429 Troppe richieste | Limite di velocità esatto | Attendere e riprovare con backoff |
| 503 Servizio Non disponibile | Sovraccarico del server o blocco | Indietro, controllare se bloccato |
| 302/307 a CAPTCHA URL | Challenge redirect | Ruota IP, riduce la velocità |
Strategia 1: Respective Throttling
L'approccio più semplice — mantenere il tasso di richiesta ben al di sotto di ciò che l'obiettivo consente. Ciò significa meno fallimenti, meno larghezza di banda sprecata e raschiamento più sostenibile.
import requests
import time
import random
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
def respectful_scrape(urls: list[str], rpm_limit: int = 10) -> list[str]:
"""Scrape URLs while respecting a requests-per-minute limit."""
delay = 60.0 / rpm_limit
results = []
for url in urls:
try:
resp = requests.get(
url,
proxies={"http": PROXY, "https": PROXY},
timeout=30
)
results.append(resp.text if resp.status_code == 200 else None)
except requests.RequestException:
results.append(None)
# Add delay with random jitter (±30%) to look less robotic
jitter = delay * random.uniform(0.7, 1.3)
time.sleep(jitter)
return resultsStrategia 2: Adaptive Throttling
Invece di un tasso fisso, regolare dinamicamente la velocità in base alle risposte che ricevi. Accelerare quando tutto funziona, rallentare quando si vedono segnali di avvertimento.
Attuazione di Python
import requests
import time
import random
from dataclasses import dataclass, field
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
@dataclass
class AdaptiveThrottle:
"""Automatically adjusts request rate based on server responses."""
base_delay: float = 2.0 # seconds between requests
min_delay: float = 0.5
max_delay: float = 30.0
current_delay: float = 2.0
success_streak: int = 0
warning_codes: set = field(default_factory=lambda: {429, 403, 503})
def on_success(self):
self.success_streak += 1
# Speed up after 10 consecutive successes
if self.success_streak >= 10:
self.current_delay = max(self.current_delay * 0.85, self.min_delay)
self.success_streak = 0
def on_rate_limit(self):
self.success_streak = 0
# Double the delay on rate limit
self.current_delay = min(self.current_delay * 2.0, self.max_delay)
def on_block(self):
self.success_streak = 0
# Aggressive backoff on block
self.current_delay = min(self.current_delay * 3.0, self.max_delay)
def wait(self):
jitter = self.current_delay * random.uniform(0.7, 1.3)
time.sleep(jitter)
def scrape_adaptive(urls: list[str]) -> list[dict]:
throttle = AdaptiveThrottle()
results = []
for url in urls:
try:
resp = requests.get(
url,
proxies={"http": PROXY, "https": PROXY},
timeout=30
)
if resp.status_code == 200:
throttle.on_success()
results.append({"url": url, "status": 200, "body": resp.text})
elif resp.status_code == 429:
throttle.on_rate_limit()
# Check Retry-After header
retry_after = int(resp.headers.get("Retry-After", 0))
if retry_after:
time.sleep(retry_after)
results.append({"url": url, "status": 429, "body": None})
elif resp.status_code == 403:
throttle.on_block()
results.append({"url": url, "status": 403, "body": None})
else:
results.append({"url": url, "status": resp.status_code, "body": resp.text})
except requests.RequestException as e:
throttle.on_block()
results.append({"url": url, "status": 0, "error": str(e)})
throttle.wait()
print(f"Current delay: {throttle.current_delay:.1f}s")
return resultsNode.js Attuazione
const HttpsProxyAgent = require('https-proxy-agent');
const fetch = require('node-fetch');
class AdaptiveThrottle {
constructor() {
this.currentDelay = 2000; // ms
this.minDelay = 500;
this.maxDelay = 30000;
this.successStreak = 0;
}
onSuccess() {
this.successStreak++;
if (this.successStreak >= 10) {
this.currentDelay = Math.max(this.currentDelay * 0.85, this.minDelay);
this.successStreak = 0;
}
}
onRateLimit() {
this.successStreak = 0;
this.currentDelay = Math.min(this.currentDelay * 2, this.maxDelay);
}
onBlock() {
this.successStreak = 0;
this.currentDelay = Math.min(this.currentDelay * 3, this.maxDelay);
}
async wait() {
const jitter = this.currentDelay * (0.7 + Math.random() * 0.6);
return new Promise(resolve => setTimeout(resolve, jitter));
}
}
async function scrapeAdaptive(urls) {
const throttle = new AdaptiveThrottle();
const agent = new HttpsProxyAgent('http://USERNAME:PASSWORD@gate.proxyhat.com:8080');
const results = [];
for (const url of urls) {
try {
const res = await fetch(url, { agent, timeout: 30000 });
if (res.ok) {
throttle.onSuccess();
results.push({ url, status: res.status, body: await res.text() });
} else if (res.status === 429) {
throttle.onRateLimit();
const retryAfter = parseInt(res.headers.get('retry-after') || '0');
if (retryAfter) await new Promise(r => setTimeout(r, retryAfter * 1000));
results.push({ url, status: 429, body: null });
} else if (res.status === 403) {
throttle.onBlock();
results.push({ url, status: 403, body: null });
}
} catch (err) {
throttle.onBlock();
results.push({ url, status: 0, error: err.message });
}
await throttle.wait();
console.log(`Current delay: ${throttle.currentDelay.toFixed(0)}ms`);
}
return results;
}Strategia 3: Limitamento del tasso distribuito
Durante l'esecuzione di più istanze raschietto in parallelo, coordinare il limite di tasso in tutti i lavoratori. Senza coordinamento, ogni lavoratore rispetta il proprio limite, ma il traffico combinato supera ancora l'obiettivo.
import requests
import time
import threading
class DistributedRateLimiter:
"""Thread-safe rate limiter for multiple scraper workers."""
def __init__(self, max_rpm: int):
self.min_interval = 60.0 / max_rpm
self.lock = threading.Lock()
self.last_request_time = 0.0
def acquire(self):
"""Block until it is safe to make the next request."""
with self.lock:
now = time.time()
elapsed = now - self.last_request_time
if elapsed < self.min_interval:
time.sleep(self.min_interval - elapsed)
self.last_request_time = time.time()
# Shared limiter across all threads
limiter = DistributedRateLimiter(max_rpm=30)
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
def worker(urls: list[str], results: list):
for url in urls:
limiter.acquire()
try:
resp = requests.get(
url,
proxies={"http": PROXY, "https": PROXY},
timeout=30
)
results.append({"url": url, "status": resp.status_code})
except Exception as e:
results.append({"url": url, "error": str(e)})Strategia 4: Richiedi la coda con la priorità
Per progetti di demolizione complessi, utilizzare una coda prioritaria che gestisce limiti di tasso per dominio target:
import requests
import time
import heapq
import threading
from collections import defaultdict
PROXY = "http://USERNAME:PASSWORD@gate.proxyhat.com:8080"
class DomainRateLimiter:
"""Per-domain rate limiting with priority queue."""
def __init__(self, default_rpm: int = 10):
self.default_rpm = default_rpm
self.domain_limits = {} # domain -> max RPM
self.domain_last = defaultdict(float) # domain -> last request time
self.lock = threading.Lock()
def set_limit(self, domain: str, rpm: int):
self.domain_limits[domain] = rpm
def wait_for_domain(self, domain: str):
rpm = self.domain_limits.get(domain, self.default_rpm)
min_interval = 60.0 / rpm
with self.lock:
now = time.time()
elapsed = now - self.domain_last[domain]
if elapsed < min_interval:
time.sleep(min_interval - elapsed)
self.domain_last[domain] = time.time()
# Configure per-domain limits
limiter = DomainRateLimiter(default_rpm=10)
limiter.set_limit("amazon.com", 3) # Very conservative for Amazon
limiter.set_limit("example.com", 30) # Lenient for simple sites
limiter.set_limit("google.com", 5) # Moderate for GoogleLettura Robots.txt per il tasso
Molti siti pubblicano le loro preferenze striscianti in robots.txt. The Crawl-delay direttiva ti dice i secondi minimi tra le richieste:
import requests
from urllib.parse import urlparse
from urllib.robotparser import RobotFileParser
def get_crawl_delay(base_url: str, user_agent: str = "*") -> float | None:
"""Extract Crawl-delay from robots.txt."""
parsed = urlparse(base_url)
robots_url = f"{parsed.scheme}://{parsed.netloc}/robots.txt"
try:
resp = requests.get(robots_url, timeout=10)
if resp.status_code != 200:
return None
rp = RobotFileParser()
rp.parse(resp.text.splitlines())
delay = rp.crawl_delay(user_agent)
return delay
except Exception:
return None
# Check before scraping
delay = get_crawl_delay("https://example.com")
if delay:
print(f"Site requests {delay}s between requests")
else:
print("No crawl-delay specified")Errori di limite del tasso comune
- Ignorando 429 risposte. Molti raschiatori trattano tutte le risposte non-200 lo stesso. Un 429 vi dice esattamente quello che è successo — utilizzare l'intestazione Retry-After e indietro.
- Risolto ritardi senza jitter. Una richiesta esattamente ogni 2.000 secondi sembra robotica. Aggiungi variazione casuale (jitter) ai tuoi ritardi.
- Non coordinare i lavoratori paralleli. Cinque lavoratori ciascuno facendo 10 RPM uguale 50 RPM totale. Utilizzare un limitatore di tasso condiviso.
- IP rotanti senza rallentare. La rotazione IP ti acquista il tempo, ma se ogni nuovo IP immediatamente martella il sito, il rilevamento avanzato ti cattura ancora. Combinare la rotazione con un corretto throttling.
- Scraping durante le ore di punta. I siti sono più aggressivi con la limitazione dei tassi durante periodi ad alto traffico. Pianificate i pesanti strisciamenti durante le ore fuori quota per il fuso orario del bersaglio.
Per calcolare quanti proxy è necessario sostenere la raschiatura limitata al tasso, vedere Quanti Proxies hai bisogno di Scraping?. Per le strategie di rotazione proxy che completano il limite di velocità, leggere Strategie di rotazione proxy per Scraping a grande scala.
Iniziare con il raschiamento del tasso limitato correttamente utilizzando ProxyHat Python SDK o esplorare piani di prezzi per il tuo progetto.
Domande frequenti
Cosa succede quando supero un limite di tariffa?
La risposta dipende dal sito. La maggior parte del ritorno HTTP 429 con un intestazione Retry-After. Alcuni servono i CAPTCHA. I siti aggressivi bloccano immediatamente l'IP con una risposta di 403. Nel peggiore dei casi, ripetute violazioni portano a divieti IP permanenti.
Come trovo il limite di tariffa di un sito?
Avviare lentamente e aumentare gradualmente durante il monitoraggio dei codici di risposta. Controllare robots.txt per le direttive Crawl-delay. Osserva le intestazioni di risposta per i campi X-RateLimit-Limit e X-RateLimit-Remaining. Alcune API pubblicano i loro limiti nella documentazione.
Usa i limiti di velocità di bypass dei proxy?
I proxy distribuiscono richieste su più IP, quindi ogni IP rimane sotto il limite per-IP. Tuttavia, i siti sofisticati tracciano anche sessioni, impronte digitali e modelli comportamentali. I proxy sono necessari ma non sufficienti — li combinano con i modelli di richiesta adeguati e realistici.
Qual è il tasso di richiesta più sicuro per la raschiatura?
Non c'è risposta universale. Per obiettivi aggressivi come Google o Amazon, 1-5 richieste al minuto per IP è sicuro. Per i siti leggermente protetti, possono funzionare 20-60 RPM per IP. Iniziare sempre conservatore e aumentare in base ai tassi di successo osservati.






