Headless? Да, это значит, что у этого браузера нет графического интерфейса (GUI). Вместо того чтобы использовать мышь или сенсорное устройство для взаимодействия с визуальными элементами, вы используете интерфейс командной строки (CLI) для выполнения автоматизации.
Для headlesschrome доступно множество инструментов для веб-скрейпинга, и headlesschrome обычно решает множество проблем.
Вам также может понравиться: Как обнаружить и избежать обнаружения headlesschrome?
Что такое Puppeteer? Это библиотека Node.js, которая предоставляет высокоуровневый API для управления headlesschrome или Chromium, а также для взаимодействия с DevTools Protocol.
Сегодня мы детально изучим headlesschrome с использованием Puppeteer.
Как можно предположить, использование Puppeteer для веб-скрейпинга имеет несколько больших преимуществ:
В следующем примере мы выполним базовый веб-скрейпинг, чтобы помочь вам быстро начать работу с Puppeteer. Страница, которую мы выбрали для сканирования, — это раздел отзывов на Amazon для Apple AirPods Pro.
Но не волнуйтесь, перед этим нам нужно немного подготовиться:
Если нет, установите Node.js (LST) напрямую, а затем установите Puppeteer через менеджер пакетов npm в Node.js. Этот процесс может занять некоторое время, так как Puppeteer также нужно установить соответствующую версию Chrome.
npm i puppeteer
Вы также можете использовать этот демо, чтобы получить общее представление о Puppeteer. Не задерживайтесь на этом этапе, потому что позже мы детально рассмотрим использование Puppeteer и связанных сценариев.
По умолчанию в Puppeteer включен режим headless. Здесь режим headless отключен через puppeteer.launch({ headless: false }), чтобы вы могли видеть процесс сканирования.
import puppeteer from 'puppeteer';
// Запустите браузер и откройте новую вкладку
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Перейдите на указанный URL
await page.goto('https://developer.chrome.com/');
// Установите размер экрана
await page.setViewport({width: 1080, height: 1024});
// Найдите элемент поискового поля и введите текст в поисковое поле
await page.locator('.devsite-search-field').fill('automate beyond recorder');
// Подождите и нажмите на первый результат поиска
await page.locator('.devsite-result-item-link').click();
// Найдите полный заголовок с помощью уникальной строки
const textSelector = await page
.locator('text/Customize and automate')
.waitHandle();
const fullTitle = await textSelector?.evaluate(el => el.textContent);
// Выведите полный заголовок в консоль
console.log('The title of this blog post is "%s".', fullTitle);
// Закройте экземпляр браузера
await browser.close();
Puppeteer является асинхронной библиотекой с поддержкой обещаний, работающей через async await, что позволяет очень интуитивно представить ее функции. В приведенном выше демо и последующих примерах нет необходимости в асинхронных функциях. Это связано с тем, что "type": "module" в package.json настроен для работы как ES Modules.
Хорошо, давайте начнем.
Пожалуйста, сначала откройте раздел комментариев Apple AirPods Pro, затем нам нужно определить элементы, в которых мы хотим захватить контент. Вы можете открыть Devtools, нажав Ctrl + Shift + I (Windows/Linux) или Cmd + Option + I (Mac).

Puppeteer поддерживает множество методов выбора элементов (селекторы puppeteer), но наиболее рекомендуемым методом для начала является простой CSS. В приведенном выше примере также используется CSS-селектор .devsite-search-field.

Для сложных CSS-структур отладочная консоль может напрямую копировать CSS-селекторы. Щелкните правой кнопкой мыши по HTML-элементу, который нужно захватить, чтобы открыть меню > Копировать > Копировать селектор.
Но не рекомендуется так делать, потому что селекторы, скопированные из сложных структур, очень плохо читаются и не способствуют поддержке кода. Конечно, для некоторых простых выборок и личного тестирования и обучения это вполне допустимо.
Теперь, когда селектор элемента определен, мы можем использовать Puppeteer, чтобы попробовать захватить имя пользователя, которое я выбрал выше.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(
`https://www.amazon.com/Apple-Generation-Cancelling-Transparency-Personalized/product-reviews/B0CHWRXH8B/ref=cm_cr_dp_d_show_all_btm?ie=UTF8&reviewerType=all_reviews`
);
const username = await page.$eval('div[data-hook="genome-widget"] .a-profile-name', node => node.textContent)
console.log('[username]===>', username);
Как видите, в приведенном выше коде используется page.goto для перехода на указанную страницу. Затем page.$eval позволяет получить первый совпавший узел элемента и получить конкретные атрибуты этого узла через функцию обратного вызова.
Если вам повезет и вы не вызовете проверочную страницу Amazon, вы сможете успешно получить значение. Однако стабильный скрипт не может полагаться только на удачу, поэтому мы должны сделать некоторые улучшения.
Хотя мы и получили информацию об узле элемента через метод, описанный выше, мы должны учитывать и другие факторы: такие как скорость загрузки сети, правильно ли загружен элемент при прокрутке страницы к целевому элементу, и если ли необходимость в обработке проверочной страницы вручную.
Поэтому до завершения загрузки мы должны терпеливо ждать. Конечно, puppeteer также предоставляет нам соответствующие API для использования.
Часто используемый waitForSelector — это API, который ждет появления элемента. Мы можем использовать его для оптимизации кода выше, чтобы обеспечить стабильность скрипта. Просто используйте API waitForSelector перед вызовом page.$eval.
Таким образом, puppeteer будет ждать, пока страница загрузит элемент div[data-hook="genome-widget"] .a-profile-name, прежде чем выполнять последующий код.
await page.waitForSelector('div[data-hook="genome-widget"] .a-profile-name');
const username = await page.$eval('div[data-hook="genome-widget"] .a-profile-name', node => node.textContent)
Существуют и другие API для ожидания, которые можно использовать в различных сценариях. Давайте рассмотрим некоторые из них:
page.waitForFunction(pageFunction, options, ...args): Ожидает, пока указанная функция вернет true в контексте страницы.import puppeteer from 'puppeteer'
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// Ждите, пока `window.title` страницы не изменится на "Example Domain"
await page.waitForFunction('document.title === "Example Domain"');
console.log('Title has changed to "Example Domain"');
await browser.close();
page.waitForNavigation(options): ожидает изменения состояния перехода. По умолчанию это событие срабатывает при переходе на новую страницу или перезагрузке.import puppeteer from 'puppeteer'
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// Ожидание перехода, вызванного нажатием на ссылку
await page.waitForNavigation({ timeout: 1000 });
console.log('Navigation occurred.');
await browser.close();
page.waitForRequest(urlOrPredicate, options): Ожидает запросов, соответствующих указанному URL или условной функции.import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com');
// Необходимо мониторить URL запроса на фактической странице. Это просто пример.
// Вы можете вручную ввести https://example.com/resolve в адресной строке браузера, чтобы инициировать запрос и проверить этот пример
const request = await page.waitForRequest('https://example.com/resolve');
console.log('request-url:', request.url());
await browser.close();
page.waitForResponse(urlOrPredicate, options): Ожидает ответа, соответствующего указанному URL или условной функции.import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://example.com');
// Необходимо мониторить URL ответа на фактической странице. Это просто пример.
// Вы можете вручную ввести https://example.com/resolve в адресной строке браузера, чтобы инициировать ответ и проверить этот пример
const response = await page.waitForResponse('https://example.com/resolve');
console.log('response-status:', response.status());
await browser.close();
page.waitForNetworkIdle(options): Ожидает, пока сетевая активность на странице не утихнет. Этот метод полезен для того, чтобы убедиться, что страница полностью загружена.import puppeteer from "puppeteer";
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.waitForNetworkIdle({
timeout: 30000, // Максимальное время ожидания 30 секунд
idleTime: 500 // То есть отсутствие сетевой активности в течение 500 миллисекунд
});
console.log('Network is idle.');
// Сохраните скриншот, чтобы проверить, полностью ли загружена страница
await page.screenshot({ path: 'example.png' });
await browser.close();
setTimeout: Использование API Javascript напрямую также является хорошим вариантом. Немного доработав, его можно использовать в контексте страницы.// Подождите две секунды перед выполнением следующего скрипта
await new Promise(resolve => setTimeout(resolve, 2000))
await page.click('.devsite-result-item-link'); // Нажмите на этот элемент
Есть ли у вас хорошие идеи или вопросы о веб-скрейпинге и Browserless?
Посмотрите чем делятся другие разработчики в Discord и Telegram!
Хорошо, давайте начнем сбор полных данных из списка комментариев на странице.
Мы можем переписать вышеуказанный код так, чтобы он не собирал только одно имя пользователя, а сосредоточился на сборе всего списка комментариев.
В следующем коде также используется page.waitForSelector для ожидания загрузки элемента комментария, и используется page.$$, чтобы получить все узлы элементов, соответствующие селектору элемента:
await page.waitForSelector('div[data-hook="review"]');
const reviewList = await page.$$('div[data-hook="review"]');
Далее нам нужно пройтись по списку элементов комментариев и извлечь нужную нам информацию из каждого элемента комментария.
В следующем коде мы можем получить textContent заголовка, оценки, имени пользователя и содержания, а также получить значение атрибута data-src в узле элемента аватара, который является URL-адресом аватара.
for (const review of reviewList) {
const title = await review.$eval(
'a[data-hook="review-title"] .cr-original-review-content',
node => node.textContent,
);
const rate = await review.$eval(
'i[data-hook="review-star-rating"] .a-icon-alt',
node => node.textContent,
);
const username = await review.$eval(
'div[data-hook="genome-widget"] .a-profile-name',
node => node.textContent,
);
const avatar = await review.$eval(
'div[data-hook="genome-widget"] .a-profile-avatar img',
node => node.getAttribute('data-src'),
);
const content = await review.$eval(
'span[data-hook="review-body"] span',
node => node.textContent,
);
console.log('[log]===>', { title, rate, username, avatar, content });
}
Запустив вышеуказанный код, вы должны увидеть информацию журнала, напечатанную в терминале.

Если вы хотите дальше сохранить эти данные, вы можете использовать базовый модуль nodejs fs, чтобы записать данные в файл json для последующего анализа данных.
Ниже приведена простая функция инструмента:
import fs from 'fs';
function saveObjectToJson(obj, filename) {
const jsonString = JSON.stringify(obj, null, 2)
fs.writeFile(filename, jsonString, 'utf8', (err) => {
err ? console.error(err) : console.log(`Файл успешно сохранен: ${filename}`);
});
}
Полный код приведен ниже. После запуска вы сможете найти файл amazon_reviews_log.json в пути выполнения текущего скрипта. Этот файл записывает все результаты вашего сбора данных!
import puppeteer from 'puppeteer';
import fs from 'fs';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(
`https://www.amazon.com/Apple-Generation-Cancelling-Transparency-Personalized/product-reviews/B0CHWRXH8B/ref=cm_cr_dp_d_show_all_btm?ie=UTF8&reviewerType=all_reviews`
);
await page.waitForSelector('div[data-hook="review"]');
const reviewList = await page.$$('div[data-hook="review"]');
const reviewLog = []
for (const review of reviewList) {
const title = await review.$eval(
'a[data-hook="review-title"] .cr-original-review-content',
node => node.textContent,
);
const rate = await review.$eval(
'i[data-hook="review-star-rating"] .a-icon-alt',
node => node.textContent,
);
const username = await review.$eval(
'div[data-hook="genome-widget"] .a-profile-name',
node => node.textContent,
);
const avatar = await review.$eval(
'div[data-hook="genome-widget"] .a-profile-avatar img',
node => node.getAttribute('data-src'),
);
const content = await review.$eval(
'span[data-hook="review-body"] span',
node => node.textContent,
);
console.log('[log]===>', { title, rate, username, avatar, content });
reviewLog.push({ title, rate, username, avatar, content })
}
function saveObjectToJson(obj, filename) {
const jsonString = JSON.stringify(obj, null, 2)
fs.writeFile(filename, jsonString, 'utf8', (err) => {
err ? console.error(err) : console.log(`Файл успешно сохранен: ${filename}`);
});
}
saveObjectToJson(reviewLog, 'amazon_reviews_log.json')
await browser.close();
Поняли базовые примеры выше? Теперь можно перейти к более мощным функциям Puppeteer. После выполнения следующих примеров, вы получите новое понимание этого инструмента.
Используйте page.mouse.move, чтобы управлять движением мыши.
Для того чтобы вы увидели, что курсор действительно двигается по странице, приведенный ниже пример представляет собой бесконечный цикл, который заставит мышь перемещаться случайным образом, чтобы активировать hover-эффект на странице.
Следует отметить, что для срабатывания hover-эффекта мышь не должна двигаться слишком быстро. Установите в методе move скорость движения steps: 10. Этот шаг также уменьшит вероятность обнаружения сайта.
Page.evaluate — это очень полезный API, который позволяет вам выполнять код JavaScript, работающий только в среде браузера, в контексте страницы, например, с использованием window API. Здесь его цель — прокрутить страницу вниз, чтобы полностью загрузить комментарии.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
await page.goto('https://www.google.com');
// Получаем ширину и высоту экрана
const { width, height } = await page.evaluate(() => {
return { width: window.innerWidth, height: window.innerHeight };
});
// Бесконечный цикл, симулирующий случайное движение мыши
while (true) {
const x = Math.floor(Math.random() * width);
const y = Math.floor(Math.random() * height);
await page.mouse.move(x, y, { steps: 10 });
console.log(`Позиция мыши: (${x}, ${y})`);
await new Promise(resolve => setTimeout(resolve, 200)); // Движение каждые 0.2 секунды
}
Мы также встречали это в начальном примере. Как насчет того, чтобы изменить способ написания и использовать другие API для реализации?
Вы можете заметить, что некоторые селекторы ниже начинаются с >>>. Это Shadow DOM selector, предоставленный Puppeteer. Большинство операций настроены с задержкой через delay, что хорошо имитирует поведение реальных людей. Это делает ваш скрипт более стабильным и помогает избежать срабатывания механизмов защиты от роботов на некоторых веб-сайтах.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({
headless: false,
// defaultView устанавливает ширину и высоту 0, что означает, что содержимое веб-страницы заполняет все окно.
defaultViewport: { width: 0, height: 0 }
});
const page = await browser.newPage();
await page.goto('https://developer.chrome.com/docs/css-ui?hl=de');
await page.click('>>> button[aria-controls="language-menu"]', { delay: 500 });
// Переход на новую страницу и ожидание завершения перехода
await Promise.all([
page.click('>>> li[role="presentation"]', { delay: 500 }),
page.waitForNavigation(),
])
// Используйте setTimeout в качестве задержки, чтобы подождать 2 секунды до полной загрузки страницы
await new Promise(resolve => setTimeout(resolve, 2000))
// Фокус на поисковой строке
await page.focus('input.devsite-search-query', { delay: 500 });
// Ввод текста через клавиатуру
await page.keyboard.type('puppeteer', { delay: 200 });
// Нажатие клавиши Enter и отправка формы
await page.keyboard.press('Enter')
console.log('форма успешно отправлена');
await page.close()
Puppeteer предоставляет готовый API для создания скриншотов, что является очень полезной функцией, которую мы уже видели в примерах выше.
Качество файла скриншота можно хорошо контролировать через параметр quality, а с помощью clip можно обрезать изображение. Если у вас есть требования к соотношению сторон скриншота, вы также можете установить параметр defaultViewport, чтобы достичь нужного результата.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ defaultViewport: { width: 1920, height: 1080 } });
const page = await browser.newPage();
await page.goto('https://www.youtube.com/');
await page.screenshot({ path: 'screenshot1.png', });
await page.screenshot({ path: 'screenshot2.jpeg', quality: 50 });
await page.screenshot({ path: 'screenshot3.jpeg', clip: { x: 0, y: 0, width: 150, height: 150 } });
console.log('скриншоты сохранены');
await browser.close();
Для перехвата запросов необходимо сначала использовать setRequestInterception, чтобы активировать перехват запросов. Запустите следующий пример, и вы будете удивлены, обнаружив, что стили страницы исчезли, как и изображения и значки.
Это происходит потому, что запросы отслеживаются через страницу, и с помощью resourceType и url из interceptedRequest можно определить, отменить или переписать соответствующий запрос.
Обратите внимание, что метод isInterceptResolutionHandled следует вызывать перед обработкой перехвата запросов, чтобы избежать повторной обработки запросов или конфликтов.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Активация перехвата запросов
await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
// Избегаем повторной обработки запроса
if (interceptedRequest.isInterceptResolutionHandled()) return;
// Перехват запроса и переписывание ответа
if (interceptedRequest.url().includes('https://fonts.gstatic.com/')) {
interceptedRequest.respond({
status: 404,
contentType: 'image/x-icon',
})
console.log('запрос иконок заблокирован');
// Блокировка запроса стилей
} else if (interceptedRequest.resourceType() === 'stylesheet') {
interceptedRequest.abort();
console.log('запрос стилей заблокирован');
// Блокировка запроса изображений
} else if (interceptedRequest.resourceType() === 'image') {
interceptedRequest.abort();
console.log('запрос изображений заблокирован');
} else {
interceptedRequest.continue();
}
});
await page.goto('https://www.youtube.com/');
Конечно, вышеупомянутые функции также можно реализовать с помощью некоторых инструментов, таких как использование Nstbrowser RPA, чтобы сделать ваш скрипт более быстрым!
Шаг 1. Перейдите на главную страницу Nstbrowser и нажмите RPA/Workflow > создать рабочий процесс.

Шаг 2. После входа на страницу редактирования рабочего процесса, вы можете непосредственно воспроизвести вышеупомянутые функции, перетаскивая мышь.
Node слева может удовлетворить почти все ваши потребности в веб-сканировании или автоматизации, и эти узлы сильно соответствуют API Puppeteer.
Вы можете откалибровать порядок выполнения этих узлов, соединяя их, как выполнение асинхронного кода JavaScript. Если вы знакомы с Puppeteer, вы можете быстро освоить функции Nstbrowser RPA. Это именно то, что вы видите, то и получаете.

Шаг 3. Каждый Node может быть настроен индивидуально, и информация о настройках почти полностью соответствует настройкам Puppeteer.
a. Движение мыши

b. Нажатие кнопки

c. Ввод текста

d. Клавиатурные клавиши

e. Скриншот

f. Запросы перехвата

Кроме того, Nstbrowser RPA имеет более распространённые и уникальные узлы. Вы можете выполнять обычные операции веб-сканирования с помощью простого перетаскивания.
HTTP-заголовки — это дополнительная информация, обмен которой происходит между клиентом (браузером) и сервером. Они содержат метаданные для запросов и ответов, такие как тип контента, агент пользователя, настройки языка и т.д.
Распространённые HTTP-заголовки включают:
User-Agent: Идентифицирует тип клиентского приложения, операционную систему, версию программного обеспечения и другую информацию.Accept-Language: Указывает на язык, который может понимать клиент, и его приоритет.Referer: Указывает исходную страницу запроса.Изменяя эти заголовки, вы можете замаскироваться под другой браузер или операционную систему, тем самым снижая риск быть распознанным как робот.
При использовании Puppeteer вы можете использовать метод page.setExtraHTTPHeaders, чтобы установить заголовки перед переходом на веб-страницу:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch({ headless: false });
const page = await browser.newPage();
// Настройка пользовательских HTTP-заголовков
await page.setExtraHTTPHeaders({
'Accept-Language': 'en-US,en;q=0.9',
'Referer': 'https://www.google.com',
'MyHeader': 'hello puppeteer'
});
await page.goto('https://www.httpbin.org/headers');
Но если вы хотите изменить User-Agent, вы не сможете использовать вышеописанный метод, так как User-Agent в браузере имеет значение по умолчанию. Если вы действительно хотите его изменить, вы можете использовать метод page.setUserAgent.
import puppeteer from "puppeteer";
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, как Gecko) Chrome/115.0.5790.98 Safari/537.36');
await page.goto('https://example.com/');
const navigator = await page.evaluate(_ => window.navigator.userAgent)
const platform = await page.evaluate(_ => window.navigator.platform)
console.log('userAgent: ', navigator);
console.log('platform: ', platform);
await browser.close();

Но этого шага недостаточно. Как видно из вышеупомянутой информации, platform всё ещё установлена как win32 и не была фактически изменена.
Большинство веб-сайтов проводят проверку через window.navigator. Поэтому необходимо глубоко изменить navigator. Перед использованием page.goto мы можем глубоко изменить navigator в page.evaluateOnNewDocument.
Краткое описание различий между page.evaluateOnNewDocument и page.evaluate:
evaluateOnNewDocument.evaluate.await page.evaluateOnNewDocument(() => {
Object.defineProperties(navigator, {
platform: {
get: () => 'Mac'
},
});
});

Каждая строка в этой статье играет важную роль в описании самых детализированных руководств по следующим вопросам:
Хотите выполнять веб-сканирование и автоматизацию без усилий? Nstbrowser RPA поможет вам упростить все задачи.