Límites de velocidad en scraping explicados

Cómo funcionan los límites de tarifas, cómo los sitios detectan raspadores y estrategias prácticas para mantenerse bajo los límites. Incluye código de oscilación adaptable y patrones de limitación de tarifas distribuidas.

Límites de velocidad en scraping explicados

¿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:

Marcas de detección de límite de tarifas comunes
SignalLo que detectaDificultad para evadir
Solicitudes por IP por minutoVelocidad crudaFácil (proxies de uso)
Solicitudes por IP por hora/díaVolumen sostenidoMedio ( IPs rotativas)
Solicitud de regularidad de tiempoIntervalos tipo máquinaMedium (add jitter)
Cabeceras desaparecidas o incorrectasClientes no CrepientesFácil (ajustar los encabezados adecuados)
Patrones de URL secuencialesArrastre sistemáticoMedium (orden de aleatoria)
TLS huella dactilarBiblioteca vs navegadorDuro (utiliza navegadores reales)
Ejecución de JavaScriptNavegador sin cabezaDuro (conjunto avanzado)
Eventos de Mouse/keyboardComportamiento de botasMuy 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ódigos de respuesta HTTP que limitan la tasa de signos
CódigoSignificadoMedida
200 (con CAPTCHA)Bloque suave — página de desafío servidaRotar IP, reducir la velocidad
403 ProhibidoIP o sesión bloqueadaRotar IP inmediatamente
429 demasiadas solicitudesLímite de tasa de explicidadEspera y vuelve con el desvío
Servicio No disponibleSobrecarga o bloque del servidorBackoff, compruebe si está bloqueado
302/307 a CAPTCHA URLChallenge redirectRotar 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 results

Estrategia 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 results

Node.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 Google

Leyendo 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.

¿Listo para empezar?

Accede a más de 50M de IPs residenciales en más de 148 países con filtrado impulsado por IA.

Ver preciosProxies residenciales
← Volver al Blog