Publicado el 4 de junio de 2018 en el blog corporativo sin navegador.¡Nos complace anunciar que recientemente hemos cruzado la línea de
dos millones de sesiones atendidas ! Estos son
millones de capturas
de pantalla generadas, PDF impresos y sitios probados. Hemos hecho casi todo lo que se te ocurre hacer con un navegador sin cabeza.
Aunque es bueno lograr tal hito, en el camino hubo claramente
muchas superposiciones y problemas. Debido a la gran cantidad de tráfico recibido, me gustaría dar un paso atrás y establecer recomendaciones generales para lanzar navegadores sin cabeza (y
titiriteros ) en producción.
Aquí hay algunos consejos.
1. No utilice un navegador sin cabeza en absoluto

Consumo de recursos de Chrome sin cabeza
De ninguna manera, si es posible,
no inicie el navegador en modo sin cabeza . Especialmente en la misma infraestructura que su aplicación (ver arriba). El navegador sin cabeza es impredecible, glotón y se reproduce como el Sr. Misix de Rick y Morty. Casi todo lo que puede hacer un navegador (excepto interpolar y ejecutar JavaScript) se puede hacer usando herramientas simples de Linux. Las bibliotecas Cheerio y otras ofrecen una elegante API Node para recuperar datos con solicitudes HTTP y scraping, si ese es su objetivo.
Por ejemplo, puede tomar una página (suponiendo que sea algún tipo de HTML) y desecharla con comandos simples como estos:
import cheerio from 'cheerio'; import fetch from 'node-fetch'; async function getPrice(url) { const res = await fetch(url); const html = await res.test(); const $ = cheerio.load(html); return $('buy-now.price').text(); } getPrice('https://my-cool-website.com/');
Obviamente, el script no cubre todos los casos de uso, y si lee este artículo, lo más probable es
que tenga
que usar un navegador sin cabeza. Entonces comencemos.
2. No inicie un navegador sin cabeza innecesariamente
Hemos encontrado numerosos usuarios que intentan mantener el navegador funcionando incluso si no está en uso (con conexiones abiertas). Aunque esta puede ser una buena estrategia para acelerar una sesión, se bloqueará en unas pocas horas. Principalmente porque a los navegadores les
gusta almacenar
en caché todo en una fila y gradualmente consumen memoria. Tan pronto como deje de usar el navegador intensivamente, ¡ciérrelo de inmediato!
import puppeteer from 'puppeteer'; async function run() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.example.com/');
En sin navegador, generalmente corregimos este error para los usuarios, siempre configuramos algún tipo de temporizador para la sesión y cerramos el navegador cuando WebSocket está desconectado. Pero si no utiliza nuestro
servicio o la
imagen Docker de respaldo , asegúrese de cerrar
automáticamente el navegador, ya que será desagradable cuando todo caiga en medio de la noche.
3. Tu página de amigo. page.evaluate
Tenga cuidado con los transpiladores como babel o mecanografiado, porque les gusta crear funciones auxiliares y asumir que son accesibles con cierres. Es decir, la devolución de llamada .evaluate puede no funcionar correctamente.
Puppeteer tiene muchos métodos interesantes, como almacenar selectores DOM y otras cosas en un entorno Node. Aunque es muy conveniente, puede dispararse fácilmente en el pie si algo en la página obliga a este nodo DOM a
mutar . Puede que esto no sea tan bueno, pero en realidad es mejor hacer todo el trabajo en el lado
del navegador en el contexto del navegador . Esto generalmente significa cargar
page.evaulate
para todo el trabajo que debe hacerse.
Por ejemplo, en lugar de algo como esto (
tres acciones asíncronas):
const $anchor = await page.$('a.buy-now'); const link = await $anchor.getProperty('href'); await $anchor.click(); return link;
Es mejor hacer esto (una acción asincrónica):
await page.evaluate(() => { const $anchor = document.querySelector('a.buy-now'); const text = $anchor.href; $anchor.click(); });
Otra ventaja de las acciones de ajuste en una llamada de
evaluate
es la portabilidad: este código se puede ejecutar en un navegador para verificar en lugar de intentar reescribir el código de Nodo. Por supuesto, siempre se recomienda
usar un depurador para reducir el tiempo de desarrollo.
Una regla general simple es contar el número de
await
o
then
en el código. Si hay más de uno, entonces probablemente sea mejor ejecutar el código dentro de la
page.evaluate
.
page.evaluate
llamada. La razón es que todas las acciones asíncronas van y vienen entre el Nount Runtime y el navegador, lo que significa una serialización y deserialización JSON constante. Aunque no hay una gran cantidad de análisis (porque todo es compatible con WebSockets), todavía lleva tiempo, que es mejor gastar en otra cosa.
4. Paralelo a los navegadores, no a las páginas web
Entonces, nos dimos cuenta de que iniciar un navegador no es bueno y que debemos hacerlo solo en caso de emergencia. El siguiente consejo es ejecutar solo una sesión por navegador. Aunque en realidad es posible ahorrar recursos paralelizando el trabajo a través de las
pages
, pero si una página cae, puede bloquear todo el navegador. Además, no se garantiza que cada página esté perfectamente limpia (las cookies y el almacenamiento pueden convertirse en un dolor de cabeza,
como vemos ).
En cambio:
import puppeteer from 'puppeteer';
Mejor haz esto:
import puppeteer from 'puppeteer'; const runJob = async (url) {
Cada nueva instancia del navegador obtiene un
--user-data-dir
limpio (a
menos que se indique lo contrario ). Es decir, se procesa completamente como una nueva sesión nueva. Si Chrome falla por algún motivo, no arrastrará otras sesiones con él.
5. Limitación de colas y concurrencia
Una de las características principales de sin navegador es la capacidad de limitar claramente la paralelización y la cola. Por lo tanto, las aplicaciones cliente solo ejecutan
puppeteer.connect
, pero no piensan en la implementación de la cola. Esto evita una gran cantidad de problemas, principalmente con instancias concurrentes de Chrome que engullen todos los recursos disponibles de su aplicación.
La mejor y más fácil es tomar nuestra imagen Docker y ejecutarla con los parámetros necesarios:
# Pull in Puppeteer@1.4.0 support $ docker pull browserless/chrome:release-puppeteer-1.4.0 $ docker run -e "MAX_CONCURRENT_SESSIONS=10" browserless/chrome:release-puppeteer-1.4.0
Esto limita el número de solicitudes simultáneas a diez (incluidas las sesiones de depuración y más). La cola está configurada por la variable
MAX_QUEUE_LENGTH
. Por lo general, puede realizar aproximadamente 10 solicitudes simultáneas por gigabyte de memoria. El porcentaje de utilización de la CPU puede variar para diferentes tareas, pero básicamente necesitará mucha y mucha RAM.
6. No te olvides de page.waitForNavigation
Uno de los problemas más comunes que hemos encontrado son las acciones que comienzan a cargar páginas con la posterior terminación repentina de los scripts. Esto se debe a que las acciones que desencadenan la
pageload
menudo provocan la deglución del trabajo posterior. Para solucionar el problema, generalmente debe llamar a la acción de carga de la página e inmediatamente después de esperar la carga.
Por ejemplo, tal
console.log
no funciona en un solo lugar (
vea la demostración ):
await page.goto('https://example.com'); await page.click('a'); const title = await page.title(); console.log(title);
Pero
funciona en otro (
ver demo ).
await page.goto('https://example.com'); page.click('a'); await page.waitForNavigation(); const title = await page.title(); console.log(title);
Puede leer más sobre waitForNavigation
aquí . Esta función tiene aproximadamente los mismos parámetros de interfaz que
page.goto
, pero solo con la parte "esperar".
7. Use Docker para todo lo que necesita.
Chrome necesita muchas dependencias para funcionar correctamente. Realmente mucho Incluso después de instalar todo, debe preocuparse por cosas como las fuentes y los procesos fantasmas. Por lo tanto, es ideal usar algún tipo de contenedor para poner todo allí. Docker está diseñado específicamente para esta tarea, ya que puede limitar la cantidad de recursos disponibles y aislarla. Si desea crear su propio
Dockerfile
, marque a continuación todas las dependencias necesarias:
# Dependencies needed for packages downstream RUN apt-get update && apt-get install -y \ unzip \ fontconfig \ locales \ gconf-service \ libasound2 \ libatk1.0-0 \ libc6 \ libcairo2 \ libcups2 \ libdbus-1-3 \ libexpat1 \ libfontconfig1 \ libgcc1 \ libgconf-2-4 \ libgdk-pixbuf2.0-0 \ libglib2.0-0 \ libgtk-3-0 \ libnspr4 \ libpango-1.0-0 \ libpangocairo-1.0-0 \ libstdc++6 \ libx11-6 \ libx11-xcb1 \ libxcb1 \ libxcomposite1 \ libxcursor1 \ libxdamage1 \ libxext6 \ libxfixes3 \ libxi6 \ libxrandr2 \ libxrender1 \ libxss1 \ libxtst6 \ ca-certificates \ fonts-liberation \ libappindicator1 \ libnss3 \ lsb-release \ xdg-utils \ wget
Y para evitar procesos zombies (algo común en Chrome), es mejor usar algo como
dumb-init para ejecutarse correctamente:
ADD https://github.com/Yelp/dumb-init/releases/download/v1.2.0/dumb-init_1.2.0_amd64 /usr/local/bin/dumb-init RUN chmod +x /usr/local/bin/dumb-init
Si desea saber más, eche un vistazo a
nuestro Dockerfile .
8. Recuerda dos tiempos de ejecución diferentes.
Es útil recordar que hay
dos tiempos de ejecución de JavaScript (Nodo y navegador). Esto es excelente para separar tareas, pero inevitablemente se produce confusión porque algunos métodos requerirán pasar enlaces explícitos en lugar de alojamientos.
Por ejemplo, tome
page.evaluate
. En las profundidades del protocolo hay una
cadena literal de la función y su transferencia a Chrome . Por lo tanto, cosas como cierres y ascensores
no funcionarán en absoluto . Si necesita pasar algunas referencias o valores a la llamada de evaluación, simplemente agréguelos como argumentos que se procesarán correctamente.
Por lo tanto, en lugar de hacer referencia al
selector
través de cierres:
const anchor = 'a'; await page.goto('https://example.com/');
Mejor parámetro de paso:
const anchor = 'a'; await page.goto('https://example.com/');
page.evaluate
agregar uno o más argumentos a la función
page.evaluate
, ya que aquí es variable. ¡Asegúrate de aprovechar esto!
El futuro
Somos increíblemente optimistas sobre el futuro de los navegadores sin cabeza y toda la automatización que pueden lograr. Usando herramientas poderosas como titiriteros y sin navegador, esperamos que la depuración y ejecución de la automatización sin cabeza en la producción sea más fácil y rápida. ¡Pronto
lanzaremos la facturación de pago por uso para cuentas y
funciones que lo ayudarán a enfrentar mejor su trabajo sin cabeza!