Latenode

Ejecución de JavaScript en el contexto de la página con page.evaluate en Puppeteer

Aprenda a utilizar eficazmente el método page.evaluate de Puppeteer para ejecutar JavaScript en el contexto del navegador en tareas de automatización web.

11 min de lectura
Código JavaScript ejecutándose en una página web mediante Puppeteer

page.evaluate() es un método clave de Puppeteer que le permite ejecutar JavaScript directamente en el contexto del navegador. Conecta Node.js y el navegador, lo que permite realizar tareas como la manipulación del DOM, la extracción de datos y la automatización de páginas web dinámicas. Esto es lo que necesita saber:

  • Qué hace: Ejecuta JavaScript en el navegador, como si estuviera usando la consola del navegador.
  • Cómo funciona: Convierte una función en una cadena, la envía al navegador, la ejecuta y devuelve el resultado.
  • Usos principales:
    • Extraer datos de sitios web (por ejemplo, texto, tablas o JSON).
    • Automatizar envíos de formularios e interacciones de usuarios.
    • Gestionar contenido dinámico, como desplazamiento infinito o actualizaciones AJAX.
  • Limitaciones: Las funciones deben poder serializarse en JSON y las variables de Node.js no son accesibles directamente desde el contexto del navegador.

Ejemplo rápido:

const title = await page.evaluate(() => document.title);

Esto recupera el título de la página directamente desde el navegador.

Comparación: contexto de Node.js frente al del navegador

CaracterísticaContexto de Node.jsContexto del navegador
Objetos globalesprocess, requirewindow, document
Ubicación del scriptMáquina localPágina web de destino
Acceso a APIAPI de Node.jsAPI web del navegador

Use page.evaluate() para tareas de automatización precisas y eficientes, especialmente al trabajar con sitios web que dependen en gran medida de JavaScript.

NodeJS : Nodejs/Puppeteer - Cómo usar page.evaluate

Explicación del contexto de la página

Al trabajar con Puppeteer para la automatización web, es fundamental comprender la diferencia entre el contexto de Node.js y el contexto del navegador. Estos dos entornos están aislados y cada uno tiene sus propias reglas para ejecutar código e intercambiar datos.

Comparación entre los contextos de Node.js y del navegador

Puppeteer opera en dos entornos: el contexto de Node.js, donde se ejecuta su script principal, y el contexto del navegador, donde se producen las interacciones con la página web. Son procesos independientes, cada uno con su propia máquina virtual [3].

A continuación, se presenta una comparación rápida de sus principales características:

CaracterísticaContexto de Node.jsContexto del navegador
Objetos globalesprocess, require, __dirnamewindow, document, localStorage
Ubicación del scriptMáquina localPágina web de destino
Ámbito de las variablesÁmbito del script de PuppeteerÁmbito del contexto de la página
Acceso a APIAPI de Node.jsAPI web del navegador
Espacio de memoriaProceso independienteProceso del navegador

Cómo funciona la comunicación entre contextos

El intercambio de datos entre estos contextos implica una serie de pasos y depende en gran medida de la serialización:

  1. La función se convierte en una cadena mediante Function.prototype.toString() [1].
  2. Esta cadena se envía al navegador a través del Chrome DevTools Protocol [1].
  3. El navegador evalúa la función dentro de su entorno.
  4. Los resultados se serializan en JSON y se envían de vuelta al contexto de Node.js [1].

Limitaciones principales: Las funciones en el contexto del navegador no pueden acceder directamente a las variables del ámbito de Node.js. Puppeteer ofrece herramientas específicas para abordar estos desafíos:

  • page.evaluateHandle(): Devuelve referencias a objetos en el contexto del navegador [1].
  • page.exposeFunction(): Permite que el navegador llame a funciones de Node.js [1].
  • evaluateOnNewDocument(): Ejecuta código antes de que se cargue cualquier script de la página [1].

Sin embargo, la serialización JSON puede eliminar ciertas propiedades, especialmente en objetos complejos como los nodos DOM [2]. Para evitar problemas, pase los datos como argumentos de función en lugar de depender de variables de Node.js [3].

Dominar estas técnicas de comunicación le permite usar page.evaluate de forma eficaz para tareas de automatización. A continuación, veremos ejemplos prácticos para observar estos conceptos en acción.

Primeros pasos con page.evaluate

Estructura y parámetros del método

Sintaxis:

await page.evaluate(pageFunction, ...args)
ParámetroTipoDescripción
pageFunctionFunción o cadenaCódigo JavaScript que se ejecutará en el contexto del navegador
argsParámetros opcionalesValores transferidos de Node.js al contexto del navegador
Valor de retornoPromiseSe resuelve con el valor de retorno de la función

La pageFunction puede ser una función o una cadena que contiene código JavaScript. Normalmente, usar una función es mejor para la depuración y la compatibilidad con TypeScript. A continuación se incluyen algunos ejemplos para mostrar cómo funciona.

Ejemplos básicos de código

Ejemplos:

  • Extraer texto del primer <h1> directamente del DOM:
const headingText = await page.evaluate(() => {
    return document.querySelector('h1').textContent;
});
  • Automatizar el envío de un formulario pasando parámetros:
await page.evaluate((username, password) => {
    document.getElementById('username').value = username;
    document.getElementById('password').value = password;
    document.querySelector('#login-form').submit();
}, 'myUsername', 'myPassword');
  • Manipular el DOM añadiendo un nuevo elemento:
await page.evaluate(() => {
    const div = document.createElement('div');
    div.textContent = 'Added by Puppeteer';
    document.body.appendChild(div);
    return div.textContent;
});

Notas clave para el desarrollo

  • Las funciones se ejecutan de forma aislada de su código de Node.js.
  • Los argumentos transferidos a la función deben ser serializables en JSON.
  • Los valores devueltos se envuelven automáticamente en una Promise.
  • La gestión de objetos complejos, como nodos DOM, requiere especial cuidado.

Consejo de depuración: Use la siguiente configuración para activar la depuración durante el desarrollo:

const browser = await puppeteer.launch({
    headless: false,
    slowMo: 100 // Adds a 100ms delay to each operation
});

A continuación, veremos técnicas para intercambiar datos entre los contextos de Node.js y del navegador.

Intercambio de datos entre contextos

Parámetros de entrada

Al transferir datos con page.evaluate, limite los argumentos de entrada a valores serializables en JSON.

A continuación se muestra un desglose rápido de los tipos de parámetros admitidos:

Tipo de parámetro¿Compatible?Ejemplo
Primitivos✓ Totalmente'text', 42, true
Matrices/objetos✓ Compatibles con JSON{ key: 'value' }, [1, 2, 3]
Funciones✗ No directamenteUse page.exposeFunction
Elementos DOM✓ Mediante JSHandleUse page.evaluateHandle

Ahora, veamos cómo se devuelven estos valores desde el contexto del navegador.

Gestión de la salida

Al usar page.evaluate, los valores devueltos se serializan automáticamente en JSON. Así es como funciona:

// Returning a simple value
const pageTitle = await page.evaluate(() => document.title);

// Returning a complex object
const metrics = await page.evaluate(() => ({
    viewport: window.innerWidth,
    scrollHeight: document.body.scrollHeight,
    timestamp: Date.now()
}));

"Como regla general, si el valor de retorno de la función proporcionada es más complejo que un objeto JSON (por ejemplo, la mayoría de las clases), es probable que evaluate devuelva algún valor truncado (o {}). Esto se debe a que no devolvemos el valor de retorno real, sino una versión deserializada como resultado de transferir el valor de retorno a Puppeteer mediante un protocolo." [1]

Una vez recuperada la salida, puede encontrarse con desafíos relacionados con la serialización. A continuación se explica cómo resolverlos.

Gestión de problemas de serialización

Algunos casos habituales requieren soluciones específicas:

  • Trabajar con elementos DOM
const bodyHandle = await page.$('body');
const html = await page.evaluate(body => body.innerHTML, bodyHandle);
await bodyHandle.dispose(); // Always clean up to avoid memory leaks
  • Usar funciones de Node.js
await page.exposeFunction('md5', text =>
    crypto.createHash('md5').update(text).digest('hex')
);

const hash = await page.evaluate(async () => {
    return await window.md5('test-string');
});
  • Ajustar la configuración del transpilador

Si trabaja con TypeScript, asegúrese de que su transpilador esté configurado correctamente:

// tsconfig.json
{
    "compilerOptions": {
        "target": "es2018"
    }
}

Estas estrategias le ayudarán a gestionar eficazmente el intercambio de datos en distintos contextos.

sbb-itb-23997f1

Ejemplos prácticos

A continuación se muestra cómo puede usar page.evaluate en situaciones reales, con fragmentos de código prácticos.

Extracción de datos

Ejemplo: extracción de detalles de productos

Este script recopila datos como el título, el precio, la valoración y el estado de existencias a partir de las tarjetas de productos de una página web:

const productData = await page.evaluate(() => {
  const products = Array.from(document.querySelectorAll('.product-card'));
  return products.map(product => ({
    title: product.querySelector('.title').textContent.trim(),
    price: product.querySelector('.price').textContent.trim(),
    rating: parseFloat(product.querySelector('.rating').dataset.value),
    inStock: product.querySelector('.stock').textContent.includes('Available')
  }));
});

Ejemplo: extracción de datos de tablas

Este enfoque recupera datos de una tabla recorriendo sus filas y columnas:

const tableData = await page.evaluate(() => {
  const rows = Array.from(document.querySelectorAll('table tr'));
  return rows.map(row => {
    const columns = row.querySelectorAll('td');
    return Array.from(columns, column => column.innerText);
  });
});

Automatización de formularios

Automatización básica de formularios

A continuación se explica cómo completar campos de formulario, activar eventos y enviar el formulario:

await page.evaluate(() => {
  // Fill form fields
  document.querySelector('#username').value = 'testuser';
  document.querySelector('#password').value = 'secretpass';

  // Trigger input events for dynamic forms
  const event = new Event('input', { bubbles: true });
  document.querySelector('#username').dispatchEvent(event);

  // Submit form
  document.querySelector('form').submit();
});

Gestión de formularios complejos

Para tareas como seleccionar opciones de listas desplegables o marcar botones de opción:

await page.evaluate(() => {
  // Select dropdown option
  const select = document.querySelector('#country');
  select.value = 'US';
  select.dispatchEvent(new Event('change', { bubbles: true }));

  // Check radio button
  const radio = document.querySelector('input[value="express"]');
  radio.checked = true;
  radio.dispatchEvent(new Event('change', { bubbles: true }));
});

Gestión de elementos dinámicos

Ejemplo: desplazamiento infinito

Este script recorre una página hasta recopilar al menos 100 elementos:

const items = await page.evaluate(async () => {
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
  const items = new Set();

  while (items.size < 100) {
    // Scroll to bottom
    window.scrollTo(0, document.body.scrollHeight);

    // Wait for new content
    await delay(1000);

    // Collect items
    document.querySelectorAll('.item').forEach(item => 
      items.add(item.textContent.trim())
    );
  }

  return Array.from(items);
});

Ejemplo: gestión de contenido AJAX

Para cargar más contenido de forma dinámica, este script hace clic en un botón "Cargar más" y espera a que aparezcan nuevos elementos:

await page.evaluate(async () => {
  // Click load more button
  document.querySelector('#loadMore').click();

  // Wait for content update
  await new Promise(resolve => {
    const observer = new MutationObserver((mutations, obs) => {
      if (document.querySelectorAll('.item').length > 10) {
        obs.disconnect();
        resolve();
      }
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true
    });
  });
});

Estos ejemplos muestran cómo gestionar diversos casos, como la extracción de datos, la automatización de formularios y el contenido dinámico. Puede realizar ajustes en función de la estructura y el comportamiento específicos de la página web con la que esté trabajando.

Uso de page.evaluate en Latenode

Latenode integra las funciones principales de Puppeteer en sus flujos de automatización, lo que facilita la ejecución de JavaScript directamente en el navegador. Con page.evaluate, los usuarios pueden manipular el DOM y extraer datos de forma eficiente. Este enfoque permite integrar sin fricciones una gestión avanzada de datos y operaciones DOM en el entorno de automatización de Latenode.

Scripts de navegador en Latenode

El módulo de automatización del navegador de Latenode utiliza page.evaluate para gestionar desde tareas sencillas del DOM hasta ejecuciones más complejas de JavaScript. Así funciona en distintos casos:

// Basic DOM interaction
await page.evaluate(() => {
  const loginButton = document.querySelector('#login');
  loginButton.click();

  // Trigger a custom event
  loginButton.dispatchEvent(new Event('customClick'));
});

// Processing data with exposed functions
await page.exposeFunction('processData', async (data) => {
  // Process data in Node.js context
  return transformedData;
});

await page.evaluate(async () => {
  const rawData = document.querySelector('#data').textContent;
  const processed = await window.processData(rawData);
  return processed;
});

Latenode también mantiene un registro del historial de ejecución, lo que facilita la depuración de scripts.

Ejemplos de automatización

Latenode está bien preparado para gestionar contenido dinámico y tareas de automatización complejas. A continuación se muestra un ejemplo de procesamiento de contenido dinámico en una página:

const extractProductData = await page.evaluate(async () => {
  const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

  // Wait for dynamic content to load
  while (!document.querySelector('.product-grid')) {
    await delay(100);
  }

  return Array.from(document.querySelectorAll('.product'))
    .map(product => ({
      name: product.querySelector('.name').textContent,
      price: product.querySelector('.price').textContent,
      availability: product.querySelector('.stock').dataset.status
    }));
});

Para operaciones más avanzadas, page.exposeFunction permite una interacción fluida entre Node.js y el navegador:

await page.exposeFunction('md5', text =>
  crypto.createHash('md5').update(text).digest('hex')
);

const processedData = await page.evaluate(async () => {
  const sensitiveData = document.querySelector('#secure-data').value;
  return await window.md5(sensitiveData);
});

Para mantener referencias a elementos DOM entre pasos, Latenode utiliza page.evaluateHandle:

const elementHandle = await page.evaluateHandle(() => {
  return document.querySelector('.dynamic-content');
});

await page.evaluate(element => {
  element.scrollIntoView();
}, elementHandle);

Estas técnicas garantizan que Latenode pueda gestionar contenido dinámico de forma eficaz y mantener un rendimiento fiable. Para los usuarios del plan Prime, la plataforma admite hasta 1,5 millones de ejecuciones de flujos cada mes, lo que proporciona amplias capacidades de automatización.

Guía de resolución de errores

Al trabajar con page.evaluate en la automatización de navegadores, puede encontrar diversos problemas. A continuación se presentan soluciones prácticas para resolverlos y garantizar una ejecución más fluida.

Corrección de errores de contexto

Configure correctamente sus ajustes de TypeScript para evitar problemas causados por la transpilación. Por ejemplo:

// Use direct, non-transpiled functions
await page.evaluate(() => {
  document.querySelector('#button').click();
});

await page.evaluate(`(async () => {
  document.querySelector('#button').click();
})()`);

Evite devolver elementos DOM directamente desde page.evaluate. En su lugar, use ElementHandle para gestionarlos mejor:

// Incorrect: Returning a DOM element
const element = await page.evaluate(() => {
  return document.querySelector('.dynamic-element');
});

// Correct: Using ElementHandle
const element = await page.evaluateHandle(() => {
  return document.querySelector('.dynamic-element');
});

Resolución de problemas de temporización

Los scripts pueden ejecutarse antes de que la página se haya cargado por completo, lo que genera errores de temporización. Use estas estrategias para gestionar estos casos:

// Wait for navigation after an action
await Promise.all([
  page.waitForNavigation(),
  page.click('#submit-button')
]);

// Wait for a specific condition
await page.waitForFunction(() => {
  const element = document.querySelector('.lazy-loaded');
  return element && element.dataset.loaded === 'true';
}, { timeout: 5000 });

Para sitios web dinámicos, adopte mecanismos de espera más específicos:

// Wait for specific network requests
await page.waitForResponse(
  response => response.url().includes('/api/data')
);

// Ensure elements are both present and visible
await page.waitForSelector('.dynamic-content', {
  visible: true,
  timeout: 3000
});

Gestión de referencias DOM

Para evitar fugas de memoria, gestione cuidadosamente las referencias DOM. A continuación se muestra cómo hacerlo:

// Use and dispose ElementHandles
const handle = await page.evaluateHandle(() => {
  return document.querySelector('.temporary-element');
});
await handle.evaluate(element => {
  // Perform operations
});
await handle.dispose(); // Dispose of handle after use

Al trabajar con varios elementos, transfiera datos de forma segura entre contextos:

// Extract data from the DOM
const selector = '.product-price';
const price = await page.evaluate((sel) => {
  const element = document.querySelector(sel);
  return element ? element.textContent.trim() : null;
}, selector);

Para los detectores de eventos, asegúrese de realizar una limpieza adecuada para evitar controladores persistentes:

await page.evaluate(() => {
  const handler = () => console.log('clicked');
  const button = document.querySelector('#button');
  button.addEventListener('click', handler);

  // Store cleanup references
  window._cleanupHandlers = window._cleanupHandlers || [];
  window._cleanupHandlers.push(() => {
    button.removeEventListener('click', handler);
  });
});

Directrices de implementación

Para obtener los mejores resultados con page.evaluate, debe centrarse en mejorar el rendimiento, reducir los cambios de contexto innecesarios y garantizar la seguridad. Así puede optimizar sus flujos de automatización del navegador.

Optimización del rendimiento

Ejecutar código de forma eficiente dentro del contexto de la página ahorra tiempo y recursos del sistema. A continuación se presentan algunas técnicas para acelerar sus scripts:

// Block unnecessary resources like images and stylesheets
await page.setRequestInterception(true);
page.on('request', request => {
  if (['image', 'stylesheet'].includes(request.resourceType())) {
    request.abort();
  } else {
    request.continue();
  }
});

// Batch operations to reduce overhead
await page.evaluate(() => {
  const results = [];
  document.querySelectorAll('.product-item').forEach(item => {
    results.push({
      title: item.querySelector('.title').textContent,
      price: item.querySelector('.price').textContent,
      stock: item.querySelector('.stock').dataset.value
    });
  });
  return results;
});

Elegir los selectores adecuados también desempeña un papel importante en el rendimiento:

Tipo de selectorVelocidadEjemplo
IDMás rápido#main-content
ClaseRápido.product-item
EtiquetaModeradodiv > span
XPath complejoMás lento//div[@class='wrapper']//span

Gestión de cambios de contexto

Cambiar de contexto entre Node.js y el entorno del navegador puede ralentizar el proceso. A continuación se explica cómo reducirlo al mínimo:

// Example of inefficient context switching
for (const item of items) {
  await page.evaluate((i) => {
    document.querySelector(`#item-${i}`).click();
  }, item);
}

// Better: Batch operations in a single context switch
await page.evaluate((itemsList) => {
  itemsList.forEach(i => {
    document.querySelector(`#item-${i}`).click();
  });
}, items);

Si necesita procesar datos en Node.js y transferirlos de vuelta al navegador, exponga funciones en lugar de cambiar de contexto repetidamente:

await page.exposeFunction('processData', async (data) => {
  // Process data in Node.js
  return transformedData;
});

await page.evaluate(async () => {
  const result = await window.processData(documentData);
  // Use the processed data in the browser
});

Directrices de seguridad

Una vez optimizados el rendimiento y los cambios de contexto, céntrese en mantener la seguridad de sus scripts. Estas son algunas prácticas recomendadas:

// Always sanitize inputs before using them
const sanitizedInput = sanitizeHtml(userInput);
await page.evaluate((input) => {
  document.querySelector('#search').value = input;
}, sanitizedInput);

// Use error handling for critical operations
try {
  await page.evaluate(() => {
    if (!window.__securityCheck) {
      throw new Error('Security check failed');
    }
    // Continue with the operation
  });
} catch (error) {
  console.error('Security violation:', error);
}

Para los flujos de Latenode, tenga en cuenta estos consejos adicionales:

  • Use userDataDir para almacenar recursos en caché y mejorar el rendimiento entre sesiones.
  • Cierre las páginas y las instancias del navegador que no utilice para ahorrar memoria.
  • Gestione las capturas de pantalla con búferes en lugar de depender de operaciones del sistema de archivos.
  • Implemente una gestión sólida de errores y comprobaciones de seguridad exhaustivas.

Resumen

Revisión de puntos clave

El método page.evaluate conecta los contextos de Node.js y del navegador enviando una función de JavaScript convertida en cadena para ejecutarse en el navegador. Esta función opera de forma independiente del entorno de Node.js, por lo que debe gestionar cuidadosamente la transferencia de datos.

A continuación se muestra un ejemplo habitual de extracción de datos:

const data = await page.evaluate(async () => {
  const results = document.querySelectorAll('.data-item');
  return Array.from(results, item => ({
    id: item.dataset.id,
    value: item.textContent.trim()
  }));
});

Aspectos que debe tener en cuenta:

  • Los argumentos deben poder serializarse en JSON.
  • Los valores devueltos se deserializan automáticamente.
  • Las API del navegador solo están disponibles dentro del contexto de evaluate.
  • Las variables de Node.js no son accesibles desde el contexto del navegador.

Estos fundamentos sientan las bases para usar Puppeteer de forma eficaz. Las herramientas adicionales pueden optimizar aún más sus tareas de automatización.

Herramientas adicionales de Puppeteer

Puppeteer ofrece varias herramientas para ampliar las capacidades de page.evaluate:

HerramientaPropósitoMejor caso de uso
page.evaluateHandleDevuelve referencias a objetosInteractuar directamente con elementos DOM
page.exposeFunctionHace que las funciones de Node.js se puedan usar en el navegadorGestionar lógica compleja del lado del servidor
page.evaluateOnNewDocumentEjecuta scripts antes de que se cargue una páginaPreparar el entorno del navegador con antelación

Por ejemplo, exponer funciones de Node.js al navegador puede simplificar el procesamiento avanzado de datos en flujos como los de Latenode. Aunque page.evaluate funciona bien para gestionar tipos primitivos y objetos serializables en JSON, page.evaluateHandle es esencial para trabajar con objetos complejos del navegador que no se pueden serializar.

References

FAQ

Frequently Asked Questions

page.evaluate() ejecuta JavaScript dentro del contexto del navegador, serializa la función en la página mediante Chrome DevTools Protocol, la ejecuta sobre el DOM y devuelve resultados serializables como JSON a su script de Node.js.

¿Te resultó útil? Compártelo →

Escrito por

Vasiliy Datsenko

Jefe de Soporte al Cliente

Vasiliy Datsenko es Jefe de Soporte al Cliente en Latenode y un escritor de automatización centrado en productos. Su trabajo conecta las conversaciones con los clientes, la investigación sobre automatización de flujos de trabajo, los casos de uso de IA y la educación práctica sobre productos para equipos que intentan automatizar procesos comerciales reales.

Perfil del autor →

Verificado por

Oleg Zankov

CEO Latenode, No-code Expert

Con una ética arraigada en la innovación, la resolución de problemas y la experiencia de usuario, me enfoco en capacitar a los equipos para crear integraciones personalizadas y automatizar flujos de trabajo con facilidad y eficiencia. Trayendo una gran experiencia en desarrollo empresarial, emprendimiento tecnológico y desarrollo de software, reconocí la necesidad de una solución de integración más accesible, escalable y adaptable. Así nació Latenode.com. Con nuestra plataforma, las empresas pueden aprovechar el poder de la tecnología sin necesidad de conocimientos extensos de codificación. Apasionado por fomentar un futuro donde la tecnología nos sirva, y no al revés, mi misión es simplificar procesos complejos. Creo en democratizar la tecnología y equipar a los equipos con las herramientas para innovar, crecer y tener éxito en un mundo cada vez más digital.

Perfil del autor →

Seguir leyendo