Блог Froxy | Новости, полезные статьи о использовании прокси

Puppeteer веб-скрапинг: руководство с практическим подходом

Written by Команда Froxy | 17.04.2025 9:00:00

Ранее мы уже упоминали разные web-драйвера и библиотеки с фреймворками для создания своих программ парсинга сайтов. В частности, к наиболее популярным решениям для управления headless-браузерами относятся Playwright, Selenium и Puppeteer. У каждого из них свои особенности и области применения. Как минимум присутствуют определённые требования по используемым языкам программирования.

В этой статье расскажем о библиотеке Puppeteer, о том, как она помогает с тестированием приложений и web-парсингом (скрейпингом сайтов). А также рассмотрим пошаговое руководство по написанию своего собственного парсера с Puppeteer и наиболее вероятные ситуации применения.

Преимущества web-скрапинга с Puppeteer

Puppeteer – это Node.js-библиотека для управления браузерами Google Chrome и Chromium по протоколу DevTool Protocol (сокращённо CDP). Библиотека имеет открытый исходный код и была представлена для демонстрации возможностей встроенного API веб-браузера (за её написанием стоит та же команда Google, что и отвечает за направление браузера, официальный сайт). До 2017 года Хромом можно было управлять только через сторонние библиотеки web-драйверов, так как собственного API-интерфейса у него не было. За организацию API обычно отвечали внешние программные решения, такие как Playwright и Selenium.

Подробное сравнение Playwright vs Puppeteer

После релиза CDP (Chrome Devtool Protocol) с браузером стало возможно общаться напрямую, например, с помощью специальных флагов и команд в консоли, а также с использованием API-интерфейса.

Чтобы показать возможности управления и была написана библиотека Puppeteer.

Чуть позже поддержка CDP появилась и в существовавших web-драйверах (Playwright, Selenium и пр.), а также стали релизиться другие высокоуровневые прослойки, такие как Chromedp.

Но сейчас не о них, а только о Puppeteer.

Ключевые возможности, которые предоставляет библиотека Puppeteer для web-скрапинга:

  • Поддержка одного из самых популярных языков web-программирования – JavaScript (Node.js). Он в свою очередь кроссплатформенный.
  • Прямое управление headless-браузерами Chromium и Google Chrome. Даже уже установленными версиями в операционной системе (без развёртывания экземпляра в изолированной среде).
  • Эта библиотека может использоваться для автоматизации тестирования браузерных расширений (для Google Chrome).
  • Есть своя реализация двунаправленного веб-драйвера – WebDriver BiDi (начиная с версии 23 Puppeteer позволяет управлять не только Хромом, но и браузером Firefox).
  • Максимально простой и понятный синтаксис команд.
  • Встроенный синтаксический анализатор, который может искать нужные элементы в DOM-структуре (в HTML-коде) и возвращать заданные атрибуты: текст, ссылки и прочее.
  • Поддержка асинхронности из коробки (парсер может ждать отрисовки определённого элемента страницы, вообще всего JS-кода или определённых событий).
  • Полная совместимость с CDP-протоколом – создание скриншотов в браузере, PDF-версий страниц, отладка и прочее.
  • Обработка cookie-файлов и работа с браузерными профилями.
  • Поддержка работы через переменные среды, в том числе через ротируемые прокси.
  • Возможность эмулирования действий пользователя и работа с полями, файлами, формами ввода.

Обратите внимание! Каждый выпуск библиотеки тестирования и автоматизации web-скрапинга Puppeteer тесно связан с определённым выпуском браузера Chromium. Тут важно помнить, что Puppeteer – это эталонная реализация библиотеки для автоматизации браузера с задействованием его API. Отсюда и такой интересный подход.

Но из-за этого возникает ряд ограничений. Разработчики библиотеки концентрируют свои усилия только на одном языке программирования и не работают в сторону оркестрации, как это делают более продвинутые братья по цеху (Playwright и Selenium). Соответственно, Puppeteer подходит только для определённых узких задач. В нашем случае – для написания парсеров на языке JavaScript.

Руководство по веб-парсингу с использованием Puppeteer

Начинать веб-скрапинг Puppeteer следует с установки и настройки среды NodeJS. Будем рассматривать процесс с привязкой к операционной системе Windows, как наиболее популярной платформы среди рядовых пользователей. Вместо этого вы можете установить Linux-дистрибутив (в том числе в среде Windows, например, через подсистему WSL или через платформы виртуализации, VMware, VirtualBox и т.п.) или обеспечить поддержку систем контейнеризации (например, Docker). Во многих Linux-системах установка NodeJS выполняется через штатный пакетный менеджер или через подсистему пакетов Snap.

Мы пойдём более простым путём – NodeJS, а соответственно и любой скрипт web-парсинга Puppeteer, может работать в Windows без слоёв виртуализации.

Установка окружения NodeJS для скрапинга с Puppeteer в Windows

Скачайте инсталляционный пакет с официального сайта NodeJS. Там же есть примеры команд для установки через интерфейс консоли (PowerShell).

Если вам принципиально наличие менеджера версий NodeJS, то можно выполнить установку через nvm, fnm, Brew или Docker.

Путь установки NodeJS по умолчанию – «C:\Program Files\nodejs\». Но вы можете изменить его на своё значение.

Не забудьте разрешить установку менеджера пакетов NPM и добавление путей (PATH) в переменные среды окружения Windows.

Дождитесь окончания установки (скрипт может скачивать и доустанавливать разные библиотеки и программы, например, репозиторий Chocolatey, редактор Microsoft Visual Studio, среду исполнения .Net, язык программирования Python и т.п.).

Установка Puppeteer

Сначала создайте каталог, в котором будет храниться скрипт вашего Puppeteer-парсера:

cd c:\mkdir puppeteer-web-scraping-scriptcd puppeteer-web-scraping-script

Всё, мы в нужном каталоге. Теперь нужно проверить версию node и менеджера пакетов npm командами:

npm -vnode –version

Установите Puppeteer:

npm install puppeteer

В папке с вашим проектом появятся новые каталоги и файлы (в отдельной папке с модулями будет около 100 подпапок, в том числе @puppeteer, chromium-bidi, devtools-protocol, http-proxy-agent и другие).

Создание первого web-парсера Puppeteer

В корне каталога проекта создайте текстовый файл, назовите его, например, «first-puppeteer-web-scraping-script». Смените расширение .txt на .js, должно получиться «first-puppeteer-web-scraping-script.js». Откройте этот файл в любом текстовом редакторе (мы используем Notepad++).

Наполните файл содержимым (скопируйте и вставьте код ниже):

const puppeteer = require('puppeteer')// Подключаем (импортируем) библиотеку с "кукловодом"async function run(){// Главная функция будет работать асинхронноconst browser = await puppeteer.launch({// Тут мы создаём экземпляр headless-браузера и загружаем его. Тоже асинхронно.// Чтобы все процессы работы скрипта можно было увидеть "вживую" отключим режим скрытия.headless: false,// Для этого установим флаг в "false". Если вы хотите, чтобы браузер работал без отображения окон, в фоне, изменить флаг на "true"ignoreHTTPSErrors: true,// Тут мы просим браузер игнорировать ошибки подключения по протоколу HTTPS})// Открываем в браузере новую вкладкуlet page = await browser.newPage();// И просим открыть конкретный URL-адресawait page.goto('http://httpbin.org/html', {// Ждём, пока загружается DOM-структураwaitUntil: 'domcontentloaded',});// Выводим найденный HTML-контент в консолиconsole.log(await page.content());// Закрываем вкладку и браузерawait page.close();await browser.close();}run();

Сохраните файл и запустите скрипт командой:

node first-puppeteer-web-scraping-script.js

Сначала откроется окно браузера, затем оно закроется и в консоли выведется HTML-содержимое страницы.

Всё, наш первый парсер отработал на 100%: он открыл браузер (вместо этого браузер может работать в фоне, без отображения графического интерфейса), перешёл на конкретную страницу и скопировал HTML-код.

Какие функции Puppeteer мы использовали:

  • puppeteer.launch() – загрузка браузера, внутри могут использоваться дополнительные флаги и опции.
  • newPage() – открытие новой вкладки.
  • goto() – переход к нужной странице (web-адресу).
  • content() – возврат HTML-содержимого страницы. При желании его можно пропустить через синтаксический анализатор и выбрать (сохранить) только нужные элементы. Об этом расскажем ниже.

Событие 'domcontentloaded' это штатное событие JavaScript, которое обозначает, что HTML-страница полностью загружена и браузер построил дерево DOM-структуры.

Вместо этого вы можете ждать загрузки определённого HTML-тега или селектора, а также выжидать время (таймаут). Например:

await page.waitForSelector('h2', {timeout: 3_000})// Ждём заголовок H2 в течение 3 секунд

Резидентные прокси

Лучшие прокси-серверы для доступа к ценным данным со всего мира.

Выбрать тариф $1.99, 100Mb

Поиск элементов на странице в web-парсере Puppeteer

Давайте немного усложним задачу и попробуем выбрать и сохранить только нужные нам элементы страницы.

Библиотека web-скрапинга Puppeteer поддерживает синтаксис анализатора XPath, поиск по имени, роли и тексту, а также поиск по префиксам и Shadow DOM (документация по синтаксису запросов).

Класс «page» в Puppeteer поддерживает большое количество встроенным методов. Среди них действия пользователя, создание сессий, запись Cookie, скриншотов, скринкастов, PDF-версий страниц, установка юзер-агента и т.п. Все подробности в официальной документации.

Нас пока интересуют только методы для поиска конкретных элементов:

  • page.$() – ищет на странице указанный элемент и выводит первое найденное значение.
  • page.$$() – ищет на странице массив элементов, которые совпадают с указанным атрибутом.

У этих методов есть вариации с признаком “eval”, которые будут сначала исполнять JS-код страницы и только потом приступать к синтаксическому разбору.

Пример скрипта, который будет искать информацию о товарах на специальной тестовой странице:

const puppeteer = require('puppeteer')// Подключаем (импортируем) библиотеку с "кукловодом"async function run(){// Главная функция будет работать асинхронноconst browser = await puppeteer.launch({// Тут мы создаём экземпляр headless-браузера и загружаем его. Тоже асинхронно.// Чтобы все процессы работы скрипта можно было увидеть "вживую" отключим режим скрытия.headless: false,// Для этого установим флаг в "false". Если вы хотите, чтобы браузер работал без отображения окон, в фоне, изменить флаг на "true"ignoreHTTPSErrors: true,// Тут мы просим браузер игнорировать ошибки подключения по протоколу HTTPS})// Открываем в браузере новую вкладкуlet page = await browser.newPage();// И просим открыть конкретный URL-адрес// В нашем случае это пример страницы с карточками продуктовawait page.goto('https://web-scraping.dev/products', {// Ждём, пока загружается DOM-структураwaitUntil: 'domcontentloaded',});// Для примера найдём первый заголовок h3 (он имеет класс "mb-0")// ... и скопируем из него текст, он прописан в теге <a>let textfirsturl = await (await page.$('.mb-0 a')).evaluate( node => node.innerText);// заодно скопируем саму ссылку, она в том же тегеlet firsturl = await (await page.$('.mb-0 a')).evaluate( node => node.getAttribute("href"));// Выводим найденный HTML-контент в консолиconsole.log("First Product:", textfirsturl, "Its URL:", firsturl);// А тут найдём сразу все товары и ссылки// По факту alllinks изначально является массивом, так как функция page.$$ всегда возвращает массивlet alllinks = await page.$$('.mb-0 a');//for (const link of alllinks){console.log("Product:", await link.evaluate( node => node.innerText));console.log("URL:", await link.evaluate( node => node.getAttribute("href")));}// Закрываем вкладку и браузерawait page.close();await browser.close();}run();

Сохраняем и запускаем наш скрипт web-скрапинга Puppeteer.

Вот так будет выглядеть вывод в консоли:

Обработка бесконечной прокрутки при web-парсинге Puppeteer

Самая каверзная задача парсинга динамических сайтов (изменяющихся по JavaScript-событиям) – это обработка бесконечной прокрутки. Контент при каждой новой попытке скроллинга обновляется, так как в конец списка добавляются новые элементы.

Ниже пример нашего скрипта для парсинга списка отзывов с бесконечной подгрузкой.

Вы можете выставить свои параметры задержек и максимальное количество попыток прокрутки.

const puppeteer = require('puppeteer')// Подключаем (импортируем) библиотеку с "кукловодом"// Сначала опишем функцию, которая будет отвечать за обработку скроллингаasync function scrollPageDown(page) {// Предыдущее значение высоты экранаlet prevHeight = -1;// Максимальное количество итераций скроллинга (если страница перестанет подгружать данные раньше, то цикл завершится досрочно)let maxScrolls = 50;// Переменная для подсчёта сделанных итерацийlet scrollCount = 0;// Описываем логику функции// Пока текущее количество итераций меньше максимального, выполняем попытки прокруткиwhile (scrollCount < maxScrolls) {// Скроллим страницу вниз через специальный метод для класса Page, в качестве параметра передаём значение текущей высоты элемента bodyawait page.evaluate('window.scrollTo(0, document.body.scrollHeight)');// Ждём пока страница загрузится, хотя бы 1 секунду, то есть 1000 миллисекундawait new Promise(resolve => setTimeout(resolve, 1000));// Вычисляем новую высоту тега bodylet newHeight = await page.evaluate('document.body.scrollHeight');// Если новое значение высоты равно предыдущемуif (newHeight == prevHeight) {// Выводим текущее значение счётсчика скролловconsole.log("Scrolls Count:", scrollCount);// Выходим из циклаbreak;}// Делаем новое значение высоты body предыдущимprevHeight = newHeight;// Добавляем счётчик итераций скроллингаscrollCount += 1;}};// Тут описываем логику функции скрапинга с Puppeteerasync function parseReviews(page) {// Ищем все элементы с классом testimonial, он используется в качестве корневого для каждого блока с рейтингомlet elements = await page.$$('.testimonial');// Создаём массив для результатовlet results = [];// Перебираем массив найденных элементовfor (let element of elements) {//Ищем значение рейтинга, в нашем случае это пиктограммы звёздlet rate = await element.$$eval('span.rating > svg', elements => elements.map(el => el.innerHTML))results.push({// Собираем текст отзыва из элемента с классом text"text": await element.$eval('.text', node => node.innerHTML),// Считаем количество звёздочек"rate" : rate.length});}// Возвращаем массивreturn results;};// Тут опишем логику главной функции - что и когда запускатьasync function run(){// Загружаем экземпляр headless-браузера через Puppeteerconst browser = await puppeteer.launch({// Для наглядности отключаем headless-режимheadless: false,// Игнорируем ошибки HTTPSignoreHTTPSErrors: true,});// Открываем новую вкладкуpage = await browser.newPage();// Переходим на целевую страницуawait page.goto('https://web-scraping.dev/testimonials/');// Сначала выполняем все процедуры скроллинга (пока не упрёмся в лимит итераций или пока страница не перестанем менять свою высоту)await scrollPageDown(page);// Создаём массив с отзывами и наполняем его данными (парсим с Puppeteer)reviews = await parseReviews(page);// Закрываем браузерawait browser.close();// Массив выводим в консолиconsole.log(reviews);};run();

 

После запуска скрипта парсинга Puppeteer, вывод в консоли будет примерно как на скрине ниже.

В нашем случае попытки прокрутки страниц закончились на 7 итерации.

Обход списка страниц при web-парсинге Puppeteer

Другая ситуация – обход списка страниц.

На тестовом целевом сайте страницы с перечнем товаров статичные, но присутствует деление на выборки по 10 элементов. Чтобы обойти эти списки напишем скрипт, который будет менять параметр пагинации и подставлять его в URL-адрес новой страницы.

Вот так будет выглядеть код нашего Puppeteer web-скрапера:

const puppeteer = require("puppeteer");// Подключаем библиотеку Puppeteer// Сначала описываем логику парсинга конкретной страницыasync function parseProducts(page) {// Ищем все блоки с описаниями продуктов, это div-элемент с классами "row" и "product"let boxes = await page.$$('div.row.product');// Создаём массивlet results = [];// Перебираем элементы в циклеfor(let box of boxes) {results.push({// Заголовок товара прячется за тегом "a", но собрать нужно только текстовое содержимое"title": await box.$eval('a', node => node.innerHTML),// Ссылка на страницу товара. Тот же тег "a", но уже собираем значение атрибута Href"link": await box.$eval('a', node => node.getAttribute('href')),// Цена товара, вынесена в блок div с классом price"price": await box.$eval('div.price', node => node.innerHTML)})}return results;}// Тут описываем логику основной функции парсинга с Puppeteerasync function run(){// Загружаем headless-браузерconst browser = await puppeteer.launch({// Делаем его видимым (отключаем headless-режим)headless: false,// Игнорируем ошибки HTTPSignoreHTTPSErrors: true,});// Открываем новую страницуpage = await browser.newPage();// Созадём массивdata = [];// Пока количество страниц к перебору менее 5... (то есть не более 4)for (let i=1; i < 5; i++) {//Переходим на страницу с нужным номером пагинации, номер берём из итерации цикла (какой цикл, такой и номер)await page.goto(`https://web-scraping.dev/products?page=${i}`)// Наполняем массив с описаниями товаров (выполняем функцию парсинга)products = await parseProducts(page)// Дополняем массив с данными массивом из функции парсингаdata.push(...products);}// Выводим массив в консольconsole.log(data);// Закрываем браузерbrowser.close();}run();

Блокировка загрузки ненужного содержимого

Чтобы сэкономить трафик и ускорить процесс загрузки страниц, вы можете отключить ненужный вам контент. Например, самый тяжёлый: изображения, видео, шрифты и т.п.

Блокировка осуществляется за счёт специальных констант:

const blockResourceType = ['здесь', 'список', 'типов', 'ресурсов', 'например', 'image', 'font', 'и прочие',];const blockResourceName = ['здесь', 'список', 'ресурсов', 'например', 'cdn.api.twitter', 'fontawesome', 'google-analytics', 'googletagmanager',];// Тогда headless-браузер нужно дополнительно настроить с помощью флаговconst page = await browser.newPage();// А вот так выглядит активация перехвата запросовawait page.setRequestInterception(true);// В этом случае мы получаем возможность инспектирования каждого запроса браузера// И тогда только мы настраиваем логику: какие запросы браузеру отправлять, а какие нетpage.on('request', request => {const requestUrl = request._url.split('?')[0];if (// Если тип ресурса в списке блокируемых…(request.resourceType() in blockedResourceType) ||// Или если конкретный ресурс в списке блокируемых…blockResourceName.some(resource => requestUrl.includes(resource))) {// Исходящий запрос сбрасываетсяrequest.abort();// В остальных случаях} else {// Всё проходит как обычноrequest.continue();}});}
Поддержка экспертов

Наша команда поддержки поможет вам всегда оставаться онлайн и не останавливаться на достигнутом.

Получить помощь

Работа скрипта веб-скрапинга Puppeteer через прокси

Тут всё максимально просто и понятно. Достаточно в качестве аргумента запуска браузера передать параметры прокси. Пример:

// Открываем новый экземпляр headless-браузераconst browser = await puppeteer.launch({args: [ '--proxy-server=http://123.456.123.456:8080' ]});// А далее остальной код.

У такого подхода есть один минус – прокси прописывается на уровне всего браузера. Соответственно, чтобы пустить поток запросов через другой прокси-сервер, вам нужно открыть ещё один экземпляр браузера.

Существуют разные скрипты принудительной ротации прокси на уровне страниц и запросов, в том числе расширение proxy-chain для NodeJS.

Но мы рекомендуем более грамотное решение – использовать ротируемые прокси. Например, вы можете арендовать ротируемые мобильные, резидентные и серверные прокси у нас.

Тогда за ротацию прокси будет отвечать наш сервис. Backconnect-прокси подключатся к парсеру всего один раз. А в личном кабинете остаётся только определить логику ротации выходных IP-адресов: по времени или при каждом новом запросе.

Такой подход существенно снизит вероятность блокировки парсера и упростит процесс его написания.

Больше деталей в нашем отдельном материале о том, как парсить без блокировок.

Другие рекомендации

Максимально краткие советы:

  • Нужно следить за указанием user-агента, набором кук и другими параметрами цифровых отпечатков (разрешение экрана, поддерживаемые web-технологии, набор шрифтов в операционной системе и пр.).
  • Важно стараться эмулировать поведение пользователей.
  • Не стоит отправлять запросы через равные промежутки времени.

Из специфических рекомендаций для web-скрапинга Puppeteer:

  • Различные скрипты на web-серверах могут определять headless-браузер c Puppeteer на основе специальных атрибутов, которые передаются в HTTP-запросах (заголовках). Чтобы этого избежать, нужно дополнительно скрывать эти параметры. Делается это с помощью профильных плагинов, таких как puppeteer-stealth.

Заключение

Puppeteer – это действительно классный инструмент для написания своих скриптов автоматизации тестирования и для создания полнофункциональных парсеров сайтов. Под капотом есть всё необходимое для эмулирования поведения пользователей, для перехвата HTTP-запросов и блокирования ненужных ресурсов, для синтаксического анализа HTML-контента и даже для работы через прокси.

Но есть и нюансы – поддерживается только один язык программирования (JavaScript) и есть ограничения по привязке прокси к экземплярам браузеров.

Чтобы обеспечить максимальный комфорт парсинга без блокировок и оперативное масштабирование нужны специальные ротируемые прокси. А качественные мобильные и резидентные прокси с ротацией – это Froxy. Более 10 млн. IP в сети, ротация по времени и при каждом новом запросе.