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/');
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';
Mieux vaut faire ceci:
import puppeteer from 'puppeteer'; const runJob = async (url) {
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/');
Meilleur paramĂštre de passage:
const anchor = 'a'; await page.goto('https://example.com/');
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!