Con el nuevo servicio Cloudflare Browser Rendering se abren más posibilidades de usar Puppeteer en los servidores de Cloudflare y sin mucha configuración de por medio.

Cloudflare Browser Rendering usa Puppeteer. Esta es una librería en JavaScript que ejecuta una instancia de Google Chrome permitiendo realizar varias interacciones, por ejemplo, de un sitio web podrás generar PDFs, tomar capturas de pantalla, obtener datos e incluso modificar el DOM.

Uno de los proyectos de la empresa donde trabajo es tomar captura de pantallas de los resultados de Google (SERP), específicamente donde los resultados de la web de la empresa aparezcan. Así que, ese proyecto cayó en buen momento para probar Cloudflare Browser Rendering.

En este post te enseñaré a cómo sacar una captura de pantalla a la primera página de resultados de Google usando Cloudflare Browser Rendering.

Cloudflare Browser Rendering

Este es un producto de Cloudflare que permite ejecutar Puppeteer en los servidores de Cloudflare.

Funciona bajo la plataforma de Cloudflare Workers y está desarrollado para que los desarrolladores solo se enfoquen en desarrollar sus aplicaciones, así que no necesitas configurar nada relacionado con Puppeteer.

Cloudflare Browser Rendering está disponible para usuarios que pagen el servicio de Workers y el precio que pagarás será de acuerdo al consumo de memoria y CPU de tu aplicación.

Proyecto: Captura de pantalla a la primera página de los resultados de Google

Aunque, por este medio, no puedo compartir el código desarrollado en la empresa donde trabajo, si puedo mostrarte cómo implementar Cloudflare Browser Rendering usando nuestro propio proyecto.

Vamos a desarrollar una aplicación de Cloudflare Workers que tome captura de pantalla a la primera página de los resultados de Google usando Cloudflare Browser Rendering.

La lógica del proyecto es la siguiente:

  1. La aplicación se ejecuta haciendo un HTTP GET.
  2. Agregamos el parámetro keyword al enlace que nos dará nuestro Worker.
  3. Aplicar validaciones necesarias.
  4. Si hay algún error, enviar una respuesta JSON.
  5. Iniciar Puppeteer, entrar a la página de Google con la palabra clave indicada.
  6. Tomar la captura de pantalla.
  7. Mostrar la captura de pantalla al navegador del usuario.

Requisitos

Los siguientes requisitos nos ayudarán a desarrollar nuestra aplicación:

Desarrollo del proyecto

1. Crea un Cloudflare Worker

En si, todo el proyecto es un Cloudflare Worker. Un Worker es un sistema serverless, es decir, ejecutas tu código en un servidor en la nube con cero configuración en la infraestructura.

Ejecuta el siguiente comando y contesta las preguntas que aparecerán en la terminal. El proyecto puedes llamarlo browser-rendering:

npm create cloudflare@latest

Si estás en Windows, ejecuta el comando usando la Windows PowerShell en modo administrador para responder las preguntas en la terminal.

Si te pregunta qué tipo de aplicación deseas crear, escoge la opción “Hello World” Worker. Además, te preguntará si usarás JavaScript o TypeScript. En nuestro caso, usaremos JavaScript.

2. Instala Puppeteer

Dentro de tu proyecto browser-rendering, instala el Puppeteer de Cloudflare. Así es, Cloudflare ha hecho un fork de la librería Puppeteer y le aplicaron algunos cambios para hacerlo funcionar con Workers.

npm install @cloudflare/puppeteer --save-dev

3. Configura tu archivo Wrangler

Un proyecto de Cloudflare Worker siempre tiene su archivo wrangler.toml, este archivo sirve para configurar tu proyecto con los productos de Cloudflare.

El archivo en tu proyecto debe lucir así:

name = "browser-rendering"
main = "src/worker.js"
compatibility_date = "2023-03-14"
compatibility_flags = [ "nodejs_compat" ]
account_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
 
browser = { binding = "MYBROWSER" }

El archivo wrangler.toml está compuesto por el nombre de variables y sus valores. En este archivo todo es importante porque declaramos valores para que nuestro proyecto sea compatible con Node.js, desde donde se ejecuta nuestro proyecto y más.

Ahora, la variable browser contiene el binding con su valor MYBROWSER. Este binding establecerá la conexión entre nuestro Worker con el Chrome ejecutado desde Cloudflare.

El valor del binding puede ser cualquiera de tu preferencia. En este proyecto usaremos MYBROWSER.

Algo que no mencionan al configurar el wrangler.toml, es la variable account_id. Aunque esta variable es opcional, por alguna razón cuando ejecutaba el Worker me pedía que ingresara el account_id. Esta nos permite interactuar directamente con la zona de Cloudflare donde ejecutaremos nuestro Worker.

Puedes ver más configuraciones del archivo wrangler.toml en este enlace.

4. El código

Una vez configurado el archivo wrangler.toml ya podemos empezar a desarrollar nuestra aplicación que tomará capturas de pantalla a un sitio web. El código JavaScript es el siguiente:

import puppeteer from "@cloudflare/puppeteer";
 
/**
* Send error response.
*
* @since 1.0.0
*
* @param {string} message The error message.
* @param {int} status The status code.
* @returns {Response} The response.
*/
function sendError(message, status = 400) {
const json = {
message,
success: false,
};
 
return new Response(JSON.stringify(json), {
status,
headers: { 'content-type': 'application/json;charset=UTF-8' },
});
}
 
/**
* Get the Google URL.
*
* @since 1.0.0
*
* @param {string} keyword The keyword.
* @returns {string} The Google URL.
*/
function getGoogleUrl(keyword) {
const params = {
q: keyword,
gl: 'us',
hl: 'en',
};
 
const urlSearchParams = new URLSearchParams(params);
 
return `https://www.google.com/search?${ urlSearchParams }`;
}
 
export default {
/**
* Fetch the SERP screenshot.
*
* @since 1.0.0
*
* @param {object} request The request.
* @param {object} env The environment variables.
* @returns {Promise<Response>} The response.
*/
async fetch(request, env) {
if (request.method !== 'GET') {
return sendError('Method not allowed.', 405);
}
 
const { searchParams } = new URL(request.url);
let keyword = searchParams.get('keyword');
 
if (!keyword) {
return sendError('You need to specify a keyword.');
}
 
try {
const browser = await puppeteer.launch(env.MYBROWSER);
const page = await browser.newPage();
 
await page.goto(getGoogleUrl(keyword), { waitUntil: 'load' });
 
const screenshot = await page.screenshot();
 
await browser.close();
 
return new Response(screenshot, {
headers: {
'content-type': 'image/png',
}
});
} catch (e) {
return sendError(e.message);
}
}
}

La función sendError la ejecutamos cuando hay algún tipo de error en nuestras validaciones o cuando Puppeteer arrojó algún otro tipo de error. Por otra parte, obtendremos el enlace de Google para buscar nuestra palabra clave con la función getGoogleUrl.

Y dentro de la función fetch se ejecutará la funcionalidad de Puppeteer. Dentro de esta función ejecutamos validaciones al principio:

  1. Solo debemos aceptar peticiones HTTP GET.
  2. La variable keyword debe existir.

Ahora, la parte que ejecuta la funcionalidad de Puppeteer, preferentemente, debe estar encerrado dentro de un try/catch, para que podamos detectar cualquier error lanzado por Puppeteer.

Y acá viene lo más importante, la constante browser contiene la instancia de Chrome, la cual usaremos para abrir nuevas ventanas y cerrar la conexión. Pero, ¿ya notaste que la función puppeteer.launch() tiene un parámetro?

const browser = await puppeteer.launch(env.MYBROWSER);

Dentro de la función puppeteer.launch() ponemos el binding que hemos configurado en nuestro archivo wrangler.toml.

browser = { binding = "MYBROWSER" }

Esto permitirá la comunicación entre nuestro Worker y Chrome. Después de establecer la comunicación, ahora podemos tomar capturas de pantalla y más.

¡No olvides cerrar la conexión del navegador! Aunque si tu instancia (Chrome) no recibe algún comando durante 60 segundos entonces se cierra automáticamente.

Más información en los límites de la plataforma en este enlace.

5. Prueba la aplicación

Cloudflare Wrangler nos provee de un comando para ejecutar y probar nuestra aplicación:

wrangler dev --remote

El comando tiene que ejecutarse dentro de tu proyecto, desde la consola de comandos, con la opción —-remote para que se conecte con los servidores de Cloudflare y ejecute la instancia de Chrome.

Abre el localhost junto con el puerto que te ha dado el output del comando y abrelo en el navegador, por ejemplo:

http://localhost:8787/?keyword=roel%20magdaleno

Verás la imagen en tu navegador en aproximadamente 6 segundos:

Lo que he notado al usar el Cloudflare Browser Rendering es que tarda alrededor de 2 o 3 segundos en abrir la instancia de Chrome y otros 3 segundos para abrir una ventana y realizar la búsqueda.

Si tu objetivo es buscar múltiples palabras claves, entonces agrega un bucle en tu código para que busque en una sola instancia de Chrome.

6. Sube la aplicación a Cloudflare Workers

Ahora, si deseas subir la aplicación a tu cuenta Cloudflare, solo tienes que ejecutar el siguiente comando:

wrangler deploy

En la terminal verás el enlace generado por el comando y luce así:

https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/?keyword=roel%20magdaleno

Este enlace será el que usaremos para tomar las capturas de pantalla en producción.

¡Y listo! Has desarrollado un Cloudflare Worker que toma capturas de pantallas de un sitio web, sin embargo, como ya sabes, también puedes generar archivos PDF. ¿Cómo usarás esta herramienta?

¿Problemas con el Buffer de Node.js?

Este problema ya ha sido solucionado en nuevas versiones del Cloudflare Browser Rendering pero si por alguna razón te sale un error con el Buffer, quiere decir que la librería de Puppeteer no está detectando esa funcionalidad de Node.js.

Por suerte, alguien en el Discord de Cloudflare sugirió aplicar la siguiente solución:

import { Buffer } from 'node:buffer';
import puppeteer from "@cloudflare/puppeteer";
 
globalThis.Buffer = Buffer;

Declaramos el Buffer justo antes de puppeteer y lo registramos en el globalThis de nuestra aplicación. Reiniciamos la aplicación y debe funcionar perfecto.

Referencias

11

Escrito por Roel Magdaleno

Desarrollador Backend y Performance Engineer.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *