Перейти к основному содержимому

Рефакторинг

Может показаться, что данные уже извлечены и краулер готов, но на самом деле это только начало. Для краткости мы полностью опустили обработку ошибок, прокси, логирование, архитектуру, тесты, документацию и другие компоненты, которые должно иметь надежное программное обеспечение. Хорошая новость в том, что обработка ошибок в основном выполняется самим Crawlee, так что об этом можно не беспокоиться, если вам не нужна какая-то особая логика.

Об автоматическом обходе защиты от ботов

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

Чтобы узнать больше, ознакомьтесь с руководствами Как избежать блокировки, Управление прокси и Управление сессиями.

Для поддержки хороших практик программирования давайте рассмотрим, как использовать Router для лучшей структуризации кода краулера.

Маршрутизация

В следующем коде мы внесли несколько изменений:

  • Разделили код на несколько файлов
  • Заменили console.log на логгер Crawlee для более красивых, цветных логов
  • Добавили Router для более чистой маршрутизации без использования условных операторов if

В файле main.mjs мы размещаем общую структуру краулера:

src/main.mjs
import { PlaywrightCrawler, log } from 'crawlee';
import { router } from './routes.mjs';

// Лучше устанавливать через переменную окружения CRAWLEE_LOG_LEVEL
// или через опции конфигурации. Это только для демонстрации 😈
log.setLevel(log.LEVELS.DEBUG);

log.debug('Настройка краулера.');
const crawler = new PlaywrightCrawler({
// Вместо длинного requestHandler с условными
// операторами мы предоставляем экземпляр роутера
requestHandler: router,
});

await crawler.run(['https://warehouse-theme-metal.myshopify.com/collections']);

Затем в отдельном файле routes.mjs:

src/routes.mjs
import { createPlaywrightRouter, Dataset } from 'crawlee';

// createPlaywrightRouter() - это вспомогательная функция для получения
// лучшего автодополнения и типизации. Можно также использовать Router.create()
export const router = createPlaywrightRouter();

// Это заменяет ветку request.label === DETAIL условного оператора
router.addHandler('DETAIL', async ({ request, page, log }) => {
log.debug(`Извлечение данных: ${request.url}`);
const urlPart = request.url.split('/').slice(-1);
const manufacturer = urlPart[0].split('-')[0];

const title = await page.locator('.product-meta h1').textContent();
const sku = await page
.locator('span.product-meta__sku-number')
.textContent();

const priceElement = page
.locator('span.price')
.filter({
hasText: '$',
})
.first();

const currentPriceString = await priceElement.textContent();
const rawPrice = currentPriceString.split('$')[1];
const price = Number(rawPrice.replaceAll(',', ''));

const inStockElement = page
.locator('span.product-form__inventory')
.filter({
hasText: 'В наличии',
})
.first();

const inStock = (await inStockElement.count()) > 0;

const results = {
url: request.url,
manufacturer,
title,
sku,
currentPrice: price,
availableInStock: inStock,
};

log.debug(`Сохранение данных: ${request.url}`);
await Dataset.pushData(results);
});

router.addHandler('CATEGORY', async ({ page, enqueueLinks, request, log }) => {
log.debug(`Добавление страниц пагинации в очередь для: ${request.url}`);

await page.waitForSelector('.product-item > a');
await enqueueLinks({
selector: '.product-item > a',
label: 'DETAIL',
});

const nextButton = await page.$('a.pagination__next');
if (nextButton) {
await enqueueLinks({
selector: 'a.pagination__next',
label: 'CATEGORY',
});
}
});

router.addDefaultHandler(async ({ request, page, enqueueLinks, log }) => {
log.debug(`Добавление категорий в очередь со страницы: ${request.url}`);

await page.waitForSelector('.collection-block-item');
await enqueueLinks({
selector: '.collection-block-item',
label: 'CATEGORY',
});
});

Давайте подробнее рассмотрим внесенные изменения. Мы считаем, что эти модификации улучшат читаемость и управляемость краулера.

Разделение кода на несколько файлов

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

Использование логгера Crawlee вместо console.log

Мы не будем подробно рассказывать об объекте log из Crawlee, так как вы можете прочитать об этом в документации, но нужно подчеркнуть одну вещь: уровни логирования.

Логгер Crawlee имеет несколько уровней, таких как log.debug, log.info или log.warning. Это не только делает ваши логи более читаемыми, но и позволяет выборочно отключать определенные уровни с помощью функции log.setLevel() или установки переменной окружения CRAWLEE_LOG_LEVEL. Благодаря этому вы можете добавить множество отладочных логов в ваш краулер, не засоряя лог, когда они не нужны, но они будут готовы помочь при возникновении проблем.

Использование роутера для структурирования краулинга

Изначально использование простых условных операторов if/else для выбора различной логики на основе типа страницы может показаться более читаемым. Однако этот подход может стать неудобным при наличии более двух типов страниц, особенно когда логика для каждой страницы растягивается на десятки или даже сотни строк кода.

Хорошей практикой в любом языке программирования является разделение логики на небольшие части, которые легко читать и понимать. Прокручивать тысячу строк в requestHandler(), где все взаимодействует со всем, а переменные могут использоваться везде - это не лучший подход, и отладка такого кода становится сложной. Поэтому мы предпочитаем разделение маршрутов на отдельные файлы.

Следующие шаги

В следующем и последнем шаге вы узнаете, как развернуть ваш проект Crawlee в облаке. Если вы использовали CLI для создания проекта, у вас уже есть готовый Dockerfile, и следующий раздел покажет, как легко развернуть его на Платформе Apify.