|
Puppeteer hat eine sehr mächtige API, womit man noch viel mehr machen kann außer Unit-Tests auszuführen.
Über die Methoden der API lassen sich sehr einfach alle Benutzeraktionen programmatisch durchführen und so bestehende Prozesse und oft anfallende Aufgaben automatisieren.
Die folgenden Beispiele machen deutlich, was damit alles möglich ist.
Voraussetzung für alle Beispiele ist nur, dass Puppeteer installiert ist.
npm i puppeteer -S
Suchergebnis von Google als PDF
const puppeteer = require('puppeteer')
puppeteer.launch({ headless: true }).then(async browser => {
const page = await browser.newPage()
await page.goto('https://www.google.com/')
await page.type('[name="q"]', 'getting started with puppeteer', {delay: 100})
const navigationPromise = page.waitForNavigation()
await page.keyboard.press('Enter')
await navigationPromise
await page.pdf({ path: './google-search-results-page-1.pdf', format: 'A4', printBackground: true, pageRanges: '1' })
await browser.close()
})
Erläuterung was hier genau passiert anhand der Zeilennummer:
{1} const puppeteer = require('puppeteer')
macht puppeteer
nach der Installation in der Datei verfügbar.
{2} puppeteer.launch({ headless: true }).then(async browser => {
startet Puppeteer und weist die Instanz der Variable browser
zu.
{ headless: true }
ist die Standardeinstellung und kann weggelassen werden. Ich habe es jedoch oft drin, damit ich recht schnell zwischen "headless" und "headful" wechseln kann. Im "headful" Modus sieht man eine normale Chromium-Instanz, worin das alles passiert. So kann man die Befehle entsprechend nachvollziehen und überprüfen.
{3} const page = await browser.newPage()
erstellt einen neuen Tab und weist dessen Instanz der page
Konstante zu.
{4} await page.goto('https://www.google.com/')
führt die Navigation zu der gewünschten URL aus.
{5} await page.type('[name="q"]', 'getting started with puppeteer', {delay: 100})
schreibt im Element dessen Name "q" ist (die Sucheingabe) den Text "getting started with puppeteer" mit einem Abstand von 100ms zwischen jeder Eingabe.
{6} const navigationPromise = page.waitForNavigation()
initialisiert ein Promise, dass auf eine Änderung in der URL reagiert.
{7} await page.keyboard.press('Enter')
sendet über die Tastatur die spezielle Taste Enter
.
{8} await navigationPromise
ist erfolgreich, wenn nach dem Drücken der Enter-Taste eine Navigation ausgelöst wird.
{9} await page.pdf({ path: './google-search-results-page-1.pdf', format: 'A4', printBackground: true, pageRanges: '1' })
speichert ein PDF mit dem Dateinamen google-search-results-page-1.pdf
im DIN A4 Format, aktiviert das Drucken der Hintergründe und enthält nur die erste druckbare Seite.
{10} await browser.close()
beendet die Browser-Instanz bzw. schließt den Browser, da wir diesen nicht mehr benötigen.
await
wird in Verbindung mit den Befehlen benötigt um Node.js anzuweisen, dass immer gewartet werden soll, bis die einzelnen Befehle erfolgreich synhron nacheinander ausgeführt wurden, da diese jeweils einen Promise generieren und wir auf dessen Erfüllung warten.
Bei den nachfolgenden Beispielen sind nur die neuen oder abweichenden Zeilen beschrieben, die zum Beispiel neue Parameter enthalten.
Screenshot mit emuliertem iPhone X
const puppeteer = require('puppeteer')
const devices = require('puppeteer/DeviceDescriptors')
const iPhone = devices['iPhone X']
puppeteer.launch({ headless: true }).then(async browser => {
const page = await browser.newPage()
await page.emulate(iPhone)
await page.goto('https://www.designmadeingermany.de/')
await page.screenshot({ path: './design-made-in-germany-iphonex.png', type: 'jpeg', fullPage: true })
await browser.close()
})
{2} const devices = require('puppeteer/DeviceDescriptors')
lädt die Liste der emulierten Geräte die es auch in den Chrome DevTools gibt.
{3} const iPhone = devices['iPhone X']
weist das emulierte iPhone X über die exportieren Einträge der Konstanten iPhone
zu.
{6} await page.emulate(iPhone)
verwendet die davor definierte Konstante für die gewünschte Emulation (Viewport, Useragent, DPR, ...).
{8} await page.screenshot({ path: './design-made-in-germany-iphonex.png', type: 'jpeg', fullPage: true })
erstellt einen Screenshot im JPEG-Format von der ganzen Seite (also auch über und unter der aktuellen Scroll-Position) und speichert diesen als design-made-in-germany-iphonex.png
im aktuellen Ordner.
Links und Cookies auslesen
const puppeteer = require('puppeteer')
puppeteer.launch({ headless: true }).then(async browser => {
const page = await browser.newPage()
await page.goto('http://alistapart.com/articles')
const cookies = await page.cookies()
console.log(cookies)
const links = await page.evaluate(() => [...document.querySelectorAll('.main-content .entry-list .entry-title a')].map(link => link.href))
links.forEach(link => console.log(link))
await browser.close()
})
{5} const cookies = await page.cookies()
speichert die Daten der aktuellen Cookies in der Konstante cookies
.
{6} console.log(cookies)
gibt die Cookies als JSON-Objekte aus.
{7} const links = await page.evaluate(() => [...document.querySelectorAll('.main-content .entry-list .entry-title a')].map(link => link.href))
führt per page.evaluate
einen Befehl aus, der alle Artikel-Links als Elemente ausliest und von diesen jeweils nur das href-Attribut als Wert nutzt.
{8} links.forEach(link => console.log(link))
gibt die Links einzeln wieder aus.
Seite als PDF mit media: screen
const puppeteer = require('puppeteer')
puppeteer.launch({ headless: true }).then(async browser => {
const page = await browser.newPage()
await page.goto('https://egghead.io/search')
await page.waitForSelector('[class*="index__eh-text-field__"]')
await page.type('[class*="index__eh-text-field__"]', 'puppeteer')
await page.click('[class*="index__filter-toggle__"]')
const selectors = ['[name="article"]', '[name="course"]', '[name="lesson"]']
selectors.forEach(async function(selector){ await page.click(selector)})
const navigationPromise = page.waitForNavigation()
await page.click('[class*="index__submit__"]')
await navigationPromise
await page.waitForSelector('.lds-ellipsis')
await page.waitForFunction('!document.querySelector(".lds-ellipsis")',{timeout:30000})
await page.emulateMedia('screen')
await page.pdf({ path: './puppeteer-learning-resources.pdf', format: 'A4', printBackground: true })
await browser.close()
})
{5} await page.waitForSelector('[class*="index__eh-text-field__"]')
wartet bis das Element mit dem Selector [class*="index__eh-text-field__"]
im DOM und sichtbar ist.
{6} await page.type('[class*="index__eh-text-field__"]', 'puppeteer')
schreibt direkt den Text ohne zwischen jeder Eingabe zu warten.
{7} await page.click('[class*="index__filter-toggle__"]')
klickt das Element mit dem Selector [class*="index__filter-toggle__"]
an.
{8} const selectors = ['[name="article"]', '[name="course"]', '[name="lesson"]']
bereitet 3 Selector-Angaben für die nächste Zeile vor.
{9} selectors.forEach(async function(selector){ await page.click(selector)})
klickt jedes der Elemente in selectors
mit dem entsprechenden Selector einzeln an.
{14} await page.waitForFunction('!document.querySelector(".lds-ellipsis")',{timeout:30000})
erstellt eine Funktion die maximal 30 Sekunden die weitere Ausführung pausiert, bis ein Element mit dem Selector .lds-ellipsis
nicht mehr sichtbar im DOM ist.
{15} await page.emulateMedia('screen')
wechselt die Druckdarstellung von media: print zu media: screen, also so wie die Seite aktuell im Browser aussehen würde.
Es gibt bereits fertige Lösungen die intern Puppeteer nutzen und zum Beispiel Seiten als PDF speichern oder auch für PWAs (Progressive Web Apps) um eine Seite serverseitig vorzurendern und als statisches HTML an den Client zu senden.
Als Alternative zu Puppeteer gibt es unter anderem Nightmare, welches Electron verwendet.