Разработка
Apps:
No items found.

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

Вася

April 5, 2025

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 браузера
Пространство памяти Отдельный процесс Процесс браузера

Как работает обмен данными между контекстами

Обмен данными между этими контекстами включает несколько шагов, сильно зависящих от сериализации:

  1. Функция преобразуется в строку с помощью Function.prototype.toString().
  2. Эта строка отправляется в браузер через Chrome DevTools Protocol.
  3. Браузер выполняет функцию в своей среде.
  4. Результаты сериализуются в 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 необходим для работы со сложными объектами браузера, которые нельзя сериализовать.

Другие статьи