Перейти к содержанию

Создание новых парсеров 🕷️

В системе Gifty сбор данных автоматизирован с помощью Scrapy. Эта станица описывает, как правильно создавать, тестировать и масштабировать пауков.


🚀 Основные концепции

1. Как работает Паук (Yield и Генераторы)

В Scrapy методы парсинга — это генераторы. Вместо того чтобы собирать все товары в огромный список и возвращать его в конце (return list), мы используем ключевое слово yield.

Почему это важно? * Экономия памяти: Scrapy обрабатывает объекты по одному, как только они "вылетают" из генератора. Это позволяет парсить миллионы товаров, не забивая RAM воркера. * Потоковость: Pipeline (цепочка обработки) начинает валидировать и отправлять первый товар, пока паук еще парсит второй.

def parse_catalog(self, response):
    for card in response.css('.card'):
        # Мы "выбрасываем" товар в систему сразу
        yield self.create_product(...)

2. Стратегии: Discovery vs Deep

Система различает две фазы жизни источника:

  • Discovery (strategy="discovery"): Используется для Хабов (главных страниц разделов). Паук ищет ссылки на конечные категории. Результат — объекты CategoryItem.
  • Deep (strategy="deep"): Используется для Списков товаров (Листингов). Паук собирает товары и идет по пагинации. Результат — объекты ProductItem.

🛠️ Реализация Паука

Используйте GiftyBaseSpider в качестве основы. Он берет на себя рутину: инициализацию URL, проброс source_id и выбор нужного метода парсинга в зависимости от стратегии.

Пример реализации

from gifty_scraper.base_spider import GiftyBaseSpider
from gifty_scraper.items import CategoryItem

class MyShopSpider(GiftyBaseSpider):
    name = "myshop"
    site_key = "myshop"

    def parse_discovery(self, response):
        """Фаза 1: Собираем ссылки на подкатегории"""
        for link in response.css('a.category-link'):
            yield CategoryItem(
                name=link.css('::text').get().strip(),
                url=response.urljoin(link.attrib['href']),
                site_key=self.site_key
            )

    def parse_catalog(self, response):
        """Фаза 2: Собираем товары из конкретной категории"""
        for card in response.css('.product-card'):
            yield self.create_product(
                title=card.css('.name::text').get(),
                price=card.css('.price::text').re_first(r'\d+'),
                product_url=response.urljoin(card.css('a::attr(href)').get()),
                image_url=response.urljoin(card.css('img::attr(src)').get()),
                merchant="My Shop"
            )

        # Пагинация
        if self.strategy == "deep":
            next_page = response.css('a.next::attr(href)').get()
            if next_page:
                yield response.follow(next_page, self.parse_catalog)

🏗️ Обработка сложных случаев

Случай: Разная верстка для разных категорий

Иногда внутри одного сайта категории "Одежда" и "Электроника" имеют абсолютно разные селекторы.

Вариант А: Проверка через URL (в коде)

def parse_catalog(self, response):
    if "/electronics/" in response.url:
        return self.parse_electronics(response)
    return self.parse_generic(response)

Вариант Б: Использование Config (через базу) Если вы хотите менять логику без пересборки кода, используйте поле config в таблице parsing_sources. Все данные из него доступны пауку в self.config.

def parse_catalog(self, response):
    title_css = self.config.get('custom_title_css', '.default-title')
    yield self.create_product(
        title=response.css(title_css).get(),
        # ...
    )


🧪 Тестирование

Всегда проверяйте обе стратегии перед коммитом:

  1. Тест сбора товаров:
    python3 scripts/test_spider.py myshop "URL_КАТЕГОРИИ" --limit 10
    
  2. Тест поиска категорий:
    python3 scripts/test_spider.py myshop "URL_ХАБА" --strategy discovery
    

🚀 Регистрация в Workflow

  1. Воркер: Пропишите паук в services/run_worker.py (словарь SPIDERS).
  2. База: Добавьте Хаб в таблицу parsing_sources.
    INSERT INTO parsing_sources (url, type, site_key, strategy) 
    VALUES ('https://myshop.ru/all-gifts', 'hub', 'myshop', 'discovery');
    
    Система сама найдет дочерние категории и создаст для них задачи типа list со стратегией deep.