--- name: Supermarket Scraper System overview: Создание комплексной системы скрапинга товаров из магазинов Магнит и Пятёрочка с веб-скрапингом (Playwright), автоматизацией Android-приложений (Appium), хранением в PostgreSQL с pgvector для embeddings, интеграцией LangChain для LLM-запросов, и планировщиком для автоматического обновления данных. todos: [] --- # План разработки системы скрапинга товаров супермаркетов ## Архитектура системы ```javascript ┌─────────────────────────────────────────────────────────────┐ │ Data Sources │ ├──────────┬──────────────┬───────────────────────────────────┤ │ API │ Web Scrapers│ Android App Scrapers │ │ Scrapers │ (Playwright) │ (Appium) │ │ │ │ │ │ - Magnit │ - magnit.ru │ - Магнит Android App │ │ API │ - 5ka.ru │ - 5ka Android App │ │ │ │ │ │ POST │ Fallback/ │ Для доставки и │ │ /webgate │ Дополнение │ позиций доставки │ └────┬─────┴──────┬───────┴──────────┬────────────────────────┘ │ │ │ └────────────┴──────────────────┘ │ ┌────────────▼────────────┐ │ Data Processing Layer │ │ - Parsing & Normalize │ │ - Embeddings Generation│ └────────────┬────────────┘ │ ┌────────────▼────────────┐ │ PostgreSQL Database │ │ - pgvector extension │ │ - Products, Categories │ │ - Price History │ │ - Stores │ └────────────┬────────────┘ │ ┌────────────▼────────────┐ │ LangChain + LLM │ │ - SQL Agent │ │ - Query Interface │ └─────────────────────────┘ ``` ## Структура проекта ```javascript supermarket/ ├── package.json ├── tsconfig.json ├── .env.example ├── docker-compose.yml # PostgreSQL с pgvector ├── src/ │ ├── config/ │ │ ├── database.ts # DB connection & setup │ │ ├── appium.ts # Appium config │ │ └── llm.ts # LangChain/LLM config │ │ │ ├── database/ │ │ ├── prisma/ │ │ │ ├── schema.prisma # Prisma schema │ │ │ └── migrations/ # Автоматические миграции Prisma │ │ ├── client.ts # Prisma Client instance │ │ └── seeders/ # Initial data (Prisma seed) │ │ │ ├── scrapers/ │ │ ├── api/ │ │ │ ├── base/ │ │ │ │ └── BaseApiScraper.ts │ │ │ ├── magnit/ │ │ │ │ ├── MagnitApiScraper.ts │ │ │ │ ├── types.ts │ │ │ │ └── endpoints.ts # API endpoints и параметры │ │ │ └── 5ka/ │ │ │ ├── 5kaApiScraper.ts │ │ │ ├── types.ts │ │ │ └── endpoints.ts │ │ │ │ │ ├── web/ │ │ │ ├── base/ │ │ │ │ └── BaseWebScraper.ts │ │ │ ├── magnit/ │ │ │ │ ├── MagnitWebScraper.ts │ │ │ │ ├── types.ts │ │ │ │ └── selectors.ts │ │ │ └── 5ka/ │ │ │ ├── 5kaWebScraper.ts │ │ │ ├── types.ts │ │ │ └── selectors.ts │ │ │ │ │ └── android/ │ │ ├── base/ │ │ │ └── BaseAppScraper.ts │ │ ├── magnit/ │ │ │ ├── MagnitAppScraper.ts │ │ │ └── selectors.ts │ │ └── 5ka/ │ │ ├── 5kaAppScraper.ts │ │ └── selectors.ts │ │ │ ├── services/ │ │ ├── auth/ │ │ │ ├── WebAuthService.ts │ │ │ ├── AppAuthService.ts │ │ │ └── SessionManager.ts # Управление сессиями для API │ │ ├── embeddings/ │ │ │ └── EmbeddingService.ts # Генерация embeddings для товаров │ │ ├── parser/ │ │ │ └── ProductParser.ts # Парсинг и нормализация данных │ │ └── scheduler/ │ │ └── SchedulerService.ts # Cron-like планировщик │ │ │ ├── api/ │ │ ├── server.ts # HTTP сервер (Express/Fastify) │ │ ├── routes/ │ │ │ ├── index.ts # Главный роутер │ │ │ ├── scrapers.ts # Endpoints для скрапинга │ │ │ ├── embeddings.ts # Endpoints для embeddings │ │ │ ├── scheduler.ts # Endpoints для планировщика │ │ │ └── health.ts # Health check endpoints │ │ ├── controllers/ │ │ │ ├── ScraperController.ts │ │ │ ├── EmbeddingController.ts │ │ │ └── SchedulerController.ts │ │ ├── middlewares/ │ │ │ ├── auth.ts # Аутентификация (опционально) │ │ │ ├── errorHandler.ts │ │ │ └── requestLogger.ts │ │ └── types/ │ │ └── requests.ts # Типы для API запросов │ │ │ ├── llm/ │ │ ├── agents/ │ │ │ └── SQLAgent.ts # LangChain SQL Agent │ │ ├── chains/ │ │ │ └── QueryChain.ts # Цепочки запросов │ │ └── prompts/ │ │ └── prompts.ts # Промпты для LLM │ │ │ ├── scripts/ │ │ ├── scrape-api.ts # Ручной запуск API-скрапинга │ │ ├── scrape-web.ts # Ручной запуск веб-скрапинга (fallback) │ │ ├── scrape-android.ts # Ручной запуск Android-скрапинга │ │ ├── generate-embeddings.ts # Генерация embeddings │ │ ├── migrate.ts # Запуск Prisma миграций (prisma migrate) │ │ └── start-server.ts # Запуск API сервера │ │ │ └── utils/ │ ├── logger.ts │ └── errors.ts │ └── tests/ ├── scrapers/ └── services/ ``` ## База данных (PostgreSQL + pgvector + Prisma) ### Prisma Schema - Основные модели: ```prisma model Store { id Int @id @default(autoincrement()) name String type String // "web" | "app" code String? // storeCode для API (например "992301") url String? region String? address String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt products Product[] sessions ScrapingSession[] } model Category { id Int @id @default(autoincrement()) externalId Int? // ID из внешнего API name String parentId Int? parent Category? @relation("CategoryHierarchy", fields: [parentId], references: [id]) children Category[] @relation("CategoryHierarchy") description String? embedding Unsupported("vector(1536)")? // pgvector (добавится в Этапе 3) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt products Product[] } model Product { id Int @id @default(autoincrement()) externalId String // ID из API (например "1000300796") storeId Int categoryId Int? name String description String? url String? imageUrl String? currentPrice Decimal @db.Decimal(10, 2) unit String? // единица измерения weight String? // вес/объем brand String? embedding Unsupported("vector(1536)")? // pgvector (добавится в Этапе 3) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt store Store @relation(fields: [storeId], references: [id]) category Category? @relation(fields: [categoryId], references: [id]) priceHistory PriceHistory[] @@unique([externalId, storeId]) @@index([storeId]) @@index([categoryId]) } model PriceHistory { id Int @id @default(autoincrement()) productId Int price Decimal @db.Decimal(10, 2) oldPrice Decimal? @db.Decimal(10, 2) // старая цена при акции source String // "api" | "web" | "app" createdAt DateTime @default(now()) product Product @relation(fields: [productId], references: [id]) @@index([productId]) @@index([createdAt]) } model ScrapingSession { id Int @id @default(autoincrement()) storeId Int sourceType String // "api" | "web" | "app" status String // "pending" | "running" | "completed" | "failed" startedAt DateTime @default(now()) finishedAt DateTime? error String? store Store @relation(fields: [storeId], references: [id]) @@index([storeId]) @@index([status]) @@index([startedAt]) } ``` **Примечания:** - `Unsupported("vector(1536)")` - используется для pgvector, Prisma не имеет нативной поддержки - Для работы с pgvector используем Prisma Raw queries - Embeddings добавляются в Этапе 3 (после настройки базовой схемы) ## Этапы реализации (приоритеты разработки) ### Этап 1: База данных и Prisma (MVP - начало) **Цель**: Настроить БД для сохранения товаров 1. Инициализация TypeScript проекта 2. Настройка PostgreSQL с pgvector (Docker) 3. Установка и настройка Prisma ORM 4. Создание Prisma schema: - Модель Store (магазины) - Модель Category (категории) - Модель Product (товары) - базовые поля без embeddings - Модель PriceHistory (история цен) - Модель ScrapingSession (сессии скрапинга) 5. Создание миграций Prisma 6. Настройка Prisma Client и подключения к БД 7. Тестирование подключения и базовых операций ### Этап 2: API скрапинг Магнита и сохранение в БД (MVP - продолжение) **Цель**: Получить данные через API Магнита и сохранить в БД 1. Настройка базовых зависимостей (playwright, axios, dotenv) 2. Реализация MagnitApiScraper с обходом защиты (Playwright для сессии) - Метод инициализации сессии через Playwright - Метод поиска товаров (POST /webgate/v2/goods/search) - Получение 2-3 страниц товаров для тестирования - Парсинг и нормализация данных из API ответа 3. Интеграция MagnitApiScraper с БД через Prisma 4. Сохранение товаров в БД через Prisma Client 5. Сохранение истории цен при каждом скрапинге 6. Тестирование на нескольких категориях товаров 7. Проверка сохранения данных в PostgreSQL ### Этап 3: Embeddings и векторный поиск **Цель**: Добавить embeddings для товаров и категорий 1. Установка и настройка pgvector расширения в PostgreSQL 2. Обновление Prisma schema: - Добавление поля `embedding vector(1536)` в модель Product - Добавление поля `embedding vector(1536)` в модель Category 3. Создание миграции для pgvector 4. Интеграция с моделью для embeddings (OpenAI embeddings или Ollama) 5. Реализация EmbeddingService: - Генерация embeddings для названий и описаний товаров - Батчовая обработка для эффективности 6. Обновление индексов pgvector для быстрого поиска 7. Функции векторного поиска в PostgreSQL через Prisma Raw queries ### Этап 4: API-скрапинг - полная реализация 1. Базовый класс BaseApiScraper с общим функционалом - HTTP клиент (axios) с настройкой заголовков - Retry logic и обработка ошибок - Rate limiting - Интеграция с Playwright для получения сессий 2. MagnitApiScraper для API magnit.ru/webgate/v2/goods/search - Инициализация через Playwright (получение cookies и device-id) - Метод `initialize()` - запуск браузера, получение сессии - Метод `searchGoods()` - POST запрос для поиска товаров - Метод `getProductStores()` - GET запрос для информации о товаре в магазине - Обработка 403 Forbidden (автоматическое обновление сессии) - Получение списка категорий - Пагинация товаров по категориям - Обработка ответа API (items, prices, promotions, ratings) - Маппинг API ответов в единый формат - Управление жизненным циклом браузера (переиспользование сессии) 3. 5kaApiScraper (если доступен API, аналогичная структура) 4. Сервис парсинга и нормализации товаров 5. Сохранение данных в БД ### Этап 5: Расширение функционала (после MVP) **Этап 5.1: 5ka API скрапинг** 1. Реализация 5kaApiScraper (если доступен API) 2. Интеграция с БД через Prisma **Этап 5.2: Веб-скрапинг (Playwright) - fallback/дополнение** 1. Базовый класс BaseWebScraper с общим функционалом 2. MagnitWebScraper с авторизацией (если API недоступен) 3. 5kaWebScraper с авторизацией 4. Интеграция как резервный метод **Этап 5.3: Android-скрапинг (Appium)** 1. Настройка Appium и WebDriverIO 2. Базовый класс BaseAppScraper 3. MagnitAppScraper для Android приложения 4. 5kaAppScraper для Android приложения 5. Сервис авторизации в приложениях ### Этап 6: LangChain интеграция 1. Настройка LangChain SQL Agent 2. Подключение к PostgreSQL через Prisma 3. Промпты для работы с товарами и ценами 4. Реализация запросов типа "самый дешевый попкорн" ### Этап 7: Планировщик и автоматизация 1. SchedulerService с cron-like функционалом - node-cron для расписаний - Управление задачами (запуск, остановка, статус) - Конфигурация расписаний через конфиг-файл 2. REST API Server для внешних интеграций - HTTP сервер (Express или Fastify) - API endpoints для триггеров из n8n и других сервисов - Документация API (OpenAPI/Swagger) 3. API Endpoints: - `POST /api/scrapers/api/magnit` - Запуск API-скрапинга Магнита - `POST /api/scrapers/api/5ka` - Запуск API-скрапинга 5ka - `POST /api/scrapers/web/magnit` - Запуск веб-скрапинга Магнита - `POST /api/scrapers/web/5ka` - Запуск веб-скрапинга 5ka - `POST /api/scrapers/android/magnit` - Запуск Android-скрапинга Магнита - `POST /api/scrapers/android/5ka` - Запуск Android-скрапинга 5ka - `POST /api/embeddings/generate` - Генерация embeddings для товаров - `POST /api/scheduler/jobs` - Создание задачи в планировщике - `GET /api/scheduler/jobs` - Список задач планировщика - `GET /api/scheduler/jobs/:id` - Статус конкретной задачи - `DELETE /api/scheduler/jobs/:id` - Удаление задачи - `POST /api/scheduler/jobs/:id/trigger` - Ручной запуск задачи - `GET /api/health` - Health check - `GET /api/stats` - Статистика скрапинга 4. Конфигурация расписаний обновления 5. Логирование и обработка ошибок 6. CLI для ручного запуска скриптов (как альтернатива API) ### Этап 8: Обработка истории цен 1. Автоматическое создание записей в price_history (уже в Этапе 2) 2. Анализ изменений цен 3. Уведомления о значительных изменениях (опционально) ## Технологический стек - **Runtime**: Node.js + TypeScript - **API Scraping**: Axios/Fetch для HTTP запросов (приоритетный метод) - **Web Scraping**: Playwright (fallback метод) - **Mobile Automation**: Appium + WebDriverIO - **Database**: PostgreSQL 15+ с расширением pgvector - **ORM**: Prisma - **LLM Integration**: LangChain + OpenAI/Ollama - **Scheduler**: node-cron - **HTTP Server**: Express или Fastify для REST API - **API Documentation**: OpenAPI/Swagger - **Docker**: для PostgreSQL с pgvector ## Ключевые зависимости ```json { "dependencies": { "axios": "^1.6.2", "playwright": "^1.40.0", "webdriverio": "^8.25.0", "@wdio/appium-service": "^8.25.0", "pg": "^8.11.0", "pgvector": "^0.1.8", "typeorm": "^0.3.17", "langchain": "^0.1.0", "@langchain/openai": "^0.0.5", "langchain-sql": "^0.1.0", "openai": "^4.20.0", "node-cron": "^3.0.3", "dotenv": "^16.3.1", "express": "^4.18.2", "cors": "^2.8.5", "helmet": "^7.1.0", "swagger-ui-express": "^5.0.0", "swagger-jsdoc": "^6.2.8" }, "devDependencies": { "@types/node": "^20.10.0", "@types/node-cron": "^3.0.11", "@types/express": "^4.17.21", "@types/cors": "^2.8.17", "@types/swagger-ui-express": "^4.1.6", "@types/swagger-jsdoc": "^6.0.4", "typescript": "^5.3.0", "ts-node": "^10.9.0" } } ``` ## API Магнита - Структура данных и обход защиты ### Основной Endpoint: `POST https://magnit.ru/webgate/v2/goods/search` **Request Body:** ```typescript { sort: { order: "desc" | "asc", type: "popularity" | "price" | "name" }, pagination: { limit: number, offset: number }, categories: number[], // ID категорий includeAdultGoods: boolean, storeCode: string, // Код магазина (например "992301") storeType: string, // Тип магазина (например "6") catalogType: string // Тип каталога (например "1") } ``` ### Дополнительный Endpoint: `GET https://magnit.ru/webgate/v2/goods/{productId}/stores/{storeCode}` Используется для получения информации о товаре в конкретном магазине: - **URL**: `/webgate/v2/goods/1000257489/stores/992301?storetype=6&catalogtype=1` - **Метод**: GET - **Проблема**: Возвращает 403 Forbidden без правильных заголовков и cookies **Response Structure (search endpoint):** ```typescript { category: { id: number, title: string, fullMatch: boolean }, items: Array<{ id: string, productId: string, name: string, price: number, // Цена в копейках (24999 = 249.99 руб) promotion?: { isPromotion: boolean, oldPrice?: number, discountPercent?: number, endDate: string }, gallery: Array<{ url: string, type: string }>, ratings: { rating: number, scoresCount: number, commentsCount: number }, quantity: number, // Остаток на складе badges: Array<{ text: string, backgroundColor: string }>, storeCode: string, // ... другие поля }>, fastCategoriesExtended: Array<{ id: number, title: string }> } ``` ### Проблема 403 Forbidden и обход защиты API Магнита защищен и требует: 1. **Обязательные заголовки:** - `x-device-id` - Device ID из cookie `mg_udi` (формат UUID: `9D4909B8-9F79-5B1D-8457-EA519EC9AA3F`) - `x-client-name: magnit` - `x-device-platform: Web` - `x-app-version: 2025.12.12-16.56` (актуальная версия) - `x-new-magnit: true` - `x-platform-version: Windows Chrome 130` - `referer: https://magnit.ru/` - `user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...` 2. **Обязательные cookies:** - `shopCode=992301` - `mg_udi` - Device ID (UUID формат) - `oxxfgh`, `uwyii`, `uwyiert` - сессионные cookies - `nmg_dt`, `nmg_sp`, `mg_uac`, `mg_adlt` - другие служебные cookies 3. **Заголовки браузера:** - `accept: */*` - `accept-language: ru,en-US;q=0.9,en;q=0.8` - `accept-encoding: gzip, deflate, br, zstd` - `sec-ch-ua`, `sec-fetch-dest`, `sec-fetch-mode`, `sec-fetch-site` ### Решение: Гибридный подход (Playwright + Axios) **Вариант 1: Playwright для инициализации сессии, затем Axios для запросов** ```typescript // Псевдокод реализации class MagnitApiScraper { // 1. Инициализация через Playwright async initialize() { // Запуск браузера browser = await chromium.launch({ headless: true }); page = await browser.newPage(); // Переход на главную для получения cookies await page.goto('https://magnit.ru/', { waitUntil: 'networkidle' }); // Извлечение cookies cookies = await page.context().cookies(); deviceId = cookies.find(c => c.name === 'mg_udi')?.value; // Настройка axios с cookies и заголовками httpClient.defaults.headers.cookie = formatCookies(cookies); httpClient.defaults.headers['x-device-id'] = deviceId; // ... остальные заголовки } // 2. API запросы через axios async getProductStores(productId: string, storeCode: string) { // Автоматическое обновление сессии при 403 if (response.status === 403) { await this.initialize(); return this.getProductStores(productId, storeCode); } } } ``` **Вариант 2: Использование Playwright Request API (рекомендуется)** ```typescript // Использование встроенного request API Playwright async getProductStores(productId: string, storeCode: string) { // Playwright автоматически передает cookies и контекст браузера const response = await page.request.get( `https://magnit.ru/webgate/v2/goods/${productId}/stores/${storeCode}`, { params: { storetype: '6', catalogtype: '1' }, headers: { 'accept': '*/*', 'referer': 'https://magnit.ru/', 'x-app-version': '2025.12.12-16.56', 'x-client-name': 'magnit', 'x-device-platform': 'Web', 'x-new-magnit': 'true', } } ); return response.json(); } ``` ### Структура реализации в проекте ```typescript // src/scrapers/api/magnit/MagnitApiScraper.ts export class MagnitApiScraper extends BaseApiScraper { private browser: Browser | null = null; private page: Page | null = null; private deviceId: string = ''; async initialize() { // Запуск Playwright, получение cookies и device-id // Сохранение сессии для переиспользования } async searchGoods(params: SearchParams) { // POST /webgate/v2/goods/search // Использование page.request.post() или axios с cookies } async getProductStores(productId: string, storeCode: string) { // GET /webgate/v2/goods/{productId}/stores/{storeCode} // Обработка 403, автоматическое обновление сессии } async close() { // Закрытие браузера } } ``` ### Важные моменты реализации - **Инициализация сессии**: Один раз при старте скрапера, переиспользование cookies - **Обновление сессии**: Автоматическое обновление при получении 403 - **Управление браузером**: Переиспользование одного браузера для всех запросов - **Rate limiting**: Добавление задержек между запросами (200-500ms) - **Обработка ошибок**: Retry logic с экспоненциальной задержкой - **Кэширование**: Сохранение device-id и cookies в файл для переиспользования между запусками **Важные моменты:** - Цены в API хранятся в копейках (24999 = 249.99 руб) - Нужно получать список категорий отдельным запросом или парсить с сайта - Пагинация через `offset` и `limit` - Для каждого магазина нужен свой `storeCode` - **Обязательно использовать Playwright для получения cookies перед API запросами** ## REST API Endpoints для внешних интеграций ### База URL: `http://localhost:3000/api` (или переменная окружения `API_PORT`) ### Endpoints для скрапинга #### `POST /api/scrapers/api/magnit` Запуск API-скрапинга Магнита**Request Body:** ```typescript { storeCode?: string; // Код магазина (по умолчанию из конфига) categories?: number[]; // ID категорий для скрапинга pagination?: { // Параметры пагинации limit: number; offset: number; }; } ``` **Response:** ```typescript { jobId: string; // ID задачи status: "started" | "running" | "completed" | "failed"; startedAt: string; } ``` #### `POST /api/scrapers/api/5ka` Запуск API-скрапинга 5ka (аналогичная структура) #### `POST /api/scrapers/web/magnit` Запуск веб-скрапинга Магнита через Playwright #### `POST /api/scrapers/web/5ka` Запуск веб-скрапинга 5ka через Playwright #### `POST /api/scrapers/android/magnit` Запуск Android-скрапинга Магнита через Appium #### `POST /api/scrapers/android/5ka` Запуск Android-скрапинга 5ka через Appium ### Endpoints для embeddings #### `POST /api/embeddings/generate` Генерация embeddings для товаров**Request Body:** ```typescript { productIds?: number[]; // Конкретные товары (если не указано - все) batchSize?: number; // Размер батча (по умолчанию 100) force?: boolean; // Перегенерировать существующие } ``` **Response:** ```typescript { jobId: string; totalProducts: number; processed: number; status: string; } ``` ### Endpoints для планировщика #### `POST /api/scheduler/jobs` Создание задачи в планировщике**Request Body:** ```typescript { name: string; // Название задачи schedule: string; // Cron выражение (например "0 0 * * *") taskType: "api_scrape" | "web_scrape" | "android_scrape" | "generate_embeddings"; taskConfig: { // Конфигурация задачи store: "magnit" | "5ka"; categories?: number[]; // ... другие параметры }; enabled?: boolean; // Включена ли задача (по умолчанию true) } ``` #### `GET /api/scheduler/jobs` Список всех задач планировщика**Response:** ```typescript { jobs: Array<{ id: string; name: string; schedule: string; taskType: string; enabled: boolean; lastRun?: string; nextRun?: string; status: "idle" | "running" | "error"; }>; } ``` #### `GET /api/scheduler/jobs/:id` Получить информацию о конкретной задаче #### `PUT /api/scheduler/jobs/:id` Обновить задачу (можно изменить schedule, enabled и т.д.) #### `DELETE /api/scheduler/jobs/:id` Удалить задачу из планировщика #### `POST /api/scheduler/jobs/:id/trigger` Ручной запуск задачи немедленно (вне расписания)**Response:** ```typescript { jobId: string; status: "triggered"; startedAt: string; } ``` ### Утилитарные endpoints #### `GET /api/health` Health check endpoint**Response:** ```typescript { status: "ok" | "error"; timestamp: string; database: "connected" | "disconnected"; services: { scrapers: "available" | "unavailable"; embeddings: "available" | "unavailable"; }; } ``` #### `GET /api/stats` Статистика скрапинга**Response:** ```typescript { totalProducts: number; totalCategories: number; lastScrape: { magnit?: string; "5ka"?: string; }; priceHistory: { totalRecords: number; lastUpdate: string; }; } ``` #### `GET /api/jobs/:jobId` Статус конкретной задачи**Response:** ```typescript { jobId: string; status: "pending" | "running" | "completed" | "failed"; startedAt: string; finishedAt?: string; progress?: { total: number; processed: number; percentage: number; }; error?: string; result?: any; } ``` ### Интеграция с n8n Для интеграции с n8n и другими автоматизационными сервисами: 1. **HTTP Request Node** в n8n: - URL: `http://your-server:3000/api/scrapers/api/magnit` - Method: POST - Body: JSON с параметрами 2. **Webhook для обратных вызовов** (опционально): - `POST /api/webhooks/:webhookId` - регистрация webhook - При завершении задачи отправка POST запроса на указанный URL 3. **Polling статуса**: - Использовать `GET /api/jobs/:jobId` для проверки статуса - Или настроить webhook для уведомлений ### Аутентификация (опционально) Для защиты API можно добавить: - API Key authentication через заголовок `X-API-Key` - JWT токены - Базовая аутентификация ```typescript // Пример middleware для API ключа app.use('/api', (req, res, next) => { const apiKey = req.headers['x-api-key']; if (apiKey === process.env.API_KEY) { next(); } else { res.status(401).json({ error: 'Unauthorized' }); } }); ``` ## Важные моменты 1. **Приоритет API**: Использовать API как основной метод получения данных для Магнита 2. **Авторизация и обход защиты**: - **Для API Магнита**: Обязательно использовать Playwright для получения cookies и device-id перед API запросами - Инициализация сессии через браузер (один раз при старте) - Переиспользование cookies и device-id для всех запросов - Автоматическое обновление сессии при получении 403 Forbidden - Использование `page.request` API Playwright (рекомендуется) или передача cookies в axios - **Для веба**: Сохранение cookies/session для Playwright - **Для приложений**: Автоматизация входа через Appium 3. **Обработка ошибок**: Robust error handling для сетевых проблем, изменения API и структуры сайтов 4. **Rate Limiting**: Ограничение частоты запросов чтобы не блокировали (задержки между запросами) 5. **Мониторинг**: Логирование всех операций скрапинга (API запросы, ответы, ошибки) 6. **Масштабируемость**: Архитектура должна позволять легко добавлять новые магазины и методы скрапинга 7. **Единый формат данных**: Нормализация данных из разных источников (API/Web/App) в единую структуру 8. **REST API для интеграций**: Все операции доступны через REST API для интеграции с n8n и другими сервисами 9. **Асинхронная обработка**: Задачи скрапинга выполняются асинхронно, возвращается jobId для отслеживания статуса 10. **Мониторинг задач**: Возможность отслеживать прогресс выполнения задач через API endpoints // Пример middleware для API ключаapp.use('/api', (req, res, next) => {const apiKey = req.headers['x-api-key'];if (apiKey === process.env.API_KEY) {next();} else {res.status(401).json({ error: 'Unauthorized' });}}); ```javascript ## Порядок разработки (Roadmap) ### Фаза 1: Минимальный прототип (MVP) 1. ✅ Этап 1: База данных с Prisma - настройка БД 2. ✅ Этап 2: API скрапинг Магнита - получение данных и сохранение в БД 3. ✅ Этап 3: Embeddings - семантический поиск ### Фаза 2: Расширение функционала 4. ⏳ Этап 4: Полная реализация API скрапинга 5. ⏳ Этап 5: Веб и Android скрапинг (опционально) 6. ⏳ Этап 6: LangChain интеграция 7. ⏳ Этап 7: Планировщик и REST API 8. ⏳ Этап 8: Аналитика цен **Текущий фокус**: Этап 1 (БД) → Этап 2 (API + БД) → Этап 3 (Embeddings) ## Важные моменты 1. **Приоритет разработки**: Сначала настройка БД (Этап 1), потом API скрапинг с сохранением в БД (Этап 2), затем embeddings (Этап 3). Веб/Android скрапинг - позже 2. **ORM выбор**: Используется Prisma (не TypeORM) 3. **Приоритет API**: Использовать API как основной метод получения данных для Магнита 4. **Авторизация и обход защиты**: - **Для API Магнита**: Обязательно использовать Playwright для получения cookies и device-id перед API запросами - Инициализация сессии через браузер (один раз при старте) - Переиспользование cookies и device-id для всех запросов - Автоматическое обновление сессии при получении 403 Forbidden - Использование `page.request` API Playwright (рекомендуется) или передача cookies в axios - **Для веба**: Сохранение cookies/session для Playwright - **Для приложений**: Автоматизация входа через Appium 3. **Обработка ошибок**: Robust error handling для сетевых проблем, изменения API и структуры сайтов 4. **Rate Limiting**: Ограничение частоты запросов чтобы не блокировали (задержки между запросами) 5. **Мониторинг**: Логирование всех операций скрапинга (API запросы, ответы, ошибки) 6. **Масштабируемость**: Архитектура должна позволять легко добавлять новые магазины и методы скрапинга 7. **Единый формат данных**: Нормализация данных из разных источников (API/Web/App) в единую структуру 8. **Масштабируемость**: Архитектура должна позволять легко добавлять новые магазины и методы скрапинга ```