DĂ©couvrez 2 millions de sessions sans tĂȘte

Publié le 4 juin 2018 sur le blog d'entreprise sans navigateur.

Nous sommes heureux d'annoncer que nous avons rĂ©cemment franchi la ligne des deux millions de sessions desservies ! Ce sont des millions de captures d'Ă©cran gĂ©nĂ©rĂ©es, de PDF imprimĂ©s et de sites testĂ©s. Nous avons fait presque tout ce que vous pouvez penser avec un navigateur sans tĂȘte.

Bien qu'il soit agrĂ©able d'atteindre un tel jalon, mais en cours de route, il y avait clairement beaucoup de chevauchements et de problĂšmes. En raison de l'Ă©norme trafic reçu, je voudrais prendre du recul et Ă©noncer des recommandations gĂ©nĂ©rales pour le lancement de navigateurs sans tĂȘte (et marionnettistes ) en production.

Voici quelques conseils.

1. N'utilisez pas du tout de navigateur sans tĂȘte




Consommation de ressources Chrome sans tĂȘte


En aucun cas, si possible, ne dĂ©marrez pas du tout le navigateur en mode sans tĂȘte . Surtout sur la mĂȘme infrastructure que votre application (voir ci-dessus). Le navigateur sans tĂȘte est imprĂ©visible, gourmand et se reproduit comme M. Misix de Rick et Morty. Presque tout ce qu'un navigateur peut faire (Ă  l'exception de l'interpolation et de l'exĂ©cution de JavaScript) peut ĂȘtre fait Ă  l'aide de simples outils Linux. Les bibliothĂšques Cheerio et autres offrent une Ă©lĂ©gante API Node pour rĂ©cupĂ©rer des donnĂ©es avec des requĂȘtes HTTP et le scraping, si tel est votre objectif.

Par exemple, vous pouvez récupérer une page (en supposant qu'il s'agit d'une sorte de code HTML) et la supprimer avec des commandes simples comme celles-ci:

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/'); 

De toute Ă©vidence, le script ne couvre pas tous les cas d'utilisation, et si vous lisez cet article, vous devrez probablement utiliser un navigateur sans tĂȘte. Commençons donc.

2. Ne lancez pas inutilement un navigateur sans tĂȘte


Nous avons rencontrĂ© de nombreux utilisateurs qui essaient de faire fonctionner le navigateur mĂȘme s'il n'est pas utilisĂ© (avec des connexions ouvertes). Bien que cela puisse ĂȘtre une bonne stratĂ©gie pour accĂ©lĂ©rer une session, elle se bloquera dans quelques heures. En grande partie parce que les navigateurs aiment tout mettre en cache et mangent progressivement de la mĂ©moire. DĂšs que vous arrĂȘtez d'utiliser intensivement le navigateur, fermez-le immĂ©diatement!

 import puppeteer from 'puppeteer'; async function run() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.example.com/'); // More stuff ...page.click() page.type() browser.close(); // <- Always do this! } 

En mode sans navigateur, nous corrigeons gĂ©nĂ©ralement cette erreur pour les utilisateurs nous-mĂȘmes, en dĂ©finissant toujours une sorte de minuteur pour la session et en fermant le navigateur lorsque WebSocket est dĂ©connectĂ©. Mais si vous n'utilisez pas notre service ou l' image Docker de sauvegarde , assurez-vous de fermer automatiquement le navigateur, car ce sera dĂ©sagrĂ©able lorsque tout tombera au milieu de la nuit.

3. Votre page d'amis page.evaluate


Soyez prudent avec les transpilers comme babel ou dactylographié, car ils aiment créer des fonctions d'aide et supposent qu'ils sont accessibles avec des fermetures. Autrement dit, le rappel .evaluate peut ne pas fonctionner correctement.

Puppeteer propose de nombreuses mĂ©thodes intĂ©ressantes, telles que le stockage de sĂ©lecteurs DOM et d'autres choses dans un environnement Node. Bien que cela soit trĂšs pratique, vous pouvez facilement vous tirer une balle dans le pied si quelque chose sur la page force ce nƓud DOM Ă  muter . Ce n'est peut-ĂȘtre pas si cool, mais en rĂ©alitĂ©, il vaut mieux faire tout le travail cĂŽtĂ© navigateur dans le contexte du navigateur . Cela signifie gĂ©nĂ©ralement charger page.evaulate pour tout le travail Ă  effectuer.

Par exemple, au lieu de quelque chose comme ça ( trois actions asynchrones):

 const $anchor = await page.$('a.buy-now'); const link = await $anchor.getProperty('href'); await $anchor.click(); return link; 

Il vaut mieux le faire (une action asynchrone):

 await page.evaluate(() => { const $anchor = document.querySelector('a.buy-now'); const text = $anchor.href; $anchor.click(); }); 

Un autre avantage Ă  encapsuler les actions dans un appel d' evaluate est la portabilitĂ©: ce code peut ĂȘtre exĂ©cutĂ© dans un navigateur pour vĂ©rifier au lieu d'essayer de réécrire le code Node. Bien sĂ»r, il est toujours recommandĂ© d' utiliser un dĂ©bogueur pour rĂ©duire le temps de dĂ©veloppement.

Une rĂšgle gĂ©nĂ©rale simple consiste Ă  compter le nombre d' await ou then dans le code. S'il y en a plusieurs, il est probablement prĂ©fĂ©rable d'exĂ©cuter le code Ă  l'intĂ©rieur de l'appel page.evaluate . La raison en est que toutes les actions asynchrones vont et viennent entre l'exĂ©cution du nƓud et le navigateur, ce qui signifie une sĂ©rialisation et une dĂ©sĂ©rialisation JSON constantes. Bien qu'il n'y ait pas une telle quantitĂ© d'analyse (car tout est pris en charge par WebSockets), cela prend encore du temps, ce qui est mieux dĂ©pensĂ© pour autre chose.

4. Parallélisez les navigateurs, pas les pages Web


Nous avons donc rĂ©alisĂ© que le lancement d'un navigateur n'est pas bon et nous devons le faire uniquement en cas d'urgence. L'astuce suivante consiste Ă  exĂ©cuter une seule session par navigateur. Bien qu'en rĂ©alitĂ©, il soit possible d'Ă©conomiser des ressources en parallĂ©lisant le travail sur les pages , mais si une page tombe, elle peut planter tout le navigateur. De plus, il n'est pas garanti que chaque page soit parfaitement propre (les cookies et le stockage peuvent devenir un casse-tĂȘte, comme on le voit ).

Au lieu de cela:

 import puppeteer from 'puppeteer'; // Launch one browser and capture the promise const launch = puppeteer.launch(); const runJob = async (url) { // Re-use the browser here const browser = await launch; const page = await browser.newPage(); await page.goto(url); const title = await page.title(); browser.close(); return title; }; 

Mieux vaut faire ceci:

 import puppeteer from 'puppeteer'; const runJob = async (url) { // Launch a clean browser for every "job" const browser = puppeteer.launch(); const page = await browser.newPage(); await page.goto(url); const title = await page.title(); browser.close(); return title; }; 

Chaque nouvelle instance de navigateur obtient un --user-data-dir propre ( sauf indication contraire ). Autrement dit, il est complÚtement traité comme une nouvelle session. Si Chrome se bloque pour une raison quelconque, il ne tirera pas d'autres sessions avec lui.

5. Limite de file d'attente et de simultanéité


L'une des principales caractéristiques de sans navigateur est la possibilité de limiter soigneusement la parallélisation et la file d'attente. Les applications clientes exécutent donc simplement puppeteer.connect , mais elles ne pensent pas à l'implémentation de la file d'attente. Cela évite une énorme quantité de problÚmes, principalement avec les instances simultanées de Chrome qui engloutissent toutes les ressources disponibles de votre application.

La meilleure et la plus simple consiste à prendre notre image Docker et à l'exécuter avec les paramÚtres nécessaires:

 # 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 

Cela limite le nombre de demandes simultanées à dix (y compris les sessions de débogage et plus). La file d'attente est configurée par la variable MAX_QUEUE_LENGTH . En rÚgle générale, vous pouvez effectuer environ 10 demandes simultanées par gigaoctet de mémoire. Le pourcentage d'utilisation du processeur peut varier pour différentes tùches, mais en gros, vous aurez besoin de beaucoup et de beaucoup de RAM.

6. N'oubliez pas page.waitForNavigation


L'un des problÚmes les plus courants que nous avons rencontrés concerne les actions qui démarrent le chargement des pages avec la fermeture soudaine des scripts. Cela est dû au fait que les actions qui déclenchent le pageload provoquent souvent la déglutition des travaux ultérieurs. Pour contourner le problÚme, vous devez généralement appeler l'action de chargement de page - et immédiatement aprÚs l'attente du chargement.

Par exemple, un tel console.log ne fonctionne pas au mĂȘme endroit ( voir dĂ©mo ):

 await page.goto('https://example.com'); await page.click('a'); const title = await page.title(); console.log(title); 

Mais ça marche dans un autre ( voir démo ).

 await page.goto('https://example.com'); page.click('a'); await page.waitForNavigation(); const title = await page.title(); console.log(title); 

Vous pouvez en savoir plus sur waitForNavigation ici . Cette fonction a approximativement les mĂȘmes paramĂštres d'interface que page.goto , mais uniquement avec la partie «wait».

7. Utilisez Docker pour tout ce dont vous avez besoin.


Chrome a besoin de beaucoup de dĂ©pendances pour fonctionner correctement. Vraiment beaucoup. MĂȘme aprĂšs avoir installĂ© tout ce dont vous avez besoin, vous devez vous soucier de choses comme les polices et les processus fantĂŽmes. Par consĂ©quent, il est idĂ©al d'utiliser une sorte de conteneur pour tout y mettre. Docker est presque spĂ©cialement conçu pour cette tĂąche, car vous pouvez limiter la quantitĂ© de ressources disponibles et l'isoler. Si vous souhaitez crĂ©er votre propre Dockerfile , vĂ©rifiez ci-dessous toutes les dĂ©pendances nĂ©cessaires:

 # 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 

Et afin d'éviter les processus zombies (une chose courante dans Chrome), il est préférable d'utiliser quelque chose comme dumb-init pour fonctionner correctement:

 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 vous voulez en savoir plus, jetez un Ɠil à notre Dockerfile .

8. N'oubliez pas deux temps d'exécution différents.


Il est utile de se rappeler qu'il existe deux environnements d'exĂ©cution JavaScript (nƓud et navigateur). Ceci est idĂ©al pour sĂ©parer les tĂąches, mais une confusion se produit inĂ©vitablement car certaines mĂ©thodes nĂ©cessitent un passage de lien explicite au lieu de levage.

Par exemple, prenez page.evaluate . Au fond des entrailles du protocole, il y a une strictification littérale de la fonction et de son transfert vers Chrome . Par conséquent, des choses comme les fermetures et les ascenseurs ne fonctionneront pas du tout . Si vous devez transmettre des références ou des valeurs à l'appel d'évaluation, ajoutez-les simplement en tant qu'arguments qui seront traités correctement.

Ainsi, au lieu de référencer le selector via des fermetures:

 const anchor = 'a'; await page.goto('https://example.com/'); // `selector` here is `undefined` since we're in the browser context const clicked = await page.evaluate(() => document.querySelector(anchor).click()); 

Meilleur paramĂštre de passage:

 const anchor = 'a'; await page.goto('https://example.com/'); // Here we add a `selector` arg and pass in the reference in `evaluate` const clicked = await page.evaluate((selector) => document.querySelector(selector).click(), anchor); 

page.evaluate pouvez ajouter un ou plusieurs arguments Ă  la fonction page.evaluate , car elle est variable ici. Assurez-vous d'en profiter!

Le futur


Nous sommes incroyablement optimistes quant Ă  l'avenir des navigateurs sans tĂȘte et Ă  toute l'automatisation qu'ils peuvent rĂ©aliser. En utilisant des outils puissants comme marionnettiste et sans navigateur, nous espĂ©rons que le dĂ©bogage et l'exĂ©cution de l'automatisation sans tĂȘte en production seront plus faciles et plus rapides. BientĂŽt, nous lancerons la facturation au fur et Ă  mesure des comptes et des fonctions qui vous aideront Ă  mieux faire face Ă  votre travail sans tĂȘte!

Source: https://habr.com/ru/post/fr413547/


All Articles