Назад в Центр знаний
Начало работы

Сбор данных с Playwright: браузер и профиль

Настройте разрешённый сбор списка и карточек через Playwright. Разделите профиль, cookies и ошибки извлечения, сохраняя проверяемый результат JSON.

BotBrowser Team

Документация

Нужна структурированная документация по теме Начало работы?

Эта статья относится к редакционной библиотеке. Для пошаговой настройки, справки и постоянных обновлений переходите сразу в соответствующий раздел docs.

При сборе данных с Playwright нужны две разные проверки: запущена ли нужная среда браузера и извлечены ли нужные записи. Успех chromium.launch() не доказывает смысл цены и не даёт разрешения её собирать. BotBrowser поддерживает запуск Chromium с профилем через executablePath и args; он не предоставляет селекторы сайта, не разрешает доступ и не гарантирует ответ сервера. На собственном или разрешённом приложении ограниченный переход от списка к карточкам позволяет раздельно диагностировать настройку и данные.

Кратко

  • Укажите executablePath, передавайте --bot-profile одним аргументом. Профиль BotBrowser отличается от cookies и локального хранилища.
  • Используйте один BrowserContext для списка и карточек одной разрешённой сессии. Новый контекст изолирует хранилище, но не создаёт новую сетевую идентичность.
  • До принятия записи проверяйте статус, конечный источник и обязательные селекторы. HTTP 200 может означать страницу входа.
  • Храните записи отдельно от диагностики. Сохранённая авторизация представляет собой секрет доступа, а не обычное приложение к отчёту.

Содержание

Для собственного каталога с проверкой HTTP и схемы строк сравнение настроек сбора данных Puppeteer разделяет параметры запуска и наблюдения, явно определяя хранилище изолированного контекста.

Настройка браузера перед сбором

Справочник BrowserType описывает executablePath для запуска браузера вместо поставляемого Playwright. Совместимость произвольного исполняемого файла с установленной версией при этом не гарантируется. Фиксируйте playwright-core в файл фиксации зависимостей, записывайте фактическую версию браузера и проверяйте совместимость после изменения любого компонента. Загрузка комплектного Chromium не обновляет настроенный сторонний файл.

Пути бинарного файла и профиля должны быть абсолютными. В контейнере файлы должны существовать внутри него, а не только на подготовившем задачу хосте. Проверьте права выполнения и чтения до запуска. Отсутствующая переменная должна вызывать ошибку конфигурации, а не переход к неожиданным значениям по умолчанию. Прямое подключение Playwright описывает установку; задача сбора дополнительно требует сведений, объясняющих плохой результат.

Справочник профилей BotBrowser требует одинаковую основную версию пакета и браузера. CLI переопределяет настройки профиля, а не создаёт дополнительную независимую идентичность. Не добавляйте чужие userAgent, часовой пояс, языки и дескриптор устройства из общего примера. Если приложению необходим определённый viewport, запишите переопределение. viewport: null отключает фиксированную эмуляцию viewport Playwright, но не задаёт размер экрана и не делает все макеты одинаковыми.

Разрешение владельца, совместимая среда выполнения и схема извлечения остаются отдельными условиями. Пример использует собственный каталог: один список и не более двух карточек, а не публичный маркетплейс. Такое ограничение позволяет проверить селекторы без неограниченного обхода и ничего не обещает о совместимости с защитой сторонних сервисов.

Установите playwright-core в отдельном проекте, сохраните collect.mjs и задайте BOTBROWSER_EXEC_PATH, BOT_PROFILE_PATH, OWNED_CATALOG_ORIGIN. Origin должен использовать HTTP или HTTPS и принадлежать контролируемому контрольный пример. Это иллюстрация документированных API, а не отчёт о запуске BotBrowser.

Контрольный пример сохраняет стандартные механизмы инструментирования автоматизации Playwright. Он не проверяет и не заявляет результаты для сигналов автоматизации.

Извлечение списка и карточек

На /catalog контрольный пример размещены ссылки data-testid="item-link". Каждая карточка содержит ровно один data-testid="item-name" и один data-testid="item-price" с атрибутом data-currency. Например, ссылка ведёт на /items/a1, а цена задана как <span data-testid="item-price" data-currency="USD">12.50</span>. Это входные данные примера, не наблюдавшийся вывод. Другой HTML требует другой схемы селекторов.

import { chromium } from 'playwright-core';
import { access, writeFile } from 'node:fs/promises';
import { constants } from 'node:fs';
import { isAbsolute } from 'node:path';

const executablePath = process.env.BOTBROWSER_EXEC_PATH;
const profilePath = process.env.BOT_PROFILE_PATH;
const origin = new URL(process.env.OWNED_CATALOG_ORIGIN);
if (!['http:', 'https:'].includes(origin.protocol)) {
  throw new Error('Fixture origin must use HTTP or HTTPS');
}
if (origin.username || origin.password || origin.search || origin.hash || origin.pathname !== '/') {
  throw new Error('Supply an origin without credentials, path, query or fragment');
}
const safeUrl = value => {
  const url = new URL(value);
  return url.origin + url.pathname;
};
for (const value of [executablePath, profilePath]) {
  if (!value || !isAbsolute(value)) throw new Error('Absolute paths required');
}
await access(executablePath, constants.X_OK);
await access(profilePath, constants.R_OK);

const browser = await chromium.launch({
  executablePath,
  headless: true,
  args: [`--bot-profile=${profilePath}`],
});
const records = [];
const diagnostics = [];
try {
  const context = await browser.newContext({ viewport: null });
  context.setDefaultTimeout(10000);
  const page = await context.newPage();
  async function open(url) {
    const response = await page.goto(url, {
      waitUntil: 'domcontentloaded',
      timeout: 30000,
    });
    const status = response?.status() ?? null;
    const finalUrl = new URL(page.url());
    diagnostics.push({ url: safeUrl(url), finalUrl: safeUrl(finalUrl), status });
    if (status === null || status < 200 || status >= 300) {
      throw new Error(`Unaccepted navigation status: ${status}`);
    }
    if (finalUrl.origin !== origin.origin) {
      throw new Error('Navigation left the fixture origin');
    }
  }
  await open(new URL('/catalog', origin).href);
  const links = page.getByTestId('item-link');
  await page.waitForFunction(() => document.querySelectorAll('[data-testid="item-link"]').length === 2);
  const hrefs = await links.evaluateAll(nodes => nodes.map(node => node.getAttribute('href')));
  const urls = [...new Set(hrefs.filter(Boolean).map(href => new URL(href, page.url()).href))].filter(
    url => new URL(url).origin === origin.origin
  );
  if (urls.length !== 2) throw new Error('Fixture requires two same-origin detail links');
  for (const url of urls) {
    await open(url);
    const name = page.getByTestId('item-name');
    const price = page.getByTestId('item-price');
    await name.waitFor({ state: 'visible' });
    await price.waitFor({ state: 'visible' });
    if ((await name.count()) !== 1 || (await price.count()) !== 1) {
      throw new Error('Detail selector is not unique');
    }
    const label = (await name.innerText()).trim();
    const amount = (await price.innerText()).trim();
    const currency = await price.getAttribute('data-currency');
    if (!label || !/^\d+\.\d{2}$/.test(amount) || !/^[A-Z]{3}$/.test(currency ?? '')) {
      throw new Error('Detail record does not match the fixture schema');
    }
    if (new URL(page.url()).origin !== origin.origin) {
      throw new Error('Detail origin changed during extraction');
    }
    records.push({ url: safeUrl(page.url()), name: label, amount, currency });
  }
  await writeFile(
    'catalog-result.json',
    JSON.stringify(
      {
        browserVersion: browser.version(),
        collectedAt: new Date().toISOString(),
        records,
        diagnostics,
      },
      null,
      2
    ),
    { flag: 'wx', mode: 0o600 }
  );
  await context.close();
} finally {
  await browser.close();
}

Документация locators отличает локатор от сохранённой ссылки на DOM-элемент. Локатор разрешается при использовании и переносит замену узлов, но не знает правильную коммерческую запись. Поэтому программа ждёт элементы, требует уникальность, проверяет формат и только затем добавляет запись. Событие domcontentloaded отмечает навигацию, а не готовность асинхронного каталога.

Контрольный пример ожидает ровно две ссылки: программа ждёт этого количества и требует два различных URL того же источник. Для постепенной загрузки приложение должно предоставлять собственный маркер завершения или стабильный API-ответ. evaluateAll() не ждёт итогового числа совпадений. Без бизнес-условия можно сохранить одну корректную запись из списка, где позже появятся двадцать, и не получить истечение ожидания.

Фильтр источник ограничивает выбранные ссылки. Проверка после навигации отклоняет перенаправление, а проверка перед сохранением отклоняет смену источник при чтении карточки; сами запросы уже произошли. Это не сетевой экран: изображения, скрипты и встроенные фреймы могут обращаться к другим хостам. Строгие ограничения исходящего трафика задаются также инфраструктурой, а не задним числом в коде.

Сумма остаётся строкой, поскольку контрольный пример определяет десятичный текстовый формат. Реальная цена может содержать десятичную запятую, разделители тысяч, налоги, период подписки и неоднозначный символ валюты. parseFloat() не заменяет схему. Задавайте формат для каждого источника и отклоняйте неоднозначность вместо преобразования первого числового префикса.

Что сохранять между запусками

Профиль, хранилище и результат решают разные задачи. Жизненный цикл контекстов и хранилища описывает изоляцию cookies и других данных внутри процесса. Контекст не выделяет другой публичный IP или аккаунт и не доказывает невозможность связать запросы. Два контекста могут показывать одинаковый профиль при отдельных cookies.

КомпонентСодержимоеЧего он не доказывает
--bot-profileНастройки браузера из пакетаАвторизованную сессию сайта
BrowserContextИзолированное состояние на время жизниДругой proxy IP или разрешение сбора
storageStateПоддерживаемые cookies и хранилища источникВсё временное состояние вкладки
userDataDirПостоянный каталог браузераБезопасность одновременной записи разными процессами
JSON результатаЗаписи и выбранную диагностикуУчётные данные для повторения сессии

Три разных пути: профиль настраивает браузер, BrowserContext хранит состояние сайта, извлечение пишет catalog-result.json. Повторное использование одного пути не восстанавливает остальные автоматически.

Для списка с карточками повторно используйте один контекст и страницу: cookies сохраняются между переходами, область задачи понятна. Новый контекст на каждую карточку может потерять легитимную сессию списка. Общий контекст для разных разрешённых аккаунтов, напротив, смешивает состояние. Сначала определите границу аккаунта и сессии, затем количество страниц.

Непостоянный контекст даёт чистое хранилище между запусками и теряет его после закрытия. Для разрешённого входа Playwright сохраняет и загружает storageState. Документация аутентификации предупреждает, что файл может содержать секреты, позволяющие воспользоваться аккаунтом. Не включайте его в git и обычные выгрузки; ограничьте доступ, срок хранения и связь с аккаунтом.

Не все механизмы включаются автоматически. Текущая документация описывает IndexedDB с indexedDB: true; проверьте поддержку закреплённой версией. Хранилище sessionStorage требует отдельной логики приложения. Восстановленная cookie может истечь или быть отозвана сервером: успешное чтение файла не доказывает действительность сессии.

Если задаче намеренно нужен полный каталог браузера, launchPersistentContext() возвращает контекст и использует userDataDir, отдельный от .enc для --bot-profile. Не направляйте несколько процессов в один каталог с записью. Выделяйте каталог каждому одновременно работающему владельцу и соблюдайте обычные блокировки вместо принудительного доступа.

Разделение данных и ошибок

Результат записывается только после проверки всех выбранных карточек. Сбой после первой не выдаётся за завершённую пару. flag: 'wx' запрещает перезапись существующего файла. В планировщике нужны уникальные каталог и идентификатор запуска; манифест должен различать успех, частичный результат и неудачную попытку.

Время означает момент сбора, а не изменение продукта. browserVersion возвращает версию подключённого браузера, но не удостоверяет исполняемый файл. JavaScript может видеть значения профиля; один navigator.userAgent не подтверждает, какой бинарный файл запустил хост. Сохраняйте разрешённый путь, ревизию файл фиксации зависимостей и digest профиля в ограниченных метаданных, отдельно от публичного каталога.

Запрошенный и конечный URL объясняют переходы к согласию, входу или снятому товару, в том числе с HTTP 200. Поэтому селекторы и схема определяют приёмку. Успешный HTTP и корректная запись должны иметь разные статусы для последующих повторов.

Браузерный слой web scraping касается наблюдаемой среды, а не правильности селектора цены. Изменение макета, отсутствие доступа и ошибка запуска могут дать пустой результат; профиль не исправляет первые две причины. Разделяйте ошибку извлечения и свидетельства о среде.

Не публикуйте полные заголовки, cookies и URL с секретами по умолчанию. Программа запрещает учётные данные в источник и удаляет строку запроса и фрагмент при сохранении. Секрет может находиться и в пути, поэтому контрольный пример предполагает публичные пути каталога и проверку полей перед передачей. Редактируйте общую копию, сохраняя одобренные ограниченные свидетельства для определения разрешённой задачи.

Диагностика сбойного слоя

НаблюдениеПервая проверкаПодходящее действие
Браузер не запускаетсяПуть, права, зависимостиИсправить установку или подключение каталога
Не создаётся контекстСовместимость версийПроверить документированную комбинацию
HTTP 401 или 403Разрешение и ответ сервераОстановиться и решить доступ с владельцем
HTTP 429Лимит и Retry-After, если естьВыдержать политику сервиса
HTTP 200 без элементовКонечный URL, вход, завершение контрольный примерКлассифицировать страницу до смены селекторов
Некорректная ценаСхема и локальный форматОтклонить без угадывания
Следующий запуск без входаКонтексты и истечение состоянияВосстановить одобренное состояние или войти штатно

Диагностика требует различающего наблюдения: категории URL, доступного статуса и типа исключения для навигации; поля схемы и достигнутого маршрута для селекторов. catch, записывающий пустой массив, делает отказ неотличимым от пустого каталога.

Не повторяйте весь скрипт вслепую. Истечение ожидания иногда означает медленное приложение, но запрет доступа требует другого решения, а неправильная цена не исправится после десяти переходов. Разделяйте повторяемые транспортные ошибки, решения доступа и детерминированные ошибки схемы. Соблюдайте лимиты и соглашение; BotBrowser не решает, уместна ли повторная попытка.

В примере нет ротации proxy, параллельного обхода или обработки проверочных запросов. Они изменили бы сессию и трафик, не помогая проверить собственный каталог. До расширения определите владение аккаунтами, разрешённый объём, полноту и восстановление. Две правильные записи проверяют схему, но не безопасность и надёжность большого обхода.

Вопросы

Достаточно ли playwright-core для выбора BotBrowser?

Только при явном executablePath. Пакет даёт API, но не ищет установленный BotBrowser и не выбирает .enc. Передача профиля другому браузеру не превращает его в BotBrowser. Отклоняйте отсутствующие пути и сохраняйте версию пакета для диагностики совместимости.

Новый контекст делает два запуска несвязанными?

Нет. Он разделяет хранилище; сервер по-прежнему видит сеть, аккаунт, характер запросов и свойства браузера. Это изоляция приложения, не гарантия анонимности. Она предотвращает смешение состояния, а не разрешает превышение лимитов сайта.

Лучше ждать networkidle, чем селектор?

Тишина сети не равна нужному бизнес-состоянию. Данные могут быть готовы при продолжающемся опросе, а страница ошибки может не отправлять запросы. Используйте сигнал завершения приложения и проверяйте поля. Контрольный пример требует ссылки в DOM и видимые уникальные поля карточки; другому приложению нужен собственный контракт.

Источники

Практический вывод

Выберите явный исполняемый файл и совместимый профиль, сохранив разрешённую сессию в одном контексте между списком и карточками. Принимайте данные только после проверок навигации, источник и схемы. Cookies, профиль и выгрузка требуют отдельных решений безопасности и хранения.

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

#Playwright#веб-скрейпинг#браузерные контексты#браузерные профили#Session Storage

Эта статья входит в руководство: Начало работы с автоматизацией браузера: карта

Выполните ту же проверку в BotBrowser

Загрузите профиль, сохраните внутри браузера те сигналы, о которых только что прочитали, и подтвердите результат на своей машине до раскатки.