¿Cuáles son los Límites de Tasa de Rastreo?
Los límites de tarifas son las paredes invisibles que los sitios web construyen para controlar lo rápido que cualquier cliente puede hacer solicitudes. Cuando raspa un sitio demasiado agresivamente, golpeó estas paredes — y las consecuencias van desde desaceleraciones temporales hasta prohibiciones IP permanentes. Comprender cómo funcionan los límites de tarifas, cómo te detectan y cómo permanecer bajo ellos es fundamental para construir raspadores que proporcionen datos de forma fiable.
Esta guía explica la mecánica detrás de la limitación de tarifas, el uso de los sitios web de las señales de detección y estrategias prácticas para el acelerador adaptativo que mantienen sus raspadores funcionando sin problemas.
Para un panorama más amplio de raspado con próxies, vea nuestra Guía completa de Proxies de Rastreo Web. Para evitar bloques en general, leer Cómo cambiar sitios web sin ser bloqueados.
Cómo funciona la limitación de tarifas
Los sitios web implementan límites de velocidad en múltiples capas, cada una con diferente granularidad de detección:
Límites de tarifas basadas en IP
El enfoque más común. El servidor rastrea solicitudes por dirección IP dentro de una ventana de tiempo. Exceed the threshold and you receive HTTP 429 (Too Many Request) or 503 responses.
# Typical rate limit behavior
Request 1-50: HTTP 200 (normal)
Request 51: HTTP 429 (rate limited)
Wait 60 seconds...
Request 52: HTTP 200 (reset)Capa 2: Límites base en sesión/novato
Pistas solicitan frecuencia por sesión o cookie del navegador. Incluso si gira IPs, el mismo token de sesión golpear el servidor rápido activará límites.
Capa 3: Límites basados en la cuenta
Para los sitios que requieren acceso, los límites están vinculados a la cuenta de usuario independientemente de IP. Común en APIs y plataformas SaaS.
Capa 4: Análisis conductual
Sistemas avanzados como Cloudflare, PerimeterX y Akamai analizan patrones conductuales: tiempo de solicitud, flujo de navegación, movimientos del ratón (en contextos del navegador). Esta capa es la más difícil de pasar porque no depende de contadores simples.
Marcas de detección de límite de tarifas comunes
Los sitios web utilizan múltiples señales simultáneamente para detectar el desguace automatizado:
| Signal | Lo que detecta | Dificultad para evadir |
|---|---|---|
| Solicitudes por IP por minuto | Velocidad cruda | Fácil (proxies de uso) |
| Solicitudes por IP por hora/día | Volumen sostenido | Medio ( IPs rotativas) |
| Solicitud de regularidad de tiempo | Intervalos tipo máquina | Medium (add jitter) |
| Cabeceras desaparecidas o incorrectas | Clientes no Crepientes | Fácil (ajustar los encabezados adecuados) |
| Patrones de URL secuenciales | Arrastre sistemático | Medium (orden de aleatoria) |
| TLS huella dactilar | Biblioteca vs navegador | Duro (utiliza navegadores reales) |
| Ejecución de JavaScript | Navegador sin cabeza | Duro (conjunto avanzado) |
| Eventos de Mouse/keyboard | Comportamiento de botas | Muy duro |
Más información sobre los mecanismos de detección en nuestra guía Cómo los sistemas anticuerpos detectan proxies.
Códigos de respuesta HTTP que limitan la tasa de signos
Saber qué códigos HTTP indican la limitación de tarifas ayuda a construir una lógica de retry adecuada:
| Código | Significado | Medida |
|---|---|---|
| 200 (con CAPTCHA) | Bloque suave — página de desafío servida | Rotar IP, reducir la velocidad |
| 403 Prohibido | IP o sesión bloqueada | Rotar IP inmediatamente |
| 429 demasiadas solicitudes | Límite de tasa de explicidad | Espera y vuelve con el desvío |
| Servicio No disponible | Sobrecarga o bloque del servidor | Backoff, compruebe si está bloqueado |
| 302/307 a CAPTCHA URL | Challenge redirect | Rotar IP, reducir la velocidad |
Estrategia 1: Respetuoso impulso
El enfoque más simple: mantenga su tasa de solicitud muy por debajo de lo que el objetivo permite. Esto significa menos fracasos, menos desperdiciado ancho de banda, y más sostenible raspado.
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 resultsEstrategia 2: Ajuste adaptativo
En lugar de una tarifa fija, ajuste dinámicamente su velocidad basado en las respuestas que recibe. Acelera cuando todo funcione, desacelera cuando vea señales de advertencia.
Python Implementation
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 Implementation
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;
}Estrategia 3: Limitación de la tasa distribuida
Cuando se ejecutan múltiples instancias de desguace en paralelo, coordine el límite de tarifas en todos los trabajadores. Sin coordinación, cada trabajador respeta su propio límite, pero el tráfico combinado aún abruma el objetivo.
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)})Estrategia 4: Solicitar información con prioridad
Para proyectos complejos de desguace, utilice una cola prioritaria que gestiona los límites de tarifas por dominio objetivo:
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 GoogleLeyendo Robots.txt para Tarifas
Muchos sitios publican sus preferencias de rastreo en robots.txt. El Crawl-delay directiva le dice los segundos mínimos entre las solicitudes:
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")Errores de límite de tarifas comunes
- Ignorando 429 respuestas. Muchos raspadores tratan todas las respuestas no-200 de la misma manera. Un 429 te dice exactamente lo que pasó: usa el encabezado Retry-After y retrocede.
- Retrasos fijos sin problemas. Una petición exactamente cada 2.000 segundos parece robótica. Agregue la variación aleatoria (trituradora) a sus retrasos.
- No coordinar trabajadores paralelos. Cinco trabajadores cada uno haciendo 10 RPM equivale a 50 RPM total. Utilice un limitador de tarifas compartido.
- Rotating IPs sin frenar. La rotación IP le compra tiempo, pero si cada nuevo IP inmediatamente martilla el sitio, la detección avanzada todavía le atrapará. Combina la rotación con un correcto agitado.
- Scraping durante horas pico. Los sitios son más agresivos con la limitación de tarifas durante períodos de alto tráfico. Programar arrastres pesados durante horas libres para la zona temporal del objetivo.
Para calcular cuántos proxies necesitas para apoyar tu raspado limitado por tarifas, vea ¿Cuántos Proxies necesitas para rascar?. Para las estrategias de rotación proxy que complementan la limitación de tarifas, leer Estrategias de Rotación Proxy para Raspado de Escala Grande.
Comenzar con el raspado de tipo adecuado utilizando el ProxyHat Python SDK o explorar planes de fijación de precios para su proyecto.
Preguntas frecuentes
¿Qué pasa cuando supero un límite de tarifas?
La respuesta depende del sitio. La mayoría devuelve HTTP 429 con un encabezado Retry-After. Algunos sirven a CAPTCHAs. Los sitios agresivos bloquean inmediatamente la IP con una respuesta de 403. En el peor de los casos, las reiteradas violaciones conducen a prohibiciones permanentes de IP.
¿Cómo encuentro el límite de tarifas de un sitio?
Comience despacio y aumente gradualmente mientras monitorice los códigos de respuesta. Revise robots.txt para directivas de Crawl-delay. Observe los encabezados de respuesta para campos X-RateLimit-Limit y X-RateLimit-Remanente. Algunas API publican sus límites en la documentación.
¿Utiliza límites de tasa de bypass?
Proxies distribuye solicitudes a través de múltiples IPs, por lo que cada IP permanece bajo el límite per-IP. Sin embargo, sitios sofisticados también rastrean sesiones, huellas dactilares y patrones conductuales. Los ejes son necesarios pero no suficientes, combinarlos con patrones adecuados de agitación y solicitud realistas.
¿Cuál es la tasa de solicitud más segura para el raspado?
No hay respuesta universal. Para objetivos agresivos como Google o Amazon, 1-5 solicitudes por minuto por IP es seguro. Para sitios ligeramente protegidos, puede funcionar 20-60 RPM por IP. Siempre empezar conservador y aumentar basado en las tasas de éxito observadas.






