Apify Web Scraping Tutorial 2026: Deja de Escribir Scrapers que se Rompen a las 2 AM
Tutorial de Apify y Crawlee SDK en español. Aprende a construir scrapers fiables con PlaywrightCrawler, Session Pool, Request Queue y despliegue como Apify Actor.
Has Pasado Dos Semanas Perfeccionando tu Scraper de Playwright. Esta Noche a las 2 AM ha Fallado.
Un cambio de CSS class. Un rate limit que no esperabas. Una sesión que se bloqueó silenciosamente.
*El problema no son tus selectores. Es que estás reinventando infraestructura que ya está resuelta. *
Llevo años construyendo scrapers para proyectos como gestoriascercademi.com o findemergencyplumber.com. Y lo que he aprendido es que el 90% del tiempo de debugging no va de extraer datos — va de gestionar sesiones rotas, proxies caídos, colas de peticiones que se corrompen.
Apify lleva años resolviendo eso. Pero la mayoría lo trata como "esa plataforma de scrapers pre-construidos". No entienden lo que realmente tienen: un runtime serverless con un SDK open-source (Crawlee) que hace que tus scrapers sean *2-3x más fiables* sin que tengas que escribir la plomería.
Este es el tutorial que me hubiera gustado tener hace dos años.
El Error que el 90% de los Desarrolladores Comete al Construir Scrapers
Crees que el scraping es fácil. Tomas Playwright, escribes un `page.goto()`, seleccionas elementos con `page.locator()`, y vuelcas a un CSV.
Funciona en local. Lo despliegas en un cron job de GitHub Actions. Y a las 48 horas el scraper está muerto.
❌ El enfoque naive (lo que hace el 90%):
```javascript
// Scraper que se romperá en produccion
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://ejemplo.com/productos');
const productos = await page.locator('.product-card').allTextContents();
console.log(productos);
// Sin session pool, sin retry, sin proxy, sin request queue
```
Este scraper funciona exactamente hasta que el sitio cambia una clase CSS, te rate-limean, o tu IP se bloquea. *No hay gestión de nada. *
El problema es que piensas que escribir scrapers va de selectores. No va de selectores. Va de state management.
Un scraper de producción necesita:
Session Pool: saber qué sesiones están bloqueadas y rotarlas automáticamente
Retry Logic: reintentar con una sesión, proxy y fingerprint diferente
Request Queue: deduplicar URLs, priorizar rutas, persistir estado si crashea
Proxy Rotation: cambiar de IP cuando te baneen
Error Recovery: no morir en el primer 403
Todo eso es plomería. Y Apify + Crawlee te lo dan gratis.
Lo que Crawlee SDK Hace por Ti (y No Sabías)
Crawlee es el SDK open-source de Apify. Licencia MIT. Funciona en local, en cualquier cloud, en CI/CD. No necesitas la plataforma de Apify para usar Crawlee.
Pero cuando juntas Crawlee con Apify Actors, obtienes algo que la mayoría de desarrolladores no construye hasta que ya es demasiado tarde.
✅ El enfoque correcto con Crawlee:
```javascript
import { PlaywrightCrawler } from 'crawlee';
const crawler = new PlaywrightCrawler({
// Anti-bloqueo automatico
useSessionPool: true,
sessionPoolOptions: {
maxPoolSize: 50,
sessionOptions: {
maxUsageCount: 10, // Renueva sesion cada 10 peticiones
},
},
maxRequestRetries: 3,
// Proxy rotation automatica
proxyConfiguration: {
proxyUrls: ['http://proxy1', 'http://proxy2'],
},
async requestHandler({ page, request, enqueueLinks, pushData }) {
// Extrae datos
const titulo = await page.locator('h1').textContent();
const precio = await page.locator('.price').textContent();
// Guarda resultados
await pushData({ url: request.url, titulo, precio });
// Descubre y encola nuevos enlaces automaticamente
await enqueueLinks({
selector: '.pagination a',
});
},
});
await crawler.run(['https://ejemplo.com/productos']);
```
Fíjate en lo que acabas de escribir sin escribir:
1. Session Pool — cada 10 peticiones, Crawlee rota la sesión automáticamente
2. Retry automático — si una petición da 403 o 429, reintenta con nueva sesión + proxy
3. Request Queue — `enqueueLinks()` descubre enlaces, los deduplica y los encola
4. Proxy rotation — si configuras proxies, Crawlee los rota transparentemente
*Eso son semanas de trabajo que te ahorras. *
### El Gran Malentendido: Cheerio vs Playwright
La mayoría elige Playwright porque "es más potente". Y tienen razón — para SPAs, es necesario.
Pero PlaywrightCrawler ejecuta un navegador completo. Eso significa:
10-50x más lento que CheerioCrawler
Mucho más consumo de RAM y CPU
Más caro en cloud (cada instancia de navegador pesa)
El patrón correcto es:
✅ Usa CheerioCrawler como default (solo parsea HTML, sin navegador)
✅ Usa PlaywrightCrawler solo cuando el sitio renderiza JS (React, Vue, Angular)
✅ Usa PlaywrightCrawler para descubrir API endpoints de SPAs, luego cambia a CheerioCrawler contra esas APIs
```javascript
// CheerioCrawler — 10x mas rapido, sin navegador
import { CheerioCrawler } from 'crawlee';
const crawler = new CheerioCrawler({
async requestHandler({ $, request, pushData }) {
const titulo = $('h1').text().trim();
await pushData({ url: request.url, titulo });
},
});
await crawler.run(['https://ejemplo.com']);
```
Para sitios estáticos, esto vuela. *Sin navegador. Sin overhead. Sin sorpresas. *
### La Capa Invisible: Request Queue y Session Pool
Aquí es donde el 95% de los scrapers caseros se rompe.
Request Queue
Cuando scrapeas un sitio con paginación o enlaces internos, necesitas:
Deduplicación: la misma URL no se crawlea dos veces
Priorización: ciertas rutas primero, otras después
Persistencia: si el scraper crashea, la cola se guarda y puede reanudarse
Crawlee te da todo eso con `enqueueLinks()` y `RequestQueue` automático.
```javascript
const crawler = new PlaywrightCrawler({
maxRequestsPerCrawl: 100, // Limite de paginas
async requestHandler({ request, enqueueLinks, pushData }) {
// Encola solo enlaces que coincidan con el patron
await enqueueLinks({
regex: /\/producto\/\d+/,
label: 'PRODUCTO',
});
// Encola paginacion
await enqueueLinks({
selector: 'a.next-page',
label: 'LISTADO',
});
await pushData({ url: request.url });
},
});
```
*Si construyes esto manualmente, te lleva días. Crawlee lo hace en una línea. *
Session Pool
Este es el héroe silencioso de los scrapers de producción.
Crawlee mantiene un pool de sesiones. Cada sesión tiene un ID único, un proxy asociado, y un contador de uso. Cuando una sesión devuelve 403 o 429, Crawlee:
1. Marca esa sesión como bloqueada
2. La elimina del pool
3. Reintenta la petición con una sesión nueva
4. Usa un proxy diferente (si configuraste proxy rotation)
```javascript
const crawler = new PlaywrightCrawler({
useSessionPool: true,
sessionPoolOptions: {
maxPoolSize: 100,
sessionOptions: {
maxUsageCount: 5, // Renueva cada 5 requests
maxErrorCount: 2, // Tras 2 errores, descarta sesion
},
},
// Personaliza cuando una sesion se considera bloqueada
sessionFingerprint: async (session) => {
session.setPuppeteerFingerprint();
return session;
},
});
```
*Esto es lo que separa un scraper de hobby de un scraper de producción. *
### El Framework: El Patrón de 5 Capas para Scrapers de Producción
No te lances a escribir scraping sin estructura. Usa este patrón que he validado en proyectos reales:
Capa 1 — Elección de Crawler
¿El sitio es estático? → `CheerioCrawler`
¿El sitio renderiza JS? → `PlaywrightCrawler`
¿Necesitas velocidad máxima en contenido estático dentro de una SPA? → PlaywrightCrawler para descubrir API endpoints, luego CheerioCrawler contra las APIs
Capa 2 — Anti-Bloqueo
Configura `useSessionPool: true`
Define `maxUsageCount` (renueva sesión cada N peticiones)
Configura `maxRequestRetries: 3` como mínimo
Añade proxies (datacenter para volumen, residential para sitios agresivos)
Capa 3 — Request Queue
Usa `enqueueLinks()` para descubrimiento automático
Define `maxRequestsPerCrawl` para no morir en sitios infinitos
Usa etiquetas (`label`) para diferenciar tipos de página
La cola persiste automáticamente si el scraper crashea
Capa 4 — Extracción
Define `requestHandler` limpio: extrae datos y llama a `pushData()`
No pongas lógica de negocio aquí — solo extracción
Valida datos antes de guardarlos
Capa 5 — Despliegue como Apify Actor
Dockeriza el scraper
Define input/output JSON schema
Despliega en Apify cloud para scheduling automático
Configura webhooks para notificaciones
### Cómo Desplegar tu Scraper como Apify Actor
Este es el paso que transforma un script en un servicio.
```dockerfile
Dockerfile
FROM apify/actor-node:20
COPY package*.json ./
RUN npm install --production
COPY . ./
CMD ["node", "main.js"]
```
Acompaña con un `actor.json`:
```json
{
"actorSpecification": 1,
"name": "mi-scraper",
"title": "Scraper de Productos",
"version": "0.1",
"buildTag": "latest",
"input": {
"type": "object",
"schema": {
"type": "object",
"properties": {
"startUrls": {
"title": "URLs iniciales",
"type": "array",
"items": { "type": "string" }
},
"maxPages": {
"title": "Maximo de paginas",
"type": "integer",
"default": 100
}
},
"required": ["startUrls"]
}
}
}
```
Despliega con:
```bash
Sube el Actor a Apify
apify push
Programalo para que se ejecute cada lunes
apify schedules create --name "weekly-scrape" --cron "0 9 1"
```
*Ahora tu scraper se ejecuta solo. Cada lunes a las 9 AM. Sin que toques nada. *
La Objeción que Vas a Poner (y por Qué No te la Compres)
"No quiero lock-in con Apify."
Crawlee es MIT. Corre en tu máquina, en GitHub Actions, en Railway, en tu VPS. No necesitas Apify cloud para nada. Las ventajas del SDK (Session Pool, Request Queue, retry automático) funcionan sin la plataforma.
"Apify es overkill para scrapers simples."
Vale si tu scraper es un script de una página que ejecutas una vez. Pero el threshold donde Crawlee paga la inversión es sorprendentemente bajo: dos páginas con paginación o un solo sitio con rate limiting. *El tiempo que pierdes debuggeando un scraper bloqueado supera el tiempo de setup. *
"Los navegadores headless son demasiado lentos y caros."
Usa CheerioCrawler como default. PlaywrightCrawler solo cuando toque. Crawlee te deja mezclar ambos en un mismo proyecto.
Lo que te Llevas
La mayoría de los desarrolladores pierde semanas escribiendo plomería que Crawlee ya resuelve. Session management, retry logic, request queues, proxy rotation — todo eso es infraestructura que no deberías construir tú.
El patrón de 5 capas te da una plantilla que funciona. Elige el crawler correcto. Configura anti-bloqueo. Usa la request queue. Extrae limpio. Despliega como Actor.
*El scraping no va de selectores. Va de state management. Y Apify + Crawlee son la mejor forma de no tener que pensarlo. *
La próxima vez que tu scraper falle a las 2 AM, que no sea porque te faltaba un session pool.
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

