O co jde
Uděláme bota co načte webovou stránku, klikne na čudlík a stáhne soubor.
Použijeme k tomu knihovnu Puppeteer a samotný kód budeme pouštět v Dockeru.
Větší podrobnosti a vysvětlení zakládání NodeJs projektu v Dockeru najde v článku NodeJS develompent v Dockeru.
Příprava projektu
Založíme nový projekt:
mkdir puppet
cd puppet
npm init -y
Upravíme package.json
a přidáme do něj knihovnu puppeteer
:
{
"name": "puppet",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"puppeteer": "^19.2.0"
}
}
Vytvoříme Dockerfile
, který použije jako základ obraz s node v16
, nainstaluje Chrome prohlížeč
a nakopíruje projekt do Docker obrazu.
FROM node:16
RUN apt-get update && apt-get install gnupg wget -y && \
wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg && \
sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' && \
apt-get update && \
apt-get install google-chrome-stable -y --no-install-recommends && \
rm -rf /var/lib/apt/lists/*
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . ./
Vytvoříme index.js
soubor a necháme ho jen vypsat zprávu do konzole.
touch index.js
console.log('Hello from docker')
Není úplně špatné vytvořit i .dockerignore
soubor.
node_modules
Dockerfile
.dockerignore
.git
.gitignore
README.md
docker-compose*
Sestavíme obraz.
docker build -t puppet-app-image .
A spustíme Docker kontejner.
# %cd% je aktuální složka ve Windows, pro jiné systémy použijte adekvátní náhradu
docker run -d --tty --name puppet-app -v %cd%:/app -v /app/node_modules puppet-app-image
--tty parametr je nutný. V opačném případě se Docker kontejner spustí a pak hned úspěšně ukončí. 🙂 Za normálních okolností předpokládá, že v něm něco poběží a až se to ukončí tak skončí taky. TTY toto chování potlačí.
Jakmile kontejner běží, můžeme se do něj přihlásit:
docker exec -it puppet-app bash
Vevnitř se ocitneme ve složce /app
, kde je náš projekt a můžeme zkusit spustit náš program.
nodejs index.js
Měl by nám odpovědět Hello from docker
.
Bot
Pro ukázku bude bot stahovat zdrojový kód puppeteeru z githubu. Upravíme index.js
následovně.
const puppeteer = require('puppeteer')
;(async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox'],
})
const page = await browser.newPage()
page.setViewport({ width: 1920, height: 1080 })
const client = await page.target().createCDPSession()
await client.send("Page.setDownloadBehavior", {
behavior: "allow",
downloadPath: './'
})
await page.goto(
'https://github.com/puppeteer/puppeteer/releases/tag/puppeteer-core-v19.2.0',
{ waitUntil: 'networkidle2' }
)
await page.waitForTimeout(1000)
await page.click('#repo-content-turbo-frame div.mb-3 > details > div > div > ul > li:nth-child(1) > div.d-flex.flex-justify-start.col-12.col-lg-9 > a')
await page.waitForTimeout(5000)
await page.screenshot({ path: './screen.jpg', fullPage: true })
await browser.close()
})()
Naimportujeme do našeho projektu knihovnu puppeteer
.
const puppeteer = require('puppeteer')
Budeme volat asynchronní funkce v java scriptu a budeme používat klíčové slovo await
, které čeká na provedení asynchronní funkce. Problém je to, že await
se může objevit jen v asynchronní funkci. Tudíž náš kód musíme do jedné takové zabalit a rovnou spustit.
;(async () => {
// code
})()
Vytvoříme nový headless prohlížeč
.
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox'],
})
Vytvoříme novou stránku/tab
a nastavíme rozlišení.
const page = await browser.newPage()
page.setViewport({ width: 1920, height: 1080 })
Pro tuto konkrétní stránku nastavíme automatické stahovaní do určitého adresáře. V našem případě adresář s projektem. Takhle nebudeme vyvolávat dialog pro umístění stahovaného souboru.
const client = await page.target().createCDPSession()
await client.send("Page.setDownloadBehavior", {
behavior: "allow",
downloadPath: './'
})
V naší stránce/tabu
otevřeme požadovanou github stránku.
await page.goto(
'https://github.com/puppeteer/puppeteer/releases/tag/puppeteer-core-v19.2.0',
{ waitUntil: 'networkidle2' }
)
await page.waitForTimeout(1000)
Klikneme na odkaz pro stažení a chvíli počkáme, než se soubor stáhne 🙂
await page.click('#repo-content-turbo-frame div.mb-3 > details > div > div > ul > li:nth-child(1) > div.d-flex.flex-justify-start.col-12.col-lg-9 > a')
await page.waitForTimeout(5000)
K definování toho na co se má kliknout se používá CSS Selector (draft, mdn). Ten pomocí html elementů, tříd, atributů a dalších dokáže sestavit unikátní cestu k prvku na stránce. Stránky generované nějakým frameworkem umí potrápit i s takhle dlouhým selektorem, jako v tomto případě.
Pak už si jen uděláme momentku, v jakém stavu stránku opouštíme.
await page.screenshot({ path: './screen.jpg', fullPage: true })
A prohlížeč korektně zavřeme.
await browser.close()
Spuštění kódu
V konzoli se připojíme k našemu kontejneru.
docker exec -it puppet-app bash
A spustíme našeho bota.
node index.js
Pokud vše proběhne, jak má, tak ve složce projektu budete mít jak stažený soubor, tak fotku stránky těsně před tím než jste ji opustili.
Toto je velice hrubý nástřel toho, co Puppeteer dokáže na konkrétním případě. Výhodou je, že se opravdu spustí prohlížeč, ve kterém můžeme ovládat i dynamicky generované single page
aplikace. Nevýhodou je, že pokud se struktura stránky změní moc, tak musíte upravit i váš kód.
Happy coding!