Protección contra bloqueos al realizar solicitudes masivas: técnicas y herramientas
El bloqueo de cuentas y direcciones IP es el principal problema al hacer scraping, automatización y operaciones masivas en redes sociales. Los sistemas anti-bot modernos analizan decenas de parámetros: desde la frecuencia de las solicitudes hasta las huellas del navegador. En esta guía, analizaremos los mecanismos específicos de detección de automatización y formas prácticas de eludirlos.
Mecanismos de detección de automatización
Los sistemas de protección modernos utilizan un análisis multinivel para identificar bots. Comprender estos mecanismos es críticamente importante para elegir la estrategia adecuada de elusión.
Parámetros clave de análisis
Reputación IP: Los sistemas anti-bot verifican el historial de la dirección IP, su pertenencia a centros de datos y su inclusión en listas negras. Las IP de pools de proxies conocidos son bloqueadas con más frecuencia.
Frecuencia de solicitudes (Request Rate): Un humano no puede enviar físicamente 100 solicitudes por minuto. Los sistemas analizan no solo el número total, sino también la distribución en el tiempo: intervalos uniformes entre solicitudes delatan un bot.
Patrones de comportamiento: Secuencia de acciones, profundidad de desplazamiento, movimientos del ratón, tiempo en la página. Un bot que salta instantáneamente entre enlaces sin retrasos es fácilmente reconocible.
Huella técnica: User-Agent, encabezados HTTP, orden de los encabezados, huella TLS, fingerprinting de Canvas/WebGL. Las discrepancias en estos parámetros son una señal de alerta para los sistemas anti-bot.
| Parámetro | Qué se analiza | Riesgo de detección |
|---|---|---|
| Dirección IP | Reputación, ASN, geolocalización | Alto |
| User-Agent | Versión del navegador, SO, dispositivo | Medio |
| Huella TLS | Conjunto de cifrados, extensiones | Alto |
| Huella HTTP/2 | Orden de los encabezados, configuraciones | Alto |
| Canvas/WebGL | Renderizado gráfico | Medio |
| Comportamiento | Clics, desplazamiento, tiempo | Alto |
Limitación de tasa y control de frecuencia de solicitudes
Controlar la velocidad de envío de solicitudes es la primera línea de defensa contra bloqueos. Incluso con rotación de proxies, un scraping demasiado agresivo puede llevar a un baneo.
Retrasos dinámicos
Los intervalos fijos (por ejemplo, exactamente 2 segundos entre solicitudes) son fácilmente reconocibles. Utiliza retrasos aleatorios con distribución normal:
import time
import random
import numpy as np
def human_delay(min_delay=1.5, max_delay=4.0, mean=2.5, std=0.8):
"""
Generación de un retraso con distribución normal
que imita el comportamiento humano
"""
delay = np.random.normal(mean, std)
# Limitar el rango
delay = max(min_delay, min(delay, max_delay))
# Añadir micro-retrasos para realismo
delay += random.uniform(0, 0.3)
time.sleep(delay)
# Uso
for url in urls:
response = session.get(url)
human_delay(min_delay=2, max_delay=5, mean=3, std=1)
Limitación de tasa adaptativa
Un enfoque más avanzado es adaptar la velocidad según las respuestas del servidor. Si recibes códigos 429 (Demasiadas solicitudes) o 503, reduce automáticamente la velocidad:
class AdaptiveRateLimiter:
def __init__(self, initial_delay=2.0):
self.current_delay = initial_delay
self.min_delay = 1.0
self.max_delay = 30.0
self.error_count = 0
def wait(self):
time.sleep(self.current_delay + random.uniform(0, 0.5))
def on_success(self):
# Aceleramos gradualmente en solicitudes exitosas
self.current_delay = max(
self.min_delay,
self.current_delay * 0.95
)
self.error_count = 0
def on_rate_limit(self):
# Reducimos drásticamente en caso de bloqueo
self.error_count += 1
self.current_delay = min(
self.max_delay,
self.current_delay * (1.5 + self.error_count * 0.5)
)
print(f"Límite de tasa alcanzado. Nuevo retraso: {self.current_delay:.2f}s")
# Aplicación
limiter = AdaptiveRateLimiter(initial_delay=2.0)
for url in urls:
limiter.wait()
response = session.get(url)
if response.status_code == 429:
limiter.on_rate_limit()
time.sleep(60) # Pausa antes de repetir
elif response.status_code == 200:
limiter.on_success()
else:
# Manejo de otros errores
pass
Consejo práctico: La velocidad óptima varía entre diferentes sitios. Las grandes plataformas (Google, Facebook) toleran de 5 a 10 solicitudes por minuto desde una sola IP. Los sitios más pequeños pueden bloquear ya con 20-30 solicitudes por hora. Siempre comienza de manera conservadora y aumenta gradualmente la carga, monitoreando el porcentaje de errores.
Rotación de proxies y gestión de direcciones IP
Utilizar una sola dirección IP para solicitudes masivas garantiza un bloqueo. La rotación de proxies distribuye la carga y reduce el riesgo de detección.
Estrategias de rotación
1. Rotación por solicitudes: Cambio de IP después de cada o cada N solicitudes. Adecuado para scraping de motores de búsqueda, donde la anonimidad de cada solicitud es importante.
2. Rotación por tiempo: Cambio de IP cada 5-15 minutos. Efectivo para trabajar con redes sociales, donde la estabilidad de la sesión es importante.
3. Sesiones pegajosas: Uso de una sola IP para toda la sesión del usuario (autenticación, secuencia de acciones). Crítico para sitios con protección CSRF.
import requests
from itertools import cycle
class ProxyRotator:
def __init__(self, proxy_list, rotation_type='request', rotation_interval=10):
"""
rotation_type: 'request' (cada solicitud) o 'time' (por tiempo)
rotation_interval: número de solicitudes o segundos
"""
self.proxies = cycle(proxy_list)
self.current_proxy = next(self.proxies)
self.rotation_type = rotation_type
self.rotation_interval = rotation_interval
self.request_count = 0
self.last_rotation = time.time()
def get_proxy(self):
if self.rotation_type == 'request':
self.request_count += 1
if self.request_count >= self.rotation_interval:
self.current_proxy = next(self.proxies)
self.request_count = 0
print(f"Rotado a: {self.current_proxy}")
elif self.rotation_type == 'time':
if time.time() - self.last_rotation >= self.rotation_interval:
self.current_proxy = next(self.proxies)
self.last_rotation = time.time()
print(f"Rotado a: {self.current_proxy}")
return {'http': self.current_proxy, 'https': self.current_proxy}
# Ejemplo de uso
proxy_list = [
'http://user:pass@proxy1.example.com:8000',
'http://user:pass@proxy2.example.com:8000',
'http://user:pass@proxy3.example.com:8000',
]
rotator = ProxyRotator(proxy_list, rotation_type='request', rotation_interval=5)
for url in urls:
proxies = rotator.get_proxy()
response = requests.get(url, proxies=proxies, timeout=10)
Elección del tipo de proxy
| Tipo de proxy | Nivel de confianza | Velocidad | Aplicación |
|---|---|---|---|
| Centros de datos | Bajo | Alta | Scraping simple, API |
| Residenciales | Alto | Media | Redes sociales, sitios protegidos |
| Móviles | Muy alto | Media | Instagram, TikTok, anti-fraude |
Para operaciones masivas en redes sociales y plataformas con protección seria, utiliza proxies residenciales. Se ven como conexiones domésticas normales y rara vez son incluidos en listas negras. Los centros de datos son adecuados para recursos menos protegidos, donde la velocidad es importante.
Fingerprinting del navegador y huellas TLS
Incluso con rotación de IP, puedes ser identificado por las huellas técnicas del navegador y la conexión TLS. Estos parámetros son únicos para cada cliente y difíciles de falsificar.
Huella TLS
Al establecer una conexión HTTPS, el cliente envía un ClientHello con un conjunto de cifrados y extensiones soportadas. Esta combinación es única para cada biblioteca. Por ejemplo, Python requests utiliza OpenSSL, cuya huella es fácilmente diferenciable de la de Chrome.
Problema: Las bibliotecas estándar (requests, urllib, curl) tienen huellas diferentes a las de los navegadores reales. Servicios como Cloudflare, Akamai, DataDome utilizan activamente el fingerprinting TLS para bloquear bots.
Solución: Utiliza bibliotecas que imiten las huellas TLS de los navegadores. Para Python, esto incluye curl_cffi, tls_client o playwright/puppeteer para una emulación completa del navegador.
# Instalación: pip install curl-cffi
from curl_cffi import requests
# Imitando Chrome 110
response = requests.get(
'https://example.com',
impersonate="chrome110",
proxies={'https': 'http://proxy:port'}
)
# Alternativa: tls_client
import tls_client
session = tls_client.Session(
client_identifier="chrome_108",
random_tls_extension_order=True
)
response = session.get('https://example.com')
Huella HTTP/2
Además de TLS, los sistemas anti-bot analizan parámetros HTTP/2: orden de los encabezados, configuraciones del marco SETTINGS, prioridades de flujos. Las bibliotecas estándar no mantienen el orden exacto de los encabezados de Chrome o Firefox.
# Orden correcto de los encabezados para Chrome
headers = {
':method': 'GET',
':authority': 'example.com',
':scheme': 'https',
':path': '/',
'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...',
'accept': 'text/html,application/xhtml+xml...',
'sec-fetch-site': 'none',
'sec-fetch-mode': 'navigate',
'sec-fetch-user': '?1',
'sec-fetch-dest': 'document',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9',
}
Fingerprinting de Canvas y WebGL
Los navegadores renderizan gráficos de manera diferente dependiendo de la GPU, controladores y SO. Los sitios utilizan esto para crear una huella única del dispositivo. Al utilizar navegadores headless (Selenium, Puppeteer), es importante enmascarar las señales de automatización:
// Puppeteer: ocultando el modo headless
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch({
headless: true,
args: [
'--disable-blink-features=AutomationControlled',
'--no-sandbox',
'--disable-setuid-sandbox',
`--proxy-server=${proxyUrl}`
]
});
const page = await browser.newPage();
// Sobrescribiendo navigator.webdriver
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
});
});
Encabezados, cookies y gestión de sesiones
Trabajar correctamente con encabezados HTTP y cookies es crítico para imitar a un usuario real. Los errores en estos parámetros son una causa frecuente de bloqueos.
Encabezados obligatorios
El conjunto mínimo de encabezados para imitar el navegador Chrome:
import requests
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'DNT': '1',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Cache-Control': 'max-age=0',
}
session = requests.Session()
session.headers.update(headers)
Gestión de cookies
Muchos sitios establecen cookies de seguimiento en la primera visita y verifican su presencia en solicitudes posteriores. La ausencia de cookies o su discrepancia es un signo de un bot.
import requests
import pickle
class SessionManager:
def __init__(self, session_file='session.pkl'):
self.session_file = session_file
self.session = requests.Session()
self.load_session()
def load_session(self):
"""Carga de la sesión guardada"""
try:
with open(self.session_file, 'rb') as f:
cookies = pickle.load(f)
self.session.cookies.update(cookies)
except FileNotFoundError:
pass
def save_session(self):
"""Guardar cookies para reutilización"""
with open(self.session_file, 'wb') as f:
pickle.dump(self.session.cookies, f)
def request(self, url, **kwargs):
response = self.session.get(url, **kwargs)
self.save_session()
return response
# Uso
manager = SessionManager('instagram_session.pkl')
response = manager.request('https://www.instagram.com/explore/')
Importante: Al rotar proxies, no olvides restablecer las cookies si están vinculadas a una IP específica. La discrepancia entre la IP y las cookies (por ejemplo, cookies con geolocalización en EE.UU. y IP de Alemania) generará sospechas.
Referer y Origin
Los encabezados Referer y Origin indican de dónde proviene el usuario. Su ausencia o valores incorrectos son una señal de alerta.
# Secuencia correcta: inicio → categoría → producto
session = requests.Session()
# Paso 1: acceder al inicio
response = session.get('https://example.com/')
# Paso 2: ir a la categoría
response = session.get(
'https://example.com/category/electronics',
headers={'Referer': 'https://example.com/'}
)
# Paso 3: ver el producto
response = session.get(
'https://example.com/product/12345',
headers={'Referer': 'https://example.com/category/electronics'}
)
Simulación del comportamiento humano
Los parámetros técnicos son solo la mitad del trabajo. Los sistemas anti-bot modernos analizan patrones de comportamiento: cómo interactúa el usuario con la página, cuánto tiempo pasa, cómo se mueve el ratón.
Desplazamiento y movimiento del ratón
Al usar Selenium o Puppeteer, agrega movimientos aleatorios del ratón y desplazamiento de la página:
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import random
import time
def human_like_mouse_move(driver):
"""Movimiento aleatorio del ratón por la página"""
action = ActionChains(driver)
for _ in range(random.randint(3, 7)):
x = random.randint(0, 1000)
y = random.randint(0, 800)
action.move_by_offset(x, y)
action.pause(random.uniform(0.1, 0.3))
action.perform()
def human_like_scroll(driver):
"""Simulación de desplazamiento natural"""
total_height = driver.execute_script("return document.body.scrollHeight")
current_position = 0
while current_position < total_height:
# Paso de desplazamiento aleatorio
scroll_step = random.randint(100, 400)
current_position += scroll_step
driver.execute_script(f"window.scrollTo(0, {current_position});")
# Pausa con variación
time.sleep(random.uniform(0.5, 1.5))
# A veces desplazamos un poco hacia atrás (como lo hacen las personas)
if random.random() < 0.2:
back_scroll = random.randint(50, 150)
current_position -= back_scroll
driver.execute_script(f"window.scrollTo(0, {current_position});")
time.sleep(random.uniform(0.3, 0.8))
# Uso
driver = webdriver.Chrome()
driver.get('https://example.com')
human_like_mouse_move(driver)
time.sleep(random.uniform(2, 4))
human_like_scroll(driver)
Tiempo en la página
Los usuarios reales pasan tiempo en la página: leen contenido, observan imágenes. Un bot que salta instantáneamente entre enlaces es fácilmente reconocible.
def realistic_page_view(driver, url, min_time=5, max_time=15):
"""
Visualización realista de la página con actividad
"""
driver.get(url)
# Retraso inicial (carga y "lectura")
time.sleep(random.uniform(2, 4))
# Desplazamiento
human_like_scroll(driver)
# Actividad adicional
total_time = random.uniform(min_time, max_time)
elapsed = 0
while elapsed < total_time:
action_choice = random.choice(['scroll', 'mouse_move', 'pause'])
if action_choice == 'scroll':
# Pequeño desplazamiento hacia arriba/abajo
scroll_amount = random.randint(-200, 300)
driver.execute_script(f"window.scrollBy(0, {scroll_amount});")
pause = random.uniform(1, 3)
elif action_choice == 'mouse_move':
human_like_mouse_move(driver)
pause = random.uniform(0.5, 2)
else: # pause
pause = random.uniform(2, 5)
time.sleep(pause)
elapsed += pause
Patrones de navegación
Evita patrones sospechosos: saltos directos a páginas profundas, ignorar la página de inicio, recorrer todos los elementos de manera secuencial sin omisiones.
Buenas prácticas:
- Comienza desde la página de inicio o secciones populares
- Utiliza la navegación interna del sitio, no URLs directas
- A veces regresa o navega a otras secciones
- Varía la profundidad de visualización: no siempre llegues al final
- Agrega "errores": saltos a enlaces inexistentes, regresos
Eludir Cloudflare, DataDome y otras protecciones
Los sistemas anti-bot especializados requieren un enfoque integral. Utilizan desafíos de JavaScript, CAPTCHA, análisis de comportamiento en tiempo real.
Cloudflare
Cloudflare utiliza varios niveles de protección: Verificación de Integridad del Navegador, Desafío de JavaScript, CAPTCHA. Para eludir la protección básica, basta con una huella TLS correcta y ejecutar JavaScript:
# Opción 1: cloudscraper (solución automática para desafíos JS)
import cloudscraper
scraper = cloudscraper.create_scraper(
browser={
'browser': 'chrome',
'platform': 'windows',
'desktop': True
}
)
response = scraper.get('https://protected-site.com')
# Opción 2: undetected-chromedriver (para casos complejos)
import undetected_chromedriver as uc
options = uc.ChromeOptions()
options.add_argument('--proxy-server=http://proxy:port')
driver = uc.Chrome(options=options)
driver.get('https://protected-site.com')
# Esperar a que se complete el desafío
time.sleep(5)
# Obtener cookies para requests
cookies = driver.get_cookies()
session = requests.Session()
for cookie in cookies:
session.cookies.set(cookie['name'], cookie['value'])
DataDome
DataDome analiza el comportamiento del usuario en tiempo real: movimientos del ratón, escritura en el teclado, tiempos. Para eludirlo, se necesita un navegador completo con simulación de actividad:
from playwright.sync_api import sync_playwright
import random
def bypass_datadome(url, proxy=None):
with sync_playwright() as p:
browser = p.chromium.launch(
headless=False, # DataDome detecta headless
proxy={'server': proxy} if proxy else None
)
context = browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64)...'
)
page = context.new_page()
# Inyección de scripts para enmascarar la automatización
page.add_init_script("""
Object.defineProperty(navigator, 'webdriver', {get: () => false});
window.chrome = {runtime: {}};
""")
page.goto(url)
# Simulación del comportamiento humano
time.sleep(random.uniform(2, 4))
# Movimientos aleatorios del ratón
for _ in range(random.randint(5, 10)):
page.mouse.move(
random.randint(100, 1800),
random.randint(100, 1000)
)
time.sleep(random.uniform(0.1, 0.3))
# Desplazamiento
page.evaluate(f"window.scrollTo(0, {random.randint(300, 800)})")
time.sleep(random.uniform(1, 2))
content = page.content()
browser.close()
return content
CAPTCHA
Para resolver automáticamente CAPTCHA, utiliza servicios de reconocimiento (2captcha, Anti-Captcha) o estrategias de evasión:
- Reduce la frecuencia de solicitudes a un nivel que no active CAPTCHA
- Utiliza IP residenciales limpias con buena reputación
- Trabaja a través de cuentas autorizadas (tienen un umbral de CAPTCHA más alto)
- Distribuye la carga a lo largo del tiempo (evita horas pico)
Monitoreo y manejo de bloqueos
Incluso con las mejores prácticas, los bloqueos son inevitables. Es importante detectarlos rápidamente y manejarlos correctamente.
Indicadores de bloqueo
| Señal | Descripción | Acción |
|---|---|---|
| HTTP 429 | Demasiadas solicitudes | Aumentar retrasos, cambiar IP |
| HTTP 403 | Prohibido (baneo de IP) | Cambiar proxy, verificar fingerprint |
| CAPTCHA | Se requiere verificación | Resolver o reducir actividad |
| Respuesta vacía | El contenido no se carga | Verificar JavaScript, cookies |
| Redirección a /blocked | Bloqueo explícito | Cambio completo de estrategia |
Sistema de reintentos
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
def create_session_with_retries():
"""
Sesión con reintentos automáticos y manejo de errores
"""
session = requests.Session()
retry_strategy = Retry(
total=5,
backoff_factor=2, # 2, 4, 8, 16, 32 segundos
status_forcelist=[429, 500, 502, 503, 504],
method_whitelist=["GET", "POST"]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
return session
def safe_request(url, session, max_attempts=3):
"""
Solicitud con manejo de bloqueos
"""
for attempt in range(max_attempts):
try:
response = session.get(url, timeout=15)
# Verificación de bloqueo
if response.status_code == 403:
print(f"IP bloqueada. Rotando proxy...")
# Lógica para cambiar proxy
continue
elif response.status_code == 429:
wait_time = int(response.headers.get('Retry-After', 60))
print(f"Límite de tasa. Esperando {wait_time}s...")
time.sleep(wait_time)
continue
elif 'captcha' in response.text.lower():
print("CAPTCHA detectado")
# Lógica para resolver CAPTCHA o saltar
return None
return response
except requests.exceptions.Timeout:
print(f"Tiempo de espera en el intento {attempt + 1}")
time.sleep(5 * (attempt + 1))
except requests.exceptions.ProxyError:
print("Error de proxy. Rotando...")
# Cambio de proxy
continue
return None
Registro y análisis
Monitorea métricas para optimizar la estrategia:
import logging
from collections import defaultdict
from datetime import datetime
class ScraperMetrics:
def __init__(self):
self.stats = {
'total_requests': 0,
'successful': 0,
'rate_limited': 0,
'blocked': 0,
'captcha': 0,
'errors': 0,
'proxy_failures': defaultdict(int)
}
def log_request(self, status, proxy=None):
self.stats['total_requests'] += 1
if status == 200:
self.stats['successful'] += 1
elif status == 429:
self.stats['rate_limited'] += 1
elif status == 403:
self.stats['blocked'] += 1
if proxy:
self.stats['proxy_failures'][proxy] += 1
def get_success_rate(self):
if self.stats['total_requests'] == 0:
return 0
return (self.stats['successful'] / self.stats['total_requests']) * 100
def print_report(self):
print(f"\n=== Informe de Scraping ===")
print(f"Total de solicitudes: {self.stats['total_requests']}")
print(f"Tasa de éxito: {self.get_success_rate():.2f}%")
print(f"Límite de tasa: {self.stats['rate_limited']}")
print(f"Baneado: {self.stats['blocked']}")
print(f"CAPTCHA: {self.stats['captcha']}")
if self.stats['proxy_failures']:
print(f"\nProxies problemáticos:")
for proxy, count in sorted(
self.stats['proxy_failures'].items(),
key=lambda x: x[1],
reverse=True
)[:5]:
print(f" {proxy}: {count} fallos")
# Uso
metrics = ScraperMetrics()
for url in urls:
response = safe_request(url, session)
if response:
metrics.log_request(response.status_code, current_proxy)
metrics.print_report()
Indicadores óptimos: Una tasa de éxito superior al 95% es un excelente resultado. 80-95% es aceptable, pero hay espacio para mejorar. Por debajo del 80% — revisa la estrategia: puede que la limitación de tasa sea demasiado agresiva, proxies deficientes o problemas con el fingerprinting.
Conclusión
La protección contra bloqueos es un aspecto crítico al realizar scraping y automatización. Implementando las técnicas y herramientas discutidas, puedes minimizar el riesgo de ser bloqueado y maximizar la eficiencia de tus operaciones.