Kas yra Puppeteer ir kodėl jis tapo tokiu populiariu
Kai Google komanda 2017 metais pristatė Puppeteer, daugelis web kūrėjų atsidusdami pagalvojo: „Pagaliau!”. Iki tol automatizuoti naršyklės veiksmus buvo panašu į bandymą išmokyti katę atsinešti laikraštį – techniškai įmanoma, bet reikalauja daug kantrybės ir dažnai baigiasi netikėtais rezultatais.
Puppeteer yra Node.js biblioteka, kuri suteikia aukšto lygio API valdyti Chrome arba Chromium naršyklę per DevTools protokolą. Paprasčiau tariant, tai įrankis, leidžiantis programiškai kontroliuoti naršyklę – spausti mygtukus, užpildyti formas, daryti ekrano kopijas, generuoti PDF failus ir daug ką daugiau. Ir viskas veikia headless režimu, t.y. be vizualios naršyklės sąsajos.
Skirtumas nuo senesnių sprendimų, tokių kaip Selenium, yra akivaizdus. Puppeteer dirba tiesiogiai su Chrome, o ne per tarpininkus. Tai reiškia greitesnį veikimą, stabilesnį elgesį ir mažiau netikėtų klaidų. Be to, jis puikiai integruojasi su modernia JavaScript ekosistema – naudoja async/await sintaksę, palaiko ES6 modulius ir jaučiasi kaip namie bet kuriame Node.js projekte.
Kaip pradėti: pirmieji žingsniai su Puppeteer
Instaliacija yra paprasta kaip du kartus du. Atidarote terminalą ir įvedate:
npm install puppeteer
Štai ir viskas. Puppeteer automatiškai atsisiųs naujausią Chromium versiją, kuri garantuotai veiks su biblioteka. Taip, tai reiškia, kad projekto node_modules aplankas išaugs maždaug 300 MB, bet už stabilumą verta mokėti.
Jei nenorite atsisiųsti viso Chromium ir planuojate naudoti jau įdiegtą Chrome naršyklę sistemoje, galite naudoti puppeteer-core paketą:
npm install puppeteer-core
Tačiau pradedantiesiems rekomenduoju standartinį puppeteer – mažiau galvos skausmo dėl versijų suderinamumo.
Štai paprasčiausias pavyzdys, kaip atidaryti puslapį ir padaryti ekrano kopiją:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'example.png' });
await browser.close();
})();
Paleiskite šį kodą ir per kelias sekundes turėsite ekrano kopiją. Jei tai neįspūdinga, nežinau, kas įspūdinga.
Headless vs headful režimai: kada naudoti kurį
Headless režimas – tai Puppeteer stiprioji pusė. Naršyklė veikia fone, be jokios vizualios sąsajos, kas leidžia sutaupyti sistemos išteklių ir paspartina veikimą. Idealus variantas CI/CD pipeline’uose, automatizuotuose testuose ar serverinėse aplikacijose.
Bet kartais reikia matyti, kas vyksta. Debuginant sudėtingus scenarijus arba bandant suprasti, kodėl jūsų skriptas neveikia taip, kaip tikėjotės, headful režimas tampa gelbėjimo ratu. Įjungti jį paprasta:
const browser = await puppeteer.launch({ headless: false });
Dar naudingesnis yra slowMo parametras, kuris sulėtina visus veiksmus, kad galėtumėte stebėti, kas vyksta:
const browser = await puppeteer.launch({
headless: false,
slowMo: 250 // sulėtina 250ms kiekvieną operaciją
});
Kūrimo metu aš dažnai dirbu su headful režimu ir DevTools atidarytu. Tai leidžia matyti console.log išvestis, tinklo užklausas ir kitus naudingus dalykus:
const browser = await puppeteer.launch({
headless: false,
devtools: true
});
Produkcijoje, žinoma, visada naudokite headless režimą. Jis ne tik greitesnis, bet ir saugesnis serverinėje aplinkoje.
Web scraping su Puppeteer: praktiniai pavyzdžiai
Vienas populiariausių Puppeteer panaudojimo būdų – duomenų išgavimas iš svetainių. Ir nors teoriškai tai skamba paprasta, praktikoje susiduriate su įvairiomis kliūtimis: dinamiškai kraunamas turinys, infinite scroll, CAPTCHA, anti-bot mechanizmai.
Štai realus pavyzdys, kaip išgauti straipsnių pavadinimus iš naujienų portalo:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://news-website.com', {
waitUntil: 'networkidle2'
});
const articles = await page.evaluate(() => {
const titles = Array.from(document.querySelectorAll('.article-title'));
return titles.map(title => ({
text: title.textContent.trim(),
link: title.href
}));
});
console.log(articles);
await browser.close();
})();
Atkreipkite dėmesį į `waitUntil: ‘networkidle2’` parametrą. Jis nurodo Puppeteer laukti, kol tinklo aktyvumas nurims (liks ne daugiau kaip 2 aktyvūs ryšiai 500ms laikotarpyje). Tai ypač svarbu su Single Page Applications, kur turinys kraunamas asinchroniškai.
Jei svetainė naudoja infinite scroll, reikia imituoti slinkimą:
await page.evaluate(async () => {
await new Promise((resolve) => {
let totalHeight = 0;
const distance = 100;
const timer = setInterval(() => {
const scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
if(totalHeight >= scrollHeight){
clearInterval(timer);
resolve();
}
}, 100);
});
});
Šis kodas slinks žemyn po truputį, laukdamas, kol naujas turinys užsikraus, kol pasieks puslapio pabaigą.
Automatizuoti testai: kai Puppeteer tampa QA inžinieriumi
E2E (end-to-end) testavimas su Puppeteer yra malonumas. Galite simuliuoti realius vartotojo scenarijus: prisijungimą, formų užpildymą, navigaciją tarp puslapių, failų įkėlimą.
Štai prisijungimo testo pavyzdys:
const puppeteer = require('puppeteer');
describe('Login functionality', () => {
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch();
page = await browser.newPage();
});
afterAll(async () => {
await browser.close();
});
test('should login successfully', async () => {
await page.goto('https://yourapp.com/login');
await page.type('#email', '[email protected]');
await page.type('#password', 'password123');
await page.click('button[type="submit"]');
await page.waitForNavigation();
const url = page.url();
expect(url).toBe('https://yourapp.com/dashboard');
}, 30000);
});
Šis pavyzdys naudoja Jest testing framework, bet Puppeteer puikiai veikia ir su Mocha, Jasmine ar bet kuria kita testavimo biblioteka.
Svarbus momentas – visada nustatykite pakankamą timeout. Numatytasis Jest timeout yra 5 sekundės, ko dažnai nepakanka naršyklės testams. Todėl matote tą `30000` (30 sekundžių) parametrą testo pabaigoje.
Dar vienas patarimas: naudokite `page.waitForSelector()` vietoj fiksuotų timeout’ų:
// Blogai
await page.click('button');
await page.waitForTimeout(3000); // laukiame 3 sekundes
// Gerai
await page.click('button');
await page.waitForSelector('.success-message'); // laukiame, kol pasirodys elementas
Antrasis variantas yra ir greitesnis (jei elementas pasirodo greičiau), ir patikimesnis (jei reikia daugiau laiko).
Sudėtingesni scenarijai: interceptai, cookies ir autentifikacija
Kai pradedate dirbti su realiais projektais, greitai susiduriate su poreikiu kontroliuoti tinklo užklausas, valdyti cookies arba apeiti autentifikacijos mechanizmus.
Puppeteer leidžia perimti ir modifikuoti tinklo užklausas:
await page.setRequestInterception(true);
page.on('request', (request) => {
if (request.resourceType() === 'image') {
request.abort(); // blokuojame visus paveikslėlius
} else {
request.continue();
}
});
Tai gali drastiškai paspartinti scraping’ą – kam kraunti paveikslėlius, jei jums reikia tik teksto?
Darbas su cookies yra paprastas:
// Nustatyti cookies
await page.setCookie({
name: 'session',
value: 'abc123',
domain: 'example.com'
});
// Gauti cookies
const cookies = await page.cookies();
console.log(cookies);
// Ištrinti cookies
await page.deleteCookie({name: 'session'});
Tai ypač naudinga, kai reikia išsaugoti sesijos būseną tarp skirtingų skriptų paleidimų. Galite išsaugoti cookies į failą ir vėliau juos atstatyti:
const fs = require('fs');
// Išsaugoti
const cookies = await page.cookies();
fs.writeFileSync('cookies.json', JSON.stringify(cookies));
// Atstatyti
const savedCookies = JSON.parse(fs.readFileSync('cookies.json'));
await page.setCookie(...savedCookies);
Jei svetainė naudoja HTTP autentifikaciją, Puppeteer turi specialų metodą:
await page.authenticate({
username: 'user',
password: 'pass'
});
Performance optimizacija ir resursų valdymas
Puppeteer gali būti resursų ėdrunas, ypač kai paleidžiate kelias naršyklės instancijas vienu metu. Štai keletas patarimų, kaip optimizuoti veikimą.
Pirma, išjunkite nereikalingas funkcijas:
const browser = await puppeteer.launch({
headless: true,
args: [
'--disable-gpu',
'--disable-dev-shm-usage',
'--disable-setuid-sandbox',
'--no-sandbox',
'--no-zygote',
'--single-process'
]
});
Šie argumentai ypač svarbūs Docker konteineriuose ar serverinėse aplinkose su ribotais ištekliais.
Antra, pakartotinai naudokite naršyklės instanciją, bet kurkite naujus puslapius:
const browser = await puppeteer.launch();
// Vietoj to, kad kiekvienam URL kurtumėte naują browser
for (const url of urls) {
const page = await browser.newPage();
await page.goto(url);
// darykite ką reikia
await page.close(); // svarbu uždaryti!
}
await browser.close();
Trečia, apribokite vienu metu atvirų puslapių skaičių. Jei turite apdoroti šimtus URL, nenaudokite `Promise.all()` su visais iš karto:
const pLimit = require('p-limit');
const limit = pLimit(5); // maksimaliai 5 vienu metu
const promises = urls.map(url =>
limit(() => processUrl(url))
);
await Promise.all(promises);
Ketvirta, stebėkite atminties naudojimą. Puppeteer turi tendenciją „leakinti” atmintį, jei netinkamai valdote resursus:
// Visada uždarykite puslapius
await page.close();
// Uždarykite naršyklę baigę darbą
await browser.close();
// Jei paleidžiate ilgai veikiantį procesą, periodiškai perkraukite naršyklę
if (processedCount % 100 === 0) {
await browser.close();
browser = await puppeteer.launch();
}
Kas toliau: ateitis ir alternatyvos
Puppeteer tebėra vienas populiariausių naršyklės automatizavimo įrankių, bet konkurencija nemiegoja. Playwright, kurį sukūrė buvę Puppeteer kūrėjai, siūlo cross-browser palaikymą (Chrome, Firefox, Safari) ir kai kurias papildomas funkcijas. Jei jums reikia testuoti keliose naršyklėse, Playwright gali būti geresnis pasirinkimas.
Tačiau Puppeteer turi svarbų pranašumą – stabilumą ir brandą. Jis egzistuoja ilgiau, turi didesnę bendruomenę ir daugiau dokumentacijos. Be to, jei jums reikia tik Chrome/Chromium palaikymo, Puppeteer yra lengvesnis ir paprastesnis.
Dar viena tendencija – serverless funkcijos. AWS Lambda, Google Cloud Functions ir kiti serverless provideriai vis labiau palaiko Puppeteer. Egzistuoja specialūs paketai kaip `chrome-aws-lambda`, kurie optimizuoja Puppeteer darbui serverless aplinkoje.
Praktiškai kalbant, jei šiandien pradedate naują projektą, kuriame reikia naršyklės automatizavimo, Puppeteer vis dar yra puikus pasirinkimas. Jis stabilus, greitas, turi puikią dokumentaciją ir didžiulę bendruomenę. Taip, Playwright auga, bet Puppeteer niekur nedingsta.
Svarbiausia – pradėti eksperimentuoti. Įdiekite Puppeteer, pabandykite paprastus pavyzdžius, palaipsniui pereikite prie sudėtingesnių scenarijų. Netrukus pastebėsite, kad daugelis užduočių, kurias anksčiau darėte rankiniu būdu, gali būti automatizuotos per kelias eilutes kodo. Ir tai yra tikroji Puppeteer vertė – ne tik technologija, bet ir laisvė nuo rutininių užduočių.
