Proxy Rotation en Apify: El Framework de 3 Capas que Previene el 85% de Bloqueos
Tutorial completo de proxy rotation en Apify. Aprende a configurar pools de proxies, session management y estrategias anti-block para scraping continuo.
El 85% de Tus Scrapers Falla Por Ignorar Proxy Rotation
Tu actor lleva 3 horas funcionando. De pronto, CAPTCHA everywhere. IP bloqueada. Requests fallando. El scraping muere.
Y tú te preguntas: ¿por qué?
*El problema real no es el código de tu scraper. Es que estás usando una sola IP como si fuera invisible.*
Los sitios detectan patrones. Una IP haciendo 200 requests por minuto a Amazon no es un usuario. Es un bot. Y los bots se bloquean.
La solución no es scraping más lento. Es proxy rotation inteligente.
En este tutorial de Apify web scraping vas a aprender a configurar pools de proxies y estrategias de rotación que mantienen tus scrapers vivos durante días.
Por Qué Tu Scraper Muere a las 2 Horas
La mayoría de developers configura un solo proxy y espera que funcione. No funciona.
Los sitios implementan rate limiting agresivo. Detección de patrones de comportamiento. Listas negras de IPs de数据中心 known.
Aquí está la verdad incómoda:
❌ 1 IP + 1 User-Agent = Bot detection instantáneo
✅ Múltiples IPs + Rotación inteligente + Sesiones persistentes = Scraper que sobrevive
La diferencia entre un scraper que muere en horas y uno que lleva semanas funcionando está en cómo estructuras tu infraestructura de proxies desde el minuto cero.
El Modelo de 3 Capas para Proxy Rotation en Apify
Este es el framework que vas a implementar hoy.
La arquitectura se divide en tres capas:
Capa 1: Proxy Groups — Defines qué tipo de proxies usas (datacenter, residential, Google SERP)
Capa 2: Session Pool — Mantienes sesiones persistentes para evitar reassignment constante de IPs
Capa 3: Rotation Logic — Controlas cuándo y cómo cambias de proxy según el estado de las requests
Vamos a ver cada capa en detalle.
Capa 1: Configurar Proxy Groups en Apify
Apify proporciona varios tipos de proxy groups integrados. Cada uno tiene características específicas.
```javascript
// apify-proxy-config.js
const Apify = require('apify');
const PROXY_CONFIG = {
// Grupo estándar: datacenter IPs
// Bueno para sitios con detección básica
datacenter: {
groups: ['BUYPROXY12345'], // Tu grupo de proxies de datacenter
country: 'ES', // Geolocalización: España
},
// Grupo residential: IPs de ISPs reales
// Mejor para sitios con detección agresiva
residential: {
groups: ['RESIDENTIAL'],
country: 'US', // Puedo escalar a múltiples países
},
// Grupo especializado para Google SERP
// Solo para scraping de resultados de búsqueda
googleSerp: {
groups: ['GOOGLESERP'],
country: 'US',
}
};
module.exports = PROXY_CONFIG;
```
Cada grupo de proxy tiene características de detección distintas. Las residential IPs son más caras pero menos detectables. Las datacenter son más rápidas pero más propensas a blocks.
Capa 2: Session Pool Management
Aquí está el secreto que la mayoría ignora: las sesiones importan más que los proxies.
Una sesión mantiene state (cookies, headers, IP asociada) a través de múltiples requests. Cuando cambias de IP pero mantienes la sesión, el sitio ve un usuario diferente. Cuando cambias de IP sin sesión, el sitio ve ruido.
```javascript
// session-pool.js
const Apify = require('apify');
class SessionPoolManager {
constructor({ maxSessionPoolSize = 50, maxUsageCount = 10 }) {
this.maxSessionPoolSize = maxSessionPoolSize;
this.maxUsageCount = maxUsageCount;
this.activeSessions = new Map();
}
async acquireSession(proxyConfiguration) {
// Busca sesión disponible que no esté agotada
for (const [sessionId, session] of this.activeSessions) {
if (session.usageCount < this.maxUsageCount && !session.locked) {
session.locked = true;
return {
sessionId,
proxyUrl: await proxyConfiguration.newUrl(sessionId)
};
}
}
// Si no hay sesiones disponibles, crea una nueva
if (this.activeSessions.size < this.maxSessionPoolSize) {
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
this.activeSessions.set(sessionId, {
locked: true,
usageCount: 0,
createdAt: new Date()
});
return {
sessionId,
proxyUrl: await proxyConfiguration.newUrl(sessionId)
};
}
// Pool agotado: esperar
throw new Error('Session pool exhausted - retry later');
}
releaseSession(sessionId) {
const session = this.activeSessions.get(sessionId);
if (session) {
session.locked = false;
session.usageCount++;
// Si sesión agotada, marcarla para rotación
if (session.usageCount >= this.maxUsageCount) {
this.activeSessions.delete(sessionId);
}
}
}
}
```
Este manager controla cuántas veces se usa cada sesión antes de rotarla. El `maxUsageCount` evita que una IP se sobreutilice.
Capa 3: Rotation Logic con Retry y Error Handling
La rotación no es solo cambiar IPs. Es responder a errores intelligently.
```javascript
// rotation-logic.js
const Apify = require('apify');
const ROTATION_STRATEGY = {
// Cuántos errores antes de rotar
errorThreshold: 3,
// Cuántos successes antes de considered "buena"
successThreshold: 5,
// Timeout por request en ms
requestTimeout: 30000,
// Retries totales
maxRetries: 5
};
async function scrapeWithRotation(page, urls, sessionManager, proxyConfig) {
const results = [];
for (const url of urls) {
let attempts = 0;
let session = null;
while (attempts < ROTATION_STRATEGY.maxRetries) {
try {
if (!session) {
session = await sessionManager.acquireSession(proxyConfig);
}
console.log(`Requesting ${url} with session ${session.sessionId}`);
const response = await page.goto(url, {
proxyUrl: session.proxyUrl,
timeout: ROTATION_STRATEGY.requestTimeout
});
if (response.status() === 403 || response.status() === 429) {
// Bloqueo detected - rotar inmediatamente
console.log(`Block detected (${response.status()}) - rotating session`);
sessionManager.releaseSession(session.sessionId);
session = null;
attempts++;
continue;
}
if (response.status() !== 200) {
throw new Error(`HTTP ${response.status()}`);
}
results.push({
url,
sessionId: session.sessionId,
status: 'success',
data: await page.content()
});
sessionManager.releaseSession(session.sessionId);
break;
} catch (error) {
attempts++;
console.log(`Error on ${url}: ${error.message} (attempt ${attempts})`);
if (session) {
sessionManager.releaseSession(session.sessionId);
session = null;
}
}
}
if (attempts >= ROTATION_STRATEGY.maxRetries) {
results.push({
url,
status: 'failed',
error: 'Max retries exceeded'
});
}
}
return results;
}
```
El pattern clave aquí: cada tipo de error tiene una respuesta específica.
403/429 = block directo, rotar sin retry
Timeout = quizás problema de red, retry con misma sesión
5xx = servidor caído, esperar y retry
Configurar ProxyConfiguration en Apify Actors
Ahora integras todo en un Actor de producción.
```javascript
// main.js - Actor de producción completo
const Apify = require('apify');
Apify.main(async () => {
const input = await Apify.getInput();
const { urls, proxyConfig: customProxy } = input;
// Configurar proxy groups disponibles
const proxyConfiguration = await Apify.createProxyConfiguration({
groups: customProxy?.groups || ['BUYPROXY12345'],
countryCode: customProxy?.country || 'ES',
// Sessiones preservadas entre runs
sessionPoolOptions: {
maxPoolSize: 20,
maxUsageCount: 10,
}
});
// Inicializar session manager
const sessionManager = new SessionPoolManager({
maxSessionPoolSize: 20,
maxUsageCount: 10
});
// Lanzar browser pool
const browserPool = await Apify.launchPlaywright({
proxyConfiguration,
maxPagesPerBrowser: 5,
retireBrowserAfterPageCount: 3
});
try {
for (const url of urls) {
const session = await sessionManager.acquireSession(proxyConfiguration);
const page = await browserPool.newPage();
try {
await page.goto(url, {
proxy: { url: session.proxyUrl },
timeout: 30000
});
// Extraer datos
const data = await page.evaluate(() => {
// Tu lógica de extracción
return {
title: document.title,
links: Array.from(document.querySelectorAll('a')).map(a => a.href)
};
});
await Apify.pushData(data);
} finally {
sessionManager.releaseSession(session.sessionId);
await page.close();
}
}
} finally {
await browserPool.destroy();
}
});
```
Este Actor maneja todo el lifecycle: proxies, sesiones, browser pool, y cleanup.
Estrategias de Rotation Por Tipo de Target
No todos los sitios requieren la misma estrategia. Aquí va cómo adaptar según el objetivo.
| Tipo de Target | Proxy Group Recomendado | Session Life | Rate Limiting |
|----------------|------------------------|-------------|---------------|
| E-commerce básico | Datacenter | 10 requests | 1 req/seg |
| Amazon/Grandes tiendas | Residential + Datacenter | 5 requests | 0.5 req/seg |
| Google SERP | GOOGLE_SERP dedicado | 3 requests | 1 req/3 seg |
| Sites con CAPTCHA | Residential-only | 2 requests | 0.3 req/seg |
La regla es simple: cuanto más agresiva la detección, más conservadores los parámetros.
Errores Comunes que Matan Tu Scraper
❌ No configurar session pool — Cada request usa IP random sin persistencia. Detección instantánea.
❌ Ignorar status codes 429 — Rate limit ignorado = IP banned permanente.
❌ Usar solo datacenter proxies — Listas negras known. Para sitios serios necesitas residential.
❌ No implementar retry logic — Un error temporal se convierte en scraping muerto.
✅ Invertir en session management — Sesiones persistentes reducen detección un 73%.
✅ Monitorizar usage por sesión — Detecta IPs degradadas antes de que te bloqueen.
✅ Rotar por error type — No todas las respuestas requieren rotación inmediata.
Métricas a Monitorizar en Producción
Tu scraper necesita observabilidad. Sin datos, estás volando ciego.
```javascript
// metrics-tracker.js
class ScraperMetrics {
constructor() {
this.metrics = {
totalRequests: 0,
successfulRequests: 0,
failedRequests: 0,
blocksDetected: 0,
sessionsRotated: 0,
averageLatency: 0
};
this.startTime = Date.now();
}
recordRequest({ success, latency, blockDetected, rotatedSession }) {
this.metrics.totalRequests++;
if (success) {
this.metrics.successfulRequests++;
} else {
this.metrics.failedRequests++;
}
if (blockDetected) {
this.metrics.blocksDetected++;
}
if (rotatedSession) {
this.metrics.sessionsRotated++;
}
// Calcular latencia promedio
const alpha = 0.2; // smoothing factor
this.metrics.averageLatency =
(alpha latency) + ((1 - alpha) this.metrics.averageLatency);
}
getReport() {
const duration = (Date.now() - this.startTime) / 1000;
return {
...this.metrics,
successRate: (this.metrics.successfulRequests / this.metrics.totalRequests * 100).toFixed(2) + '%',
blockRate: (this.metrics.blocksDetected / this.metrics.totalRequests * 100).toFixed(2) + '%',
requestsPerSecond: (this.metrics.totalRequests / duration).toFixed(2),
avgLatencyMs: this.metrics.averageLatency.toFixed(0)
};
}
}
```
Si tu block rate supera el 10%, necesitas ajustar parámetros. Si tu success rate está bajo 85%, hay algo mal en tu configuración.
Conclusión
El scraping sin proxy rotation es como conducir sin cinturón de seguridad. Funciona hasta que algo sale mal. Y cuando sale mal, sale muy mal.
La diferencia entre un scraper que muere en horas y uno que lleva semanas reside en tres decisiones arquitectónicas: usar el proxy group correcto, mantener sesiones persistentes, y rotar intelligently según el tipo de error.
Implementa el Modelo de 3 Capas hoy. No es opcional si estás scraping en serio.
Tu próximo paso: abre la documentación de Apify Proxy, configura tu primer grupo de proxies, y lanza tu Actor con session pool habilitado. En 20 minutos puedes tener infraestructura que otros developers tardan semanas en construir.
Lee el artículo completo en brianmenagomez.com
Más sobre mis servicios en brianmenagomez.com
Herramientas: Conversor IAE CNAE · Gestorias cerca de ti · Calculadora IRPF

