Descarga de archivos con Puppeteer: Una guía completa para desarrolladores de Node.js
Senior Web Scraping Engineer
Resumen:
- Las descargas en Chrome sin cabeza son un problema del CDP, no un problema de la API de Puppeteer. Puppeteer no tiene un
page.download()de primer nivel; controlas directamente el Protocolo de Herramientas para Desarrolladores de Chrome conBrowser.setDownloadBehaviory escuchasBrowser.downloadProgress. allowAndNamemáseventsEnabledes la combinación que realmente informa sobre la finalización. Configurar el comportamiento solo silencia el diálogo de "guardar archivo"; activar eventos es lo que te permiteawaitcontar los bytes terminados en lugar de adivinar con una pausa fija.- En un navegador en la nube, el archivo se queda del lado del servidor — así que lo recuperas a través de la página. Un
fetch()de mismo origen dentro depage.evaluate()devuelve el archivo como base64, que luego decodificas y escribes en el disco local. Sin máquina con interfaz gráfica, ni volumen compartido. - Los anti-bots y las restricciones geográficas también se aplican a las descargas. Muchos archivos restringidos solo se sirven a una IP residencial en el país adecuado; fijar la salida con
proxyCountryes parte de hacer que la descarga tenga éxito. - Todo el flujo se ejecuta en Scrapeless Scraping Browser con una sola llamada a
Puppeteer.connect(). El navegador en la nube crea la sesión, aplica la huella dactilar de anti-detección y le entrega a Puppeteer un punto de conexión WebSocket estándar; tu código de descarga es puro CDP a partir de ahí. - Gratis para comenzar. Nuevas cuentas de Scrapeless incluyen tiempo de ejecución gratuito de Scraping Browser — regístrate en app.scrapeless.com.
Introducción: por qué "descargar un archivo con Puppeteer" es más difícil de lo que parece
Puppeteer le da a los desarrolladores de Node.js control total sobre una página de Chrome — haciendo clic, escribiendo, leyendo el DOM. Las descargas de archivos son la única tarea común para la que no tiene un método dedicado. No hay un await page.download(selector). Hacer clic en un enlace de descarga en una sesión sin cabeza por defecto hace una de dos cosas poco útiles: la navegación es ignorada, o Chrome bloquea la transferencia porque no se ha configurado un directorio de descargas.
El verdadero control se encuentra un nivel más abajo, en el Protocolo de Herramientas para Desarrolladores de Chrome (CDP). Browser.setDownloadBehavior le dice a Chrome dónde van los archivos y si debe permitirlos; Browser.downloadWillBegin y Browser.downloadProgress informan del ciclo de vida para que puedas esperar el momento exacto en que un archivo termina en lugar de dormir y esperar.
Ejecuta esto en tu propia máquina y aún así tienes dos problemas no relacionados con el CDP: el sitio objetivo marca a Chrome sin cabeza como un bot, y los archivos restringidos a menudo rechazan cualquier cosa que no sea una IP residencial en la región esperada. Esta guía ejecuta todo el flujo sobre Scrapeless Scraping Browser — un navegador en la nube anti-detección que crea la sesión, aplica una salida residencial y le entrega a Puppeteer un punto de conexión WebSocket normal. El código de descarga se mantiene como puro CDP; los problemas de detección y geográficos son manejados por el tiempo de ejecución. Hay un giro que un navegador en la nube añade: los archivos se descargan del lado del servidor — y la segunda mitad de esta guía muestra el patrón para recuperar esos bytes a tu disco local.
Lo Que Puedes Hacer Con Esto
- Obtener informes, exportaciones y declaraciones detrás de un inicio de sesión — PDFs de facturas, exportaciones CSV, archivos de salas de datos — una vez que la sesión esté autenticada.
- Capturar archivos generados que una página construye del lado del cliente a partir de un blob (exportaciones de gráficos, botones de "descargar CSV") y nunca existen como una URL estática.
- Obtener documentos restringidos que solo se sirven a una IP residencial en un país específico, fijando la salida antes de la solicitud.
- Esperar de manera determinista a que termine una transferencia usando
downloadProgressen lugar de una demora fija y frágil. - Escalar el mismo script a través de muchos archivos o cuentas sin tener que utilizar máquinas con interfaz gráfica — el navegador es remoto.
Por Qué Scrapeless Scraping Browser
Scrapeless Scraping Browser es un navegador en la nube personalizable y anti-detección diseñado para rastreadores web y agentes de IA. Para descargas de Puppeteer específicamente, trae:
- Una conexión estándar de Puppeteer —
Puppeteer.connect()devuelve un objetoBrowsercomún, por lo que cada llamada CDP que ya conoces funciona sin cambios. - Proxies residenciales en más de 195 países — fija
proxyCountrypara que los archivos restringidos se sirvan a una IP en la que confían. - Huella dactilar de anti-detección del lado de la nube — la sesión se parece a un navegador real, por lo que el enlace de descarga se ofrece en lugar de ser ocultado detrás de un muro de bots.
- Persistencia de sesión — mantener cookies y estado de autenticación a lo largo de las navegaciones, lo que hace posibles las descargas con inicio de sesión.
- Chromium desarrollado internamente — superficie CDP completa, por lo que
Browser.setDownloadBehaviory los eventos de descarga se comportan exactamente como se documenta.
Obtén tu clave API en el plan gratuito en app.scrapeless.com.
Requisitos Previos
- Node.js 18 o superior
- Una cuenta de Scrapeless y clave API — regístrate en app.scrapeless.com
- Familiaridad básica con Puppeteer y
async/await
Instalación
1. Añadir el SDK y Puppeteer
El SDK de Scrapeless acuña la sesión en la nube y conecta Puppeteer a ella. Necesitas puppeteer-core (el cliente de protocolo sin un Chromium empaquetado — el navegador es remoto):
bash
npm install @scrapeless-ai/sdk puppeteer-core
2. Configura tu clave API
Léela del entorno; nunca la codifiques directamente:
bash
export SCRAPELESS_API_KEY="tu_token_api_aquí"
Configurar: conectar Puppeteer al navegador en la nube
Puppeteer.connect() crea una sesión de Scrapeless y devuelve un Browser estándar de Puppeteer. Fija el país del proxy aquí para que cada solicitud — incluido la descarga — salga de la región que desees:
javascript
import { Puppeteer } from '@scrapeless-ai/sdk';
const browser = await Puppeteer.connect({
apiKey: process.env.SCRAPELESS_API_KEY,
sessionName: 'puppeteer-downloads',
proxyCountry: 'US',
sessionTTL: 300, // segundos que la sesión permanece activa
});
const page = await browser.newPage();
Desde aquí, browser y page son objetos estándar de Puppeteer. Todo lo que sigue es estándar CDP.
Implementación básica: establecer el comportamiento de descarga y esperar a la finalización
Hay dos llamadas CDP que importan y un evento que esperas:
Browser.setDownloadBehaviorconbehavior: 'allowAndName'permite la descarga y nombra el archivo por su GUID del lado del servidor.eventsEnabled: truees la parte que la mayoría de las guías omiten — sin esto, no se disparan eventos de progreso y vuelves a estar en espera.Browser.downloadWillBeginte informa que una transferencia ha comenzado, con elsuggestedFilenamey unguid.Browser.downloadProgressinformainProgress→completed(ocanceled), conreceivedBytesytotalBytes.
javascript
const cdp = await page.createCDPSession();
await cdp.send('Browser.setDownloadBehavior', {
behavior: 'allowAndName',
downloadPath: '/tmp/downloads',
eventsEnabled: true,
});
// Resuelve solo cuando la transferencia realmente finaliza
const downloadComplete = new Promise((resolve) => {
let meta = {};
cdp.on('Browser.downloadWillBegin', (e) => {
meta = { filename: e.suggestedFilename, guid: e.guid };
});
cdp.on('Browser.downloadProgress', (e) => {
if (e.state === 'completed') {
resolve({ ...meta, totalBytes: e.totalBytes });
}
});
});
Ahora inicia la descarga — haciendo clic en un botón, un enlace, o, como aquí, un archivo que la página genera del lado del cliente — y espera la promesa:
javascript
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
// Un botón de "descargar CSV" que construye el archivo en el navegador a partir de un blob
await page.evaluate(() => {
const blob = new Blob(['col1,col2\n1,2\n3,4\n'], { type: 'text/csv' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'report.csv';
document.body.appendChild(a);
a.click();
});
const result = await downloadComplete;
console.log(result);
// { filename: 'report.csv', guid: '845c9455-…', totalBytes: 18 }
La promesa se resuelve en el instante en que llega state === 'completed' — sin dormir fijo, y obtienes la cuenta real de bytes de vuelta.
Obtén tu clave API en el plan gratuito: app.scrapeless.com
El giro en la nube: obtener los bytes en tu disco local
En una ejecución local con interfaz gráfica, allowAndName escribe el archivo en tu downloadPath y lo lees desde el disco. En un navegador en la nube, el archivo aterriza en la sesión remota, no en tu máquina — por lo que downloadPath es del lado del servidor y tu local /tmp/downloads permanece vacío.
El patrón confiable es obtener los bytes del archivo dentro del contexto de la página y devolverlos como base64, luego decodificarlos y escribirlos localmente. La única regla: fetch() está sujeto a la política de mismo origen, así que navega primero a la propia origen del archivo, luego obtén el archivo:
javascript
import { writeFileSync } from 'node:fs';
const fileUrl = 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf';
// Aterriza en el origen del archivo para que fetch() sea de mismo origen (sin bloqueo CORS)
await page.goto(new URL(fileUrl).origin, { waitUntil: 'domcontentloaded' });
const out = await page.evaluate(async (url) => {
const res = await fetch(url);
const bytes = new Uint8Array(await res.arrayBuffer());
let binary = '';
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
return {
status: res.status,
type: res.headers.get('content-type'),
b64: btoa(binary),
};
}, fileUrl);
const buffer = Buffer.from(out.b64, 'base64');
writeFileSync('./dummy.pdf', buffer);
console.log({ status: out.status, type: out.type, bytes: buffer.length });
// { status: 200, type: 'application/pdf; qs=0.001', bytes: 13264 }
Debido a que la fetch se ejecuta dentro de la página autenticada, lleva las cookies de sesión — así que los archivos detrás de un inicio de sesión se descargan exactamente igual que los archivos visibles. El enfoque de eventos CDP y este enfoque de fetch son complementarios: utiliza los eventos para saber cuándo terminó una transferencia activada por un clic; utiliza el fetch en la página cuando ya tienes la URL del archivo y quieres recuperar los bytes localmente.
Patrones avanzados
Fijar la salida para archivos restringidos. Si un documento solo se sirve a una región, establece proxyCountry en el momento de la conexión. La descarga hereda esa IP residencial — sin manipulación de proxy por solicitud.
Autentica y luego descarga. Mantén la misma página (y, por lo tanto, la misma sesión y cookies) para el flujo de inicio de sesión y la descarga. La persistencia de sesión sin scrap mantiene el estado de autenticación activo entre navegaciones, así la fetch en la página anterior ve un contexto de inicio de sesión.
Leer el progreso para archivos grandes. Browser.downloadProgress también se activa con state: 'inProgress' y un receivedBytes creciente. Registra la proporción contra totalBytes para mostrar una barra de progreso real en lugar de un spinner.
Cierra la sesión cuando termines. await browser.close() finaliza la sesión en la nube rápidamente, así no mantienes tiempo de ejecución que no estás utilizando.
Solución de problemas
| Síntoma | Causa | Solución |
|---|---|---|
No se activan eventos downloadProgress |
eventsEnabled no está configurado |
Pasa eventsEnabled: true a Browser.setDownloadBehavior |
| Al hacer clic navega en lugar de descargar | El servidor envía el archivo en línea, no como un adjunto | Usa el patrón de recuperación fetch() en la URL del archivo |
La downloadPath local está vacía |
El archivo está en la sesión remota de la nube, no en tu disco | Recupera los bytes a través de la página (ver "el giro en la nube") |
fetch() lanza un error CORS |
El origen de la página difiere del origen del archivo | page.goto(new URL(fileUrl).origin) antes de buscar |
| El enlace de descarga nunca aparece | El sitio lo ocultó detrás de un muro de bots/geográfico | Fija proxyCountry y deja que la huella digital del navegador en la nube renderice la página real |
Conclusión: descargas como un paso de primera clase en tu pipeline
Descargar un archivo con Puppeteer se reduce a tres movimientos: decirle a Chrome que permita e informe sobre descargas (Browser.setDownloadBehavior con eventsEnabled), esperar el evento de finalización real (Browser.downloadProgress), y — en un navegador en la nube — recuperar los bytes a través de un fetch en la misma origin. Ejecutarlo en Scrapeless Scraping Browser une las dos partes complicadas que no tienen nada que ver con CDP — detección de bots y geo-restricciones — en el tiempo de ejecución, así que el mismo script que funciona en un archivo público también funciona en uno restringido y con sesión iniciada. Para flujos impulsados por formularios que conducen a una descarga, combina esto con la guía de navegador en la nube Scrapling, y consulta la página del producto Scraping Browser y la documentación para obtener toda la superficie CDP. Fija la salida, mantén la sesión activa para archivos autenticados y espera el completed en lugar de un sueño.
¿Listo para construir tu Pipeline de Datos Potenciado por IA?
Únete a nuestra comunidad para reclamar un plan gratuito y conectar con desarrolladores que crean pipelines de descarga y extracción: Discord · Telegram.
Regístrate en app.scrapeless.com para obtener de forma gratuita el tiempo de ejecución de Scraping Browser y adapta los patrones anteriores a los archivos, regiones y inicios de sesión que necesita tu flujo de trabajo. Consulta precios para escalabilidad.
Preguntas Frecuentes
P: ¿Tiene Puppeteer un método de descarga incorporado?
No. No hay page.download(). Configuras las descargas a través del Protocolo de Chrome DevTools con Browser.setDownloadBehavior y las observas con los eventos Browser.downloadWillBegin y Browser.downloadProgress.
P: ¿Necesito un proxy para descargar archivos?
Para archivos públicos, no estrictamente. Para archivos restringidos por región o servidos solo a IPs residenciales, sí — fija proxyCountry en el momento de la conexión para que la transferencia salga de una IP que el sitio confía.
P: ¿Por qué está vacía mi carpeta de descarga local al ejecutar en un navegador en la nube?
Porque el archivo se descarga en la sesión remota, no en tu máquina. Recupera los bytes buscando la URL del archivo dentro de page.evaluate() y decodificando el base64 localmente, como se mostró anteriormente.
P: ¿Cómo descargo un archivo que está detrás de un inicio de sesión?
Autentícate primero en la misma página, luego ejecuta el fetch() en la página — lleva las cookies de sesión, por lo que el archivo se descarga como el usuario autenticado. La persistencia de sesión de Scrapeless mantiene ese estado de autenticación a través de las navegaciones.
P: ¿Cómo espero a que la descarga termine en lugar de adivinar un tiempo de espera?
Escucha Browser.downloadProgress y resuelve cuando state === 'completed'. El evento incluye receivedBytes y totalBytes, por lo que también puedes mostrar un progreso real.
P: ¿Puedo ejecutar esto sin un agente de IA o herramientas adicionales?
Sí. Esto es Puppeteer puro más CDP sobre la sesión de Scrapeless — no se requiere agente. El SDK solo establece la conexión.
En Scrapeless, solo accedemos a datos disponibles públicamente y cumplimos estrictamente con las leyes, regulaciones y políticas de privacidad del sitio web aplicables. El contenido de este blog es sólo para fines de demostración y no implica ninguna actividad ilegal o infractora. No ofrecemos garantías y renunciamos a toda responsabilidad por el uso de la información de este blog o enlaces de terceros. Antes de realizar cualquier actividad de scraping, consulte a su asesor legal y revise los términos de servicio del sitio web de destino u obtenga los permisos necesarios.



