Создание новых парсеров 🕷️
В системе 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(),
# ...
)
🧪 Тестирование
Всегда проверяйте обе стратегии перед коммитом:
- Тест сбора товаров:
- Тест поиска категорий:
🚀 Регистрация в Workflow
- Воркер: Пропишите паук в
services/run_worker.py(словарьSPIDERS). - База: Добавьте Хаб в таблицу
parsing_sources.Система сама найдет дочерние категории и создаст для них задачи типаINSERT INTO parsing_sources (url, type, site_key, strategy) VALUES ('https://myshop.ru/all-gifts', 'hub', 'myshop', 'discovery');listсо стратегиейdeep.