Создание скриншотов веб-сайтов с помощью Puppeteer может оказаться сложной задачей. Нас ждет множество подводных камней. Давайте рассмотрим Puppeteer на множестве проблем со «скриншотами» и устраним возникающие подводные камни.
Делать скриншоты сайтов с помощью Кукловода может быть непросто. Нас ждет множество подводных камней. Давайте рассмотрим Puppeteer на множестве проблем со скриншотами и устраним возникающие подводные камни.
Я опубликовал рабочие примеры Puppeteer, чтобы лучше понять контекст решения и скопировать его, если это необходимо.
Познакомьтесь с кукловодом
Это библиотека Node, которая взаимодействует с браузерами, поддерживающими протокол Chrome DevTools Protocol (CDP). Это не только Chrome и Chromium, но и Firefox частично поддерживает CDP.
Протокол Chrome DevTools был разработан для управления, отладки и проверки Chromium и Chrome на низком уровне.
Итак, подумайте о высокоуровневом API Puppeteer по протоколу Chrome DevTools, который позволяет вам делать в браузере все, что вы можете сделать вручную:
- Извлекайте данные из SPA, отправляйте форму, вводите текст, выполняйте сквозное тестирование пользовательского интерфейса и другие задачи, связанные с автоматизацией.
- Отладка проблем с производительностью.
- Запускайте, отлаживайте и тестируйте расширения Chrome.
- Предварительный рендеринг SPA для создания статического сайта. Но для Google SEO это не имеет значения, так как сегодня Google отображает JavaScript для каждой страницы.
- И угадай что? Делайте скриншоты и PDF-файлы страниц.
Создание скриншотов и PDF-файлов с помощью Puppeteer — основная тема поста.
Кукольная архитектура и внутренности для любопытных
Вы можете пропустить этот раздел. Начинать пользоваться библиотекой не требуется. Но я люблю исследовать внутренности библиотек, которые использую, и вы можете тоже.
Облегченный вариант Puppeteer
Во-первых, доступны две версии библиотеки: puppeteer-core и puppeteer. Вы должны использовать puppeteer-core, когда собираетесь самостоятельно управлять экземплярами браузера, или вам это не нужно, в противном случае придерживайтесь puppeteer.
Три простых примера, которые приходят мне на ум с puppeteer-core:
- Вы используете CDP из расширения, поэтому вам не нужно загружать Chrome или Chromium.
- Вы хотите использовать другую сборку Chrome, Chromium или Firefox.
- У вас есть работающий кластер браузеров или отдельный экземпляр браузера на другом компьютере.
При использовании puppeteer-core вы должны убедиться, что используете совместимую версию браузера. Но библиотека puppeteer загружает и запускает для вас совместимую версию экземпляра Chromium, не беспокоясь.
Кукловод Альтернативы
Их намного больше, но самые популярные два:
- Самая старая альтернатива созданию скриншотов — использование протокола Selenium WebDriver.
- Второй — Драматург, и он хороший. Это конкурент Кукловода.
Playwright и Puppeteer имеют совместимый API, но Playwright поддерживает больше браузеров. Так что, если вам нужно делать скриншоты в разных браузерах, лучше используйте Playwright. Кстати, над Драматургом работают ведущие участники Кукловода. Но библиотека до сих пор считается новой.
Практические примеры использования Puppeteer для создания скриншотов
Прежде чем приступить к работе с Puppeteer, давайте установим его с помощью npm:
$ npm i puppeteer
Простой скриншот
Чтобы сделать простой снимок экрана с помощью Puppeteer и сохранить его в файл, вы можете использовать следующий код:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'example.png' });
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Всегда закрывайте браузер, чтобы избежать утечки ресурсов.
Разрешение и дисплей Retina
Чтобы избежать размытия изображения на дисплеях с высоким разрешением, таких как Retina Display, вы можете изменить свойства окна просмотра width, height и deviceScaleFactor:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({
width: 2880, // default: 800
height: 1800, // default: 600
deviceScaleFactor: 2 // default: 1
});
await page.goto('https://apple.com');
await page.screenshot({ path: 'apple.com.png' });
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Это называется пиксельные скриншоты.
Полный скриншот страницы
Puppeteer умеет делать скриншот прокручиваемой страницы. Используйте опцию fullPage:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://apple.com');
await page.screenshot({
path: 'apple.com.png',
fullPage: true
});
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Но это не будет работать с «бесконечной» прокруткой.
Скриншот полной страницы с «бесконечной» прокруткой
Это выходит за рамки статьи, но сложно найти случай, когда нужно делать скриншоты с «бесконечной» прокруткой сайтов. И если вам нужно, вы можете использовать следующий алгоритм:
- Загрузите страницу, подождите, пока она загрузится.
- Прокрутка до тех пор, пока размер страницы не изменится.
- Сделайте снимок экрана.
Если вы попытаетесь сделать это с помощью Twitter или Instagram для учетной записи с большим количеством сообщений, вы обязательно получите аварийный экземпляр браузера из-за исчерпания памяти.
Дождитесь полной загрузки страницы
Хорошей практикой является дождаться полной загрузки страницы, чтобы сделать снимок экрана:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({});
try {
const page = await browser.newPage();
await page.goto('https://apple.com/', {
waitUntil: 'networkidle0',
});
await page.screenshot({ path: 'apple.com.png' });
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Это немного магии, но событие networkidle0 является эвристическим для определения состояния загрузки страницы. Команда Puppeteer считает, что он работает достаточно хорошо во многих реальных случаях использования.
Но если вам нужно дождаться, пока какой-то элемент отрендерится и станет видимым, вам нужно добавить Page.waitForSelector():
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({});
try {
const page = await browser.newPage();
await page.goto('https://example.com/', {
waitUntil: 'networkidle0',
});
const selector = 'div';
await page.waitForSelector(selector, {
visible: true,
});
await page.screenshot({ path: 'example.com.png' });
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Вы также можете подождать:
- для селектора или функции или тайм-аута;
- для выбора файла;
- для рамы;
- по функции;
- для навигации;
- при простое сети;
- по запросу;
- за ответ;
- для селектора;
- на тайм-аут;
- и для XPath.
Скриншот области страницы
Чтобы сделать скриншот области страницы, используйте опцию clip:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://apple.com');
await page.screenshot({
path: 'apple.com.png',
clip: {
x: 100,
y: 100,
width: 800,
height: 800
},
});
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Но если вам нужно сделать скриншот элемента, есть способ получше.
Скриншот конкретного элемента
Puppeteer позволяет сделать скриншот любого элемента на веб-странице:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
const selector = 'body > div:first-child';
await page.waitForSelector(selector);
const element = await page.$(selector);
await element.screenshot({
path: 'example.com.png',
});
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Как видите, важно убедиться, что элемент готов.
Скриншот с прозрачным фоном
Puppeteer предоставляет полезную возможность скрыть фон сайта. Просто установите omitBackground в true:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({
path: 'example.com.png',
omitBackground: true,
});
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Вы запускали код? Если да, то вы заметили, что на скриншоте нет прозрачного фона. Это происходит потому, что пропуск фона работает только для элементов с прозрачным фоном.
Поэтому, если на вашем целевом сайте нет прозрачного фона, и вы хотите сделать его принудительным, вы можете использовать JavaScript для выполнения этой задачи. Измените фон тела в функции оценки:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.goto('https://example.com');
await page.evaluate(() => {
document.body.style.background = 'transparent';
});
await page.screenshot({
path: 'example.com.png',
omitBackground: true,
});
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Скриншот в формате Base64
Вы создаете Puppeteer как сервис и не хотите хранить файлы снимков экрана. Вы можете вернуть скриншот в формате кодировки Base64:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({});
try {
const page = await browser.newPage();
await page.goto('https://example.com/');
const base64 = await page.screenshot({ encoding: "base64" })
console.log(base64);
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Вы получите строку, которую можно передать другому сервису или даже где-то сохранить.
Генерировать PDF вместо PNG
Относительно легко создать PDF вместо PNG:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({});
try {
const page = await browser.newPage();
await page.goto('https://example.com/', {
waitUntil: 'networkidle0',
});
const selector = 'div';
await page.waitForSelector(selector, {
visible: true,
});
await page.pdf({path: 'example.com.pdf', format: 'a4'})
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Посмотрите все возможные варианты Puppeteer PDF. Это захватывающая и сложная проблема, которая заслуживает отдельного поста.
Это зависит от вашего варианта использования, но также рассмотрите возможность использования PDFKit для программного создания PDF.
Блокировка рекламы при использовании Puppeteer
Я не использую никаких расширений для блокировки рекламы, потому что жизнь тяжелая, и всем нужно каким-то образом зарабатывать деньги. Если я могу помочь сайтам поддерживать и выживать, не блокируя рекламу, я это сделаю.
Но когда вы тестируете свой сайт или сайт клиента, вам может потребоваться заблокировать рекламу. Есть 2 способа сделать это:
- Перехватывать и блокировать запросы, загружающие рекламу на сайт.
- Используйте расширение, оптимизированное именно для решения этой проблемы.
Первый сложный и сильно зависит от сайта, скриншоты которого вы делаете. Но использование расширения — это хорошо масштабируемый подход, который работает «из коробки».
Установите puppeteer-extra и puppeteer-extra-plugin-adblocker в дополнение к пакету puppeteer:
$ npm i puppeteer-extra puppeteer-extra-plugin-adblocker
А затем используйте его:
'use strict';
const puppeteer = require('puppeteer-extra');
const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');
puppeteer.use(AdblockerPlugin());
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
// ads are blocked automatically
await page.goto('https://www.example.com');
await page.screenshot({
path: 'example.com.png',
fullPage: true,
});
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Большинство страниц содержат рекламу и трекеры, которые потребляют много трафика и долго загружаются. Поскольку при блокировке рекламы и трекеров выполняется меньше запросов и выполняется меньше JavaScript, страницы загружаются значительно быстрее.
Блок-трекеры
Чтобы делать скриншоты быстрее, вы можете заблокировать трекеры. Это поможет ускорить рендеринг. Плагин блокировки рекламы может помочь нам с этой проблемой.
Не забудьте установить puppeteer-extra и puppeteer-extra-plugin-adblocker дополнительно к puppeteer пакету:
$ npm i puppeteer-extra puppeteer-extra-plugin-adblocker
А затем используйте его:
'use strict';
const puppeteer = require('puppeteer-extra');
const AdblockerPlugin = require('puppeteer-extra-plugin-adblocker');
puppeteer.use(AdblockerPlugin({
blockTrackers: true, // default: false
}));
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
// ads are blocked automatically
await page.goto('https://www.example.com');
await page.screenshot({
path: 'example.com.png',
fullPage: true,
});
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Если вам нужно блокировать только трекеры, но не блокировать рекламу, просто используйте перехватчик запросов.
Предотвращение обнаружения Кукловода
Некоторые сайты могут блокировать ваш скрипт Puppeteer из-за пользовательского агента, и это легко исправить:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const options = {
args: [
'--user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10puppeteer7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"'
],
headless: true,
};
const browser = await puppeteer.launch(options);
try {
const page = await browser.newPage();
await page.goto('https://www.example.com');
await page.screenshot({
path: 'example.com.png',
fullPage: true,
});
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Есть также много других хаков, чтобы гарантировать, что Puppeteer не будет обнаружен, но вы можете сэкономить время, используя готовый плагин puppeteer-extra-plugin-stealth для скрытого режима. Установите его в дополнение к пакету puppeteer:
$ npm i puppeteer-extra puppeteer-extra-plugin-stealth
А затем используйте:
'use strict';
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.evaluateOnNewDocument(() => {
const newProto = navigator.__proto__;
delete newProto.webdriver;
navigator.__proto__ = newProto;
});
await page.goto('https://bot.sannysoft.com');
await page.waitForTimeout(5000);
await page.screenshot({
path: 'stealth.png', fullPage: true
});
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Важный! Как видите, я удаляю свойство webdriver, так как стелс-плагин пропускает этот хак и с помощью свойства webdriver можно обнаружить использование Кукловода.
Скрыть баннеры cookie
Это сложная задача для реализации в общем, но вы можете принять файл cookie, найдя селектор кнопки «Принять или отклонить» и щелкнув ее.
Использование базовой аутентификации доступа с Puppeteer
Если ваша страница защищена базовой HTTP-аутентификацией доступа, единственное, что вам нужно сделать, это указать имя пользователя и пароль перед загрузкой и сделать скриншот страницы:
'use strict';
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.authenticate({
'username':'YOUR_BASIC_AUTH_USERNAME',
'password': 'YOUR_BASIC_AUTH_PASSWORD'
});
await page.goto('https://example.com');
await page.screenshot({ path: 'example.png' });
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Использование прокси для Puppeteer
В случае, если вам нужно использовать прокси для создания скриншота с помощью Puppeteer, вы можете указать прокси для всего браузера:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
args: ['--proxy-server=127.0.0.1:9876']
});
try {
const page = await browser.newPage();
await page.goto('https://example.com/', {
waitUntil: 'networkidle0',
});
await page.screenshot({ path: 'example.com.png' })
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Но в некоторых случаях вы можете захотеть использовать прокси-сервер на уровне страницы без повторного создания экземпляра браузера. В этом случае можно установить puppeteer-page-proxy:
npm i puppeteer-page-proxy
И используйте его для указания прокси для каждой страницы:
const puppeteer = require('puppeteer');
const useProxy = require('puppeteer-page-proxy');
(async () => {
const browser = await puppeteer.launch({});
try {
const page = await browser.newPage();
useProxy(page, '127.0.0.1:9876')
await page.goto('https://example.com/', {
waitUntil: 'networkidle0',
});
await page.screenshot({ path: 'example.com.png' })
} catch (e) {
console.log(e)
} finally {
await browser.close();
}
})();
Добавлена поддержка эмодзи, японского, арабского и других нелатинских языков в Puppeteer.
Если вы запускаете Puppeteer в ОС без поддержки эмодзи, вам необходимо установить общесистемные шрифты для поддержки эмодзи. То же самое может произойти с неанглийскими символами, такими как китайский, японский, корейский, арабский, иврит и т. д.
Чтобы заставить Puppeteer отображать смайлики, вы можете использовать Noto Fonts, опубликованные в соответствии с SIL Open Font License (OFL) v1.1.
Вам нужно искать и как установить шрифты для вашей хост-ОС.
Хорошего дня 👋
Я разместил много примеров Puppeteer и надеюсь, что помог вам решить ваши проблемы со скриншотами с помощью Puppeteer. Я описал каждую проблему, с которой столкнулся, и пути ее решения.
Первоначально опубликовано на https://screenshotone.com 5 января 2022 г.