Veröffentlicht am 4. Juni 2018 im browserlosen Unternehmensblog.Wir freuen uns, Ihnen mitteilen zu können, dass wir kürzlich die Grenze von
zwei Millionen Sitzungen überschritten haben! Dies sind
Millionen von generierten Screenshots, gedruckten PDFs und getesteten Websites. Wir haben fast alles getan, was Sie sich mit einem kopflosen Browser vorstellen können.
Es ist zwar schön, einen solchen Meilenstein zu erreichen, aber auf dem Weg gab es eindeutig
viele Überschneidungen und Probleme. Aufgrund des enormen Verkehrsaufkommens möchte ich einen Schritt zurücktreten und allgemeine Empfehlungen für den Start von Headless-Browsern (und
Puppenspielern ) in der Produktion
abgeben .
Hier sind einige Tipps.
1. Verwenden Sie überhaupt keinen kopflosen Browser

Headless Chrome-Ressourcenverbrauch
Starten Sie den Browser nach Möglichkeit
keinesfalls im Headless-Modus . Insbesondere auf derselben Infrastruktur wie Ihre Anwendung (siehe oben). Der kopflose Browser ist unvorhersehbar, gefräßig und züchtet wie Mr. Misix von Rick und Morty. Fast alles, was ein Browser tun kann (außer das Interpolieren und Ausführen von JavaScript), kann mit einfachen Linux-Tools ausgeführt werden. Die Cheerio-Bibliotheken und andere bieten eine elegante Node-API zum Abrufen von Daten mit HTTP-Anforderungen und zum Scraping, wenn dies Ihr Ziel ist.
Sie können beispielsweise eine Seite aufnehmen (vorausgesetzt, es handelt sich um eine Art HTML) und sie mit einfachen Befehlen wie den folgenden verschrotten:
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/');
Offensichtlich deckt das Skript nicht alle Anwendungsfälle ab. Wenn Sie diesen Artikel lesen, müssen Sie höchstwahrscheinlich einen kopflosen Browser verwenden. Also fangen wir an.
2. Starten Sie einen Headless-Browser nicht unnötig
Wir haben zahlreiche Benutzer getroffen, die versuchen, den Browser auch dann am Laufen zu halten, wenn er nicht verwendet wird (mit offenen Verbindungen). Obwohl dies eine gute Strategie ist, um eine Sitzung zu beschleunigen, stürzt sie in wenigen Stunden ab. Vor allem, weil Browser
gerne alles hintereinander zwischenspeichern und nach und nach Speicherplatz verbrauchen. Sobald Sie den Browser nicht mehr intensiv nutzen, schließen Sie ihn sofort!
import puppeteer from 'puppeteer'; async function run() { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://www.example.com/');
In browserless beheben wir diesen Fehler normalerweise für Benutzer selbst, indem wir immer einen Timer für die Sitzung einstellen und den Browser schließen, wenn WebSocket getrennt wird. Wenn Sie jedoch unseren
Service oder das
Backup-Docker-Image nicht nutzen, stellen Sie sicher, dass Sie den Browser
automatisch schließen, da dies unangenehm ist, wenn alles mitten in der Nacht fällt.
3. Ihre Freundeseite
Seien Sie vorsichtig mit Transpilern wie Babel oder Typoskript, da sie gerne Hilfsfunktionen erstellen und davon ausgehen, dass sie mit Verschlüssen zugänglich sind. Das heißt, der .evaluate-Rückruf funktioniert möglicherweise nicht richtig.
Puppenspieler hat viele nette Methoden, wie das Speichern von DOM-Selektoren und anderen Dingen in einer Knotenumgebung. Obwohl dies sehr praktisch ist, können Sie sich leicht in den Fuß schießen, wenn etwas auf der Seite diesen DOM-Knoten zur
Mutation zwingt. Das mag nicht so cool sein, aber in Wirklichkeit ist es besser, die gesamte Arbeit auf der Browserseite
im Kontext des Browsers zu erledigen. Dies bedeutet normalerweise, dass
page.evaulate
für alle zu erledigenden Arbeiten
page.evaulate
.
Zum Beispiel anstelle von so etwas (
drei asynchrone Aktionen):
const $anchor = await page.$('a.buy-now'); const link = await $anchor.getProperty('href'); await $anchor.click(); return link;
Es ist besser, dies zu tun (eine asynchrone Aktion):
await page.evaluate(() => { const $anchor = document.querySelector('a.buy-now'); const text = $anchor.href; $anchor.click(); });
Ein weiterer Vorteil beim Umschließen von Aktionen in einen
evaluate
ist die Portabilität: Dieser Code kann in einem Browser ausgeführt werden, um zu überprüfen, anstatt zu versuchen, den Knotencode neu zu schreiben. Natürlich wird immer empfohlen,
einen Debugger zu verwenden, um die Entwicklungszeit zu verkürzen.
Eine einfache Faustregel ist, die Anzahl der
await
oder
then
im Code zu zählen. Wenn es mehr als eine gibt, ist es wahrscheinlich am besten, den Code innerhalb des Aufrufs
page.evaluate
. Der Grund dafür ist, dass alle asynchronen Aktionen zwischen der Node-Laufzeit und dem Browser hin und her gehen, was eine konstante JSON-Serialisierung und -Deserialisierung bedeutet. Obwohl nicht so viel analysiert wird (da alles von WebSockets unterstützt wird), nimmt es dennoch Zeit in Anspruch, die besser für etwas anderes ausgegeben wird.
4. Parallelisieren Sie Browser, nicht Webseiten
Wir haben festgestellt, dass das Starten eines Browsers nicht gut ist und wir dies nur im Notfall tun müssen. Der nächste Tipp ist, nur eine Sitzung pro Browser auszuführen. In der Realität ist es zwar möglich, Ressourcen durch Parallelisierung der Arbeit durch
pages
zu sparen, aber wenn eine Seite ausfällt, kann der gesamte Browser abstürzen. Darüber hinaus kann nicht garantiert werden, dass jede Seite perfekt sauber ist (Cookies und Speicher können,
wie wir sehen, zu Kopfschmerzen führen).
Stattdessen:
import puppeteer from 'puppeteer';
Mach das besser:
import puppeteer from 'puppeteer'; const runJob = async (url) {
Jede neue Browserinstanz erhält ein sauberes
--user-data-dir
(
sofern nicht anders angegeben ). Das heißt, es wird vollständig als neue Sitzung verarbeitet. Wenn Chrome aus irgendeinem Grund abstürzt, werden keine anderen Sitzungen damit abgerufen.
5. Begrenzung der Warteschlange und der Parallelität
Eines der Hauptmerkmale von browserless ist die Möglichkeit, Parallelisierung und Warteschlange sauber einzuschränken. Client-Anwendungen führen also nur
puppeteer.connect
, denken jedoch nicht an die Implementierung der Warteschlange. Dies verhindert eine Vielzahl von Problemen, vor allem bei gleichzeitigen Chrome-Instanzen, die alle verfügbaren Ressourcen Ihrer Anwendung verschlingen.
Der beste und einfachste Weg ist, unser Docker-Image mit den erforderlichen Parametern auszuführen:
# 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
Dies begrenzt die Anzahl gleichzeitiger Anforderungen auf zehn (einschließlich Debugging-Sitzungen und mehr). Die Warteschlange wird durch die Variable
MAX_QUEUE_LENGTH
. In der Regel können Sie ungefähr 10 gleichzeitige Anforderungen pro Gigabyte Speicher ausführen. Der Prozentsatz der CPU-Auslastung kann für verschiedene Aufgaben variieren, aber im Grunde benötigen Sie viel und viel RAM.
6. Vergessen Sie nicht page.waitForNavigation
Eines der häufigsten Probleme, auf die wir gestoßen sind, sind die Aktionen, die das Laden von Seiten mit der anschließenden plötzlichen Beendigung von Skripten starten. Dies liegt daran, dass die Aktionen, die das
pageload
von
pageload
auslösen, häufig zum Verschlucken nachfolgender Arbeiten führen. Um das Problem zu umgehen, müssen Sie normalerweise die Aktion zum Laden von Seiten aufrufen - und unmittelbar danach auf das Laden warten.
Zum Beispiel funktioniert eine solche
console.log
nicht an einem Ort (
siehe Demo ):
await page.goto('https://example.com'); await page.click('a'); const title = await page.title(); console.log(title);
Aber es
funktioniert in einem anderen (
siehe Demo ).
await page.goto('https://example.com'); page.click('a'); await page.waitForNavigation(); const title = await page.title(); console.log(title);
Weitere Informationen zu waitForNavigation finden Sie
hier . Diese Funktion hat ungefähr die gleichen Schnittstellenparameter wie
page.goto
, jedoch nur mit dem Teil "wait".
7. Verwenden Sie Docker für alles, was Sie brauchen.
Chrome benötigt viele Abhängigkeiten, um ordnungsgemäß zu funktionieren. Wirklich viel. Auch nach der Installation müssen Sie sich um Dinge wie Schriftarten und Phantomprozesse kümmern. Daher ist es ideal, einen Behälter zu verwenden, um alles dort hinein zu stellen. Docker wurde fast speziell für diese Aufgabe entwickelt, da Sie die Menge der verfügbaren Ressourcen begrenzen und isolieren können. Wenn Sie eine eigene
Dockerfile
erstellen
Dockerfile
, überprüfen Sie unten alle erforderlichen Abhängigkeiten:
# 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
Und um Zombie-Prozesse zu vermeiden (eine häufige Sache in Chrome), ist es besser, etwas wie
dummes Init zu verwenden, um richtig zu laufen:
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
Wenn Sie mehr wissen möchten, schauen Sie sich
unsere Docker-Datei an .
8. Denken Sie an zwei verschiedene Laufzeiten.
Beachten Sie, dass es
zwei JavaScript-Laufzeiten gibt (Knoten und Browser). Dies ist ideal für die Trennung von Aufgaben, aber es kommt unweigerlich zu Verwirrung, da einige Methoden eine explizite Verbindungsübergabe anstelle von Hebevorgängen erfordern.
Nehmen
page.evaluate
zum Beispiel
page.evaluate
. Tief im
Inneren des Protokolls gibt es eine
wörtliche Stringifizierung der Funktion und ihrer Übertragung auf Chrome . Daher
funktionieren Dinge wie Verschlüsse und Aufzüge überhaupt
nicht . Wenn Sie einige Referenzen oder Werte an den Evaluierungsaufruf übergeben müssen, fügen Sie sie einfach als Argumente hinzu, die korrekt verarbeitet werden.
Anstatt den
selector
über Verschlüsse zu referenzieren:
const anchor = 'a'; await page.goto('https://example.com/');
Besserer Parameter übergeben:
const anchor = 'a'; await page.goto('https://example.com/');
page.evaluate
können der Funktion
page.evaluate
ein oder mehrere Argumente
page.evaluate
, da diese hier variabel ist. Nutzen Sie dies unbedingt!
Die Zukunft
Wir sind unglaublich optimistisch in Bezug auf die Zukunft von Headless-Browsern und die Automatisierung, die sie erreichen können. Mit leistungsstarken Tools wie Puppenspieler und browserlos hoffen wir, dass das Debuggen und Ausführen der kopflosen Automatisierung in der Produktion einfacher und schneller wird. In Kürze werden wir
eine Abrechnung für Konten und
Funktionen durchführen , mit denen Sie Ihre kopflose Arbeit besser bewältigen können!