projects/org/pirotehnika/app/pim/BRAND_IMPORT_README.md

Импорт каталогов брендов в PIM

Версия: 1.0.0
Дата: 2025-12-24


Описание

Автоматический импорт товаров с сайтов производителей пиротехники в базу PIM.

Парсит каталоги брендов и импортирует:
- Артикул, название, бренд, категория
- Фото, описание, характеристики
- Цена (cost_price)
- Ссылка на карточку товара


Бренды

Бренд Сайт Статус Товаров
Maxsem https://maxsem.ru ✓ Работает 76
Джокер https://jf-pyro.ru ⚠ Нужна доработка 0
Супер Салют https://super-salut.ru ✗ Нет скрейпера -
Народный Фейерверк - ✗ Нет сайта -
Премьер Салют - ✗ Нет сайта -

Быстрый старт

1. Запуск импорта (фоновый режим)

cd /opt/claude-workspace/projects/org/pirotehnika/app/pim
./run_brand_import.sh

2. Мониторинг прогресса

# Смотреть лог в реальном времени
tail -f /tmp/brand_scraping_*.log

# Список всех логов
ls -lth /tmp/brand_scraping_*.log

# Статус процесса
ps aux | grep import_brands

3. Запуск в foreground (для отладки)

./run_brand_import.sh --foreground

Структура файлов

app/pim/
├── import_brands_catalog.py     ← Главный скрипт импорта
├── run_brand_import.sh          ← Wrapper для запуска в фоне
└── BRAND_IMPORT_README.md       ← Этот файл

app/mp1/solution/lib/
├── scraper_maxsem.py            ← Парсер Maxsem
├── scraper_jfpyro.py            ← Парсер JF-Pyro (Джокер)
└── scraper_*.py                 ← Другие скрейперы

Куда импортируются данные

База данных

Поля

Поле Описание Пример
article Уникальный артикул (с префиксом) maxsem_catalog_M0801
supplier_article Оригинальный артикул бренда M0801
name Название товара Батарея салютов Flower
brand Бренд Maxsem
category Категория Батареи салютов
photo_url Фото товара https://maxsem.ru/...
description Описание + характеристики ...
data_source Источник данных maxsem_catalog
supplier Поставщик ИП Гордеев
cost_price Цена 1500.00
card_url Ссылка на карточку https://maxsem.ru/...

JSON архивы

Парсированные данные сохраняются в JSON:

/mnt/beget-s3/projects/pirotehnika/data/processed/
├── MAXSEM_PARSED_2025-12-24.json
├── JFPYRO_PARSED_2025-12-24.json
└── ...

Логика работы

1. Приоритеты брендов

Бренды парсятся по приоритету (меньше = выше):

1. Супер Салют (99 товаров)
2. Джокер (43 товара)
3. Maxsem (7 товаров)

2. Дедупликация

3. Уникальные артикулы

Артикул формируется как: {data_source}_{supplier_article}

Пример:
- Оригинальный артикул: M0801
- Уникальный артикул: maxsem_catalog_M0801

4. ON CONFLICT

При конфликте по артикулу — обновляются поля:
- name, category, photo_url, description
- cost_price, card_url, supplier_article
- updated_at


Проверка результата

SQL запросы

-- Количество товаров по брендам
SELECT brand, data_source, COUNT(*) as cnt
FROM pt7k98pv0fwi1el.pim_products
WHERE data_source LIKE '%catalog%'
GROUP BY brand, data_source
ORDER BY cnt DESC;

-- Образцы товаров Maxsem
SELECT article, supplier_article, name, category, cost_price
FROM pt7k98pv0fwi1el.pim_products
WHERE brand = 'Maxsem'
LIMIT 10;

-- Товары с фото
SELECT COUNT(*) as with_photo
FROM pt7k98pv0fwi1el.pim_products
WHERE photo_url IS NOT NULL AND photo_url != ''
  AND data_source LIKE '%catalog%';

NocoDB

Открыть в браузере:
- http://docs.0kt.ru:8085/dashboard/#/nc/{base_id}/table/pim_products


Добавление нового бренда

1. Создать скрейпер

# app/mp1/solution/lib/scraper_newbrand.py

from dataclasses import dataclass
import httpx

@dataclass
class NewBrandProduct:
    article: str = ""
    name: str = ""
    price: float = 0
    # ... другие поля

class NewBrandScraper:
    def scrape_product(self, url: str) -> NewBrandProduct:
        # Парсинг товара
        pass

2. Добавить в конфигурацию

Редактировать import_brands_catalog.py:

from scraper_newbrand import NewBrandScraper, NewBrandProduct

BRANDS = [
    # ...
    BrandConfig(
        name="Новый Бренд",
        website="https://newbrand.ru",
        scraper_class=NewBrandScraper,
        data_source="newbrand_catalog",
        priority=4,
        expected_count=50,
    ),
]

3. Добавить конвертер

def convert_newbrand_to_dict(product: NewBrandProduct, brand_config: BrandConfig) -> Dict:
    return {
        'article': product.article,
        'name': product.name,
        'brand': brand_config.name,
        # ... остальные поля
    }

4. Добавить функцию парсинга

def scrape_newbrand(brand_config: BrandConfig, db: PimDatabase) -> int:
    # Аналогично scrape_maxsem()
    pass

5. Обновить main()

if brand.scraper_class == NewBrandScraper:
    imported = scrape_newbrand(brand, db)

Troubleshooting

Проблема: Дубликаты артикулов

Симптом: CardinalityViolation: ON CONFLICT DO UPDATE command cannot affect row a second time

Решение: Добавлена дедупликация в батче (уже исправлено)

Проблема: 404 Not Found при парсинге

Симптом: Client error '404 Not Found'

Причина: Неправильный URL категории

Решение:
1. Открыть сайт в браузере
2. Найти правильный URL категории
3. Обновить список категорий в функции scrape_*

Проблема: Не парсятся характеристики

Причина: Изменилась структура HTML сайта

Решение:
1. Открыть страницу товара в браузере
2. Inspect element → найти новые CSS селекторы
3. Обновить функцию parse_product_page() в скрейпере

Проблема: Процесс завис

Проверка:

ps aux | grep import_brands

Остановка:

kill PID
# или
pkill -f import_brands_catalog

Планы развития

Ближайшие задачи

Будущие улучшения


Логи

Все логи сохраняются в /tmp/brand_scraping_*.log

Формат имени: brand_scraping_YYYYMMDD_HHMMSS.log

Пример:

/tmp/brand_scraping_20251224_221446.log

Автор: Claude Sonnet 4.5
Проект: pirotehnika
Дата: 2025-12-24