Выполнение JavaScript в контексте страницы с помощью page.evaluate в Puppeteer

page.evaluate() — это ключевой метод Puppeteer, который позволяет выполнять JavaScript напрямую в контексте браузера. Он служит мостом между Node.js и браузером, обеспечивая возможности для манипуляции DOM, извлечения данных и автоматизации динамических веб-страниц. Вот что нужно знать:
- Что делает: выполняет JavaScript в браузере, как если бы вы использовали консоль браузера.
- Как работает: преобразует функцию в строку, отправляет её в браузер, выполняет и возвращает результат.
- Основные применения:
- Извлечение данных с веб-сайтов (например, текста, таблиц, JSON).
- Автоматизация отправки форм и взаимодействия с пользователем.
- Обработка динамического контента, такого как бесконечная прокрутка или AJAX-обновления.
- Ограничения: функции должны быть JSON-сериализуемыми, и переменные Node.js недоступны напрямую в контексте браузера.
Быстрый пример:
const title = await page.evaluate(() => document.title);
Этот код получает заголовок страницы напрямую из браузера.
Сравнение: Node.js vs. Контекст браузера

| Функция | Контекст Node.js | Контекст браузера |
|---|---|---|
| Глобальные объекты | process, require | window, document |
| Расположение скрипта | Локальная машина | Целевая веб-страница |
| Доступ к API | API Node.js | Веб-API браузера |
NodeJS / Puppeteer — Как использовать page.evaluate
Объяснение контекста страницы
При работе с Puppeteer для автоматизации веб-страниц важно понимать разницу между контекстом Node.js и контекстом браузера. Эти две среды изолированы друг от друга, каждая со своими правилами выполнения кода и обмена данными.
Сравнение контекстов Node.js и браузера
Puppeteer работает в двух средах: контекст Node.js, где выполняется основной скрипт, и контекст браузера, где происходят взаимодействия с веб-страницей. Это отдельные процессы, каждый со своей виртуальной машиной.
Вот краткое сравнение их ключевых характеристик:
| Функция | Контекст Node.js | Контекст браузера |
|---|---|---|
| Глобальные объекты | process, require, __dirname | window, document, localStorage |
| Расположение скрипта | Локальная машина | Целевая веб-страница |
| Область видимости переменных | Область видимости скрипта Puppeteer | Область видимости страницы |
| Доступ к API | API Node.js | Веб-API браузера |
| Пространство памяти | Отдельный процесс | Процесс браузера |
Как работает обмен данными между контекстами
Обмен данными между этими контекстами включает несколько шагов, сильно зависящих от сериализации:
- Функция преобразуется в строку с помощью Function.prototype.toString().
- Эта строка отправляется в браузер через Chrome DevTools Protocol.
- Браузер выполняет функцию в своей среде.
- Результаты сериализуются в JSON и отправляются обратно в контекст Node.js.
Ключевые ограничения: функции в контексте браузера не могут напрямую обращаться к переменным из контекста Node.js. Puppeteer предлагает специальные инструменты для решения этих задач:
- page.evaluateHandle(): возвращает ссылки на объекты в контексте браузера.
- page.exposeFunction(): позволяет браузеру вызывать функции Node.js.
- evaluateOnNewDocument(): выполняет код до загрузки скриптов страницы.
Однако JSON-сериализация может удалить некоторые свойства, особенно у сложных объектов, таких как DOM-узлы. Чтобы избежать проблем, передавайте данные как аргументы функции, а не полагайтесь на переменные Node.js.
Освоение этих техник обмена данными гарантирует эффективное использование page.evaluate() для задач автоматизации. Далее рассмотрим практические примеры.
Начало работы с page.evaluate
Структура метода и параметры
Синтаксис:
await page.evaluate(pageFunction, ...args);
| Параметр | Тип | Описание |
|---|---|---|
| pageFunction | Функция или строка | JavaScript-код для выполнения в контексте браузера |
| args | Опциональные параметры | Значения, передаваемые из Node.js в контекст браузера |
| Возвращаемое значение | Promise | Разрешается с возвращаемым значением функции |
Базовые примеры кода
Примеры:
- Извлечение текста из первого <h1> напрямую из DOM:
const headingText = await page.evaluate(() => {
return document.querySelector('h1').textContent;
});
- Автоматизация отправки формы с передачей параметров:
await page.evaluate((username, password) => {
document.getElementById('username').value = username;
document.getElementById('password').value = password;
document.querySelector('#login-form').submit();
}, 'myUsername', 'myPassword');
- Манипуляция DOM: добавление нового элемента:
await page.evaluate(() => {
const div = document.createElement('div');
div.textContent = 'Добавлено с помощью Puppeteer';
document.body.appendChild(div);
return div.textContent;
});
Ключевые заметки для разработки
- Функции выполняются изолированно от кода Node.js.
- Аргументы, передаваемые в функцию, должны быть JSON-сериализуемыми.
- Возвращаемые значения автоматически оборачиваются в Promise.
- Работа со сложными объектами, такими как DOM-узлы, требует особого внимания.
Совет по отладке: используйте следующую конфигурацию для включения отладки во время разработки:
const browser = await puppeteer.launch({
headless: false,
slowMo: 100 // Добавляет задержку в 100 мс для каждой операции
});
Далее рассмотрим техники обмена данными между контекстами Node.js и браузера.
Обмен данными между контекстами
Входные параметры
При передаче данных с page.evaluate используйте JSON-сериализуемые значения для входных аргументов.
Вот краткий обзор поддерживаемых типов параметров:
| Тип параметра | Поддерживается? | Пример |
|---|---|---|
| Примитивы | ✓ Полностью | 'текст', 42, true |
| Массивы/Объекты | ✓ JSON-совместимые | { key: 'значение' }, [1, 2, 3] |
| Функции | ✗ Не напрямую | Используйте page.exposeFunction |
| DOM-элементы | ✓ Через JSHandle | Используйте page.evaluateHandle |
Теперь рассмотрим, как эти значения возвращаются из контекста браузера.
Обработка вывода
При использовании page.evaluate возвращаемые значения автоматически сериализуются в JSON. Вот как это работает:
// Возврат простого значения
const pageTitle = await page.evaluate(() => document.title);
// Возврат сложного объекта
const metrics = await page.evaluate(() => ({
viewport: window.innerWidth,
scrollHeight: document.body.scrollHeight,
timestamp: Date.now()
}));
"Как правило, если возвращаемое значение функции сложнее, чем JSON-объект (например, большинство классов), то evaluate, скорее всего, вернет усеченное значение (или {}). Это происходит потому, что мы возвращаем не фактическое значение, а десериализованную версию в результате передачи через протокол Puppeteer."
После получения вывода могут возникнуть проблемы, связанные с сериализацией. Вот как их решить.
Решение проблем сериализации
Некоторые сценарии требуют специальных решений:
- Работа с DOM-элементами:
const bodyHandle = await page.$('body');
const html = await page.evaluate(body => body.innerHTML, bodyHandle);
await bodyHandle.dispose(); // Всегда очищайте, чтобы избежать утечек памяти
- Использование функций Node.js:
await page.exposeFunction('md5', text =>
crypto.createHash('md5').update(text).digest('hex')
);
const hash = await page.evaluate(async () => {
return await window.md5('test-string');
});
- Настройки транспилятора:
Если вы работаете с TypeScript, убедитесь, что транспилятор настроен правильно:
// tsconfig.json
{
"compilerOptions": {
"target": "es2018"
}
}
Эти стратегии помогут эффективно обмениваться данными в различных контекстах.
Практические примеры
Вот как можно использовать page.evaluate в реальных сценариях с практическими примерами кода.
Извлечение данных
Пример: сбор деталей товара
Этот скрипт собирает информацию о названии, цене, рейтинге и наличии товара на веб-странице:
const productData = await page.evaluate(() => {
const products = Array.from(document.querySelectorAll('.product-card'));
return products.map(product => ({
title: product.querySelector('.title').textContent.trim(),
price: product.querySelector('.price').textContent.trim(),
rating: parseFloat(product.querySelector('.rating').dataset.value),
inStock: product.querySelector('.stock').textContent.includes('В наличии')
}));
});
Пример: извлечение данных из таблицы
Этот подход извлекает данные из таблицы, перебирая её строки и столбцы:
const tableData = await page.evaluate(() => {
const rows = Array.from(document.querySelectorAll('table tr'));
return rows.map(row => {
const columns = row.querySelectorAll('td');
return Array.from(columns, column => column.innerText);
});
});
Автоматизация форм
Базовая автоматизация форм
Вот как заполнить поля формы, вызвать события и отправить форму:
await page.evaluate(() => {
// Заполнение полей формы
document.querySelector('#username').value = 'testuser';
document.querySelector('#password').value = 'secretpass';
// Вызов событий для динамических форм
const event = new Event('input', { bubbles: true });
document.querySelector('#username').dispatchEvent(event);
// Отправка формы
document.querySelector('form').submit();
});
Работа со сложными формами
Для задач, таких как выбор опций из выпадающего списка или отметка радио-кнопок:
await page.evaluate(() => {
// Выбор опции из выпадающего списка
const select = document.querySelector('#country');
select.value = 'US';
select.dispatchEvent(new Event('change', { bubbles: true }));
// Отметка радио-кнопки
const radio = document.querySelector('input[value="express"]');
radio.checked = true;
radio.dispatchEvent(new Event('change', { bubbles: true }));
});
Управление динамическими элементами
Пример: бесконечная прокрутка
Этот скрипт прокручивает страницу, пока не соберет хотя бы 100 элементов:
const items = await page.evaluate(async () => {
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const items = new Set();
while (items.size < 100) {
// Прокрутка вниз
window.scrollTo(0, document.body.scrollHeight);
// Ожидание нового контента
await delay(1000);
// Сбор элементов
document.querySelectorAll('.item').forEach(item =>
items.add(item.textContent.trim())
);
}
return Array.from(items);
});
Пример: обработка AJAX-контента
Для динамической загрузки контента этот скрипт нажимает кнопку "Загрузить ещё" и ожидает появления новых элементов:
await page.evaluate(async () => {
// Нажатие кнопки "Загрузить ещё"
document.querySelector('#loadMore').click();
// Ожидание обновления контента
await new Promise(resolve => {
const observer = new MutationObserver((mutations, obs) => {
if (document.querySelectorAll('.item').length > 10) {
obs.disconnect();
resolve();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
});
Эти примеры демонстрируют, как обрабатывать различные сценарии, такие как сбор данных, автоматизация форм и динамический контент. Настройки можно адаптировать под конкретную структуру и поведение веб-страницы.
Использование page.evaluate в Нодуле
Нодуль интегрирует основные функции Puppeteer в свои рабочие процессы автоматизации, упрощая выполнение JavaScript напрямую в браузере. С page.evaluate пользователи могут манипулировать DOM и извлекать данные эффективно. Этот подход обеспечивает плавную интеграцию сложной обработки данных и операций с DOM в среде автоматизации Нодуля.
Скрипты браузера в Нодуле
Модуль автоматизации браузера в Нодуле использует page.evaluate для выполнения задач от простых операций с DOM до сложных сценариев JavaScript. Вот как это работает:
// Базовое взаимодействие с DOM
await page.evaluate(() => {
const loginButton = document.querySelector('#login');
loginButton.click();
// Вызов пользовательского события
loginButton.dispatchEvent(new Event('customClick'));
});
// Обработка данных с помощью открытых функций
await page.exposeFunction('processData', async (data) => {
// Обработка данных в контексте Node.js
return transformedData;
});
await page.evaluate(async () => {
const rawData = document.querySelector('#data').textContent;
const processed = await window.processData(rawData);
return processed;
});
Нодуль также ведет журнал выполнения, что упрощает отладку скриптов.
Примеры автоматизации
Нодуль хорошо подходит для обработки динамического контента и сложных задач автоматизации. Вот пример обработки динамического контента на странице:
const extractProductData = await page.evaluate(async () => {
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
// Ожидание загрузки динамического контента
while (!document.querySelector('.product-grid')) {
await delay(100);
}
return Array.from(document.querySelectorAll('.product'))
.map(product => ({
name: product.querySelector('.name').textContent,
price: product.querySelector('.price').textContent,
availability: product.querySelector('.stock').dataset.status
}));
});
Для более сложных операций page.exposeFunction позволяет легко взаимодействовать между Node.js и браузером:
await page.exposeFunction('md5', text =>
crypto.createHash('md5').update(text).digest('hex')
);
const processedData = await page.evaluate(async () => {
const sensitiveData = document.querySelector('#secure-data').value;
return await window.md5(sensitiveData);
});
Для сохранения ссылок на DOM-элементы между шагами Нодуль использует page.evaluateHandle:
const elementHandle = await page.evaluateHandle(() => {
return document.querySelector('.dynamic-content');
});
await page.evaluate(element => {
element.scrollIntoView();
}, elementHandle);
Эти техники гарантируют, что Нодуль может эффективно обрабатывать динамический контент, сохраняя при этом надежную производительность. Для пользователей с Prime-платформой поддерживается до 1,5 миллионов запусков сценариев в месяц, что обеспечивает широкие возможности автоматизации.
Руководство по устранению ошибок
При работе с page.evaluate в автоматизации браузера могут возникать различные проблемы. Вот практические решения для их устранения и обеспечения более плавного выполнения.
Исправление ошибок контекста
Правильно настройте параметры TypeScript, чтобы избежать проблем, вызванных транспиляцией. Например:
// Используйте прямые, нетранспилированные функции
await page.evaluate(() => {
document.querySelector('#button').click();
});
await page.evaluate(`(async () => {
document.querySelector('#button').click();
})()`);
Избегайте прямого возврата DOM-элементов из page.evaluate. Вместо этого используйте ElementHandle для лучшего управления:
// Неправильно: возврат DOM-элемента
const element = await page.evaluate(() => {
return document.querySelector('.dynamic-element');
});
// Правильно: использование ElementHandle
const element = await page.evaluateHandle(() => {
return document.querySelector('.dynamic-element');
});
Решение проблем с таймингом
Скрипты могут выполняться до полной загрузки страницы, что приводит к ошибкам тайминга. Используйте следующие стратегии:
// Ожидание навигации после действия
await Promise.all([
page.waitForNavigation(),
page.click('#submit-button')
]);
// Ожидание определенного условия
await page.waitForFunction(() => {
const element = document.querySelector('.lazy-loaded');
return element && element.dataset.loaded === 'true';
}, { timeout: 5000 });
Для динамических веб-сайтов используйте более точные механизмы ожидания:
// Ожидание определенных сетевых запросов
await page.waitForResponse(
response => response.url().includes('/api/data')
);
// Проверка наличия и видимости элементов
await page.waitForSelector('.dynamic-content', {
visible: true,
timeout: 3000
});
Управление ссылками на DOM
Чтобы избежать утечек памяти, тщательно управляйте ссылками на DOM. Вот как:
// Использование и удаление ElementHandle
const handle = await page.evaluateHandle(() => {
return document.querySelector('.temporary-element');
});
await handle.evaluate(element => {
// Выполнение операций
});
await handle.dispose(); // Удаление после использования
При работе с несколькими элементами безопасно передавайте данные между контекстами:
// Извлечение данных из DOM
const selector = '.product-price';
const price = await page.evaluate((sel) => {
const element = document.querySelector(sel);
return element ? element.textContent.trim() : null;
}, selector);
Для обработчиков событий обеспечьте их очистку, чтобы избежать "зависших" обработчиков:
await page.evaluate(() => {
const handler = () => console.log('clicked');
const button = document.querySelector('#button');
button.addEventListener('click', handler);
// Хранение ссылок для очистки
window._cleanupHandlers = window._cleanupHandlers || [];
window._cleanupHandlers.push(() => {
button.removeEventListener('click', handler);
});
});
Рекомендации по реализации
Для достижения наилучших результатов с page.evaluate сосредоточьтесь на улучшении производительности, сокращении ненужных переключений контекста и обеспечении безопасности. Вот как можно оптимизировать рабочие процессы автоматизации браузера.
Оптимизация производительности
Эффективное выполнение кода в контексте страницы экономит время и системные ресурсы. Вот несколько техник для ускорения скриптов:
// Блокировка ненужных ресурсов, таких как изображения и стили
await page.setRequestInterception(true);
page.on('request', request => {
if (['image', 'stylesheet'].includes(request.resourceType())) {
request.abort();
} else {
request.continue();
}
});
// Пакетные операции для снижения нагрузки
await page.evaluate(() => {
const results = [];
document.querySelectorAll('.product-item').forEach(item => {
results.push({
title: item.querySelector('.title').textContent,
price: item.querySelector('.price').textContent,
stock: item.querySelector('.stock').dataset.value
});
});
return results;
});
Выбор правильных селекторов также играет важную роль в производительности:
| Тип селектора | Скорость | Пример |
|---|---|---|
| ID | Наивысшая | #main-content |
| Class | Высокая | .product-item |
| Tag | Средняя | div > span |
| Сложный XPath | Наименьшая | //div[@class='wrapper']//span |
Управление переключением контекста
Частые переключения между контекстами Node.js и браузера могут замедлять работу. Вот как их минимизировать:
// Пример неэффективного переключения контекста
for (const item of items) {
await page.evaluate((i) => {
document.querySelector(`#item-${i}`).click();
}, item);
}
// Лучше: пакетные операции в одном переключении контекста
await page.evaluate((itemsList) => {
itemsList.forEach(i => {
document.querySelector(`#item-${i}`).click();
});
}, items);
Если нужно обработать данные в Node.js и передать их обратно в браузер, используйте открытые функции вместо частых переключений контекста:
await page.exposeFunction('processData', async (data) => {
// Обработка данных в Node.js
return transformedData;
});
await page.evaluate(async () => {
const result = await window.processData(documentData);
// Использование обработанных данных в браузере
});
Рекомендации по безопасности
После оптимизации производительности и переключения контекста сосредоточьтесь на безопасности скриптов. Вот несколько лучших практик:
// Всегда очищайте входные данные перед использованием
const sanitizedInput = sanitizeHtml(userInput);
await page.evaluate((input) => {
document.querySelector('#search').value = input;
}, sanitizedInput);
// Используйте обработку ошибок для критических операций
try {
await page.evaluate(() => {
if (!window.__securityCheck) {
throw new Error('Проверка безопасности не пройдена');
}
// Продолжение операции
});
} catch (error) {
console.error('Нарушение безопасности:', error);
}
Для рабочих процессов в Нодуле учитывайте следующие дополнительные советы:
- Используйте userDataDir для кэширования ресурсов и улучшения производительности между сеансами.
- Закрывайте неиспользуемые страницы и экземпляры браузера для экономии памяти.
- Обрабатывайте скриншоты с помощью буферов вместо операций с файловой системой.
- Реализуйте надежную обработку ошибок и тщательные проверки безопасности.
Итоги
Обзор ключевых моментов
Метод page.evaluate соединяет контексты Node.js и браузера, отправляя строкифицированную JavaScript-функцию для выполнения в браузере. Эта функция работает независимо от среды Node.js, поэтому необходимо тщательно управлять передачей данных.
Вот типичный пример для извлечения данных:
const data = await page.evaluate(async () => {
const results = document.querySelectorAll('.data-item');
return Array.from(results, item => ({
id: item.dataset.id,
value: item.textContent.trim()
}));
});
Что нужно помнить:
- Аргументы должны быть JSON-сериализуемыми.
- Возвращаемые значения автоматически десериализуются.
- API браузера доступны только в контексте evaluate.
- Переменные Node.js недоступны в контексте браузера.
Эти основы закладывают фундамент для эффективного использования Puppeteer. Дополнительные инструменты могут ещё больше упростить задачи автоматизации.
Дополнительные инструменты Puppeteer
Puppeteer предлагает несколько инструментов для расширения возможностей page.evaluate:
| Инструмент | Назначение | Лучший вариант использования |
|---|---|---|
| page.evaluateHandle | Возвращает ссылки на объекты | Прямое взаимодействие с DOM-элементами |
| page.exposeFunction | Делает функции Node.js доступными в браузере | Управление сложной серверной логикой |
| page.evaluateOnNewDocument | Выполняет скрипты до загрузки страницы | Предварительная подготовка окружения браузера |
Например, открытие функций Node.js для браузера может упростить сложную обработку данных в рабочих процессах, таких как в Нодуле. В то время как page.evaluate хорошо работает с примитивами и JSON-сериализуемыми объектами, page.evaluateHandle необходим для работы со сложными объектами браузера, которые нельзя сериализовать.