type: standard
aspect: structure
title: "Архитектура компонентов: Parser / Scraper / Connector / Adapter"
version: 1.0.0
date: 2026-02-19
status: active
Версия: 1.0.0
Дата: 2025-12-24
Статус: СТАНДАРТ
КЛАССЫ (определения) ЭКЗЕМПЛЯРЫ (использование)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BaseConnector (абстрактный) ─── нельзя создать
│
├── BaseApiConnector ─── нельзя создать
│ │
│ └── OzonConnector ozon = OzonConnector(creds)
│ └── OneC_Connector onec = OneC_Connector(creds)
│
└── BaseDataConnector ─── нельзя создать
│
└── PostgresConnector pg = PostgresConnector(creds)
└── NocoDBConnector ndb = NocoDBConnector(creds)
КЛАСС (Class) — шаблон/чертёж, определяет ЧТО может делать.
ЭКЗЕМПЛЯР (Instance) — конкретный объект, созданный из класса.
┌────────────────────────────────────────────────────────────────────┐
│ ИСТОЧНИК → ПРИЁМНИК │
├────────────────────────────────────────────────────────────────────┤
│ │
│ CONNECTOR PARSER ADAPTER STORAGE │
│ ═══════════ ═══════ ═══════ ═══════ │
│ Транспорт Формат Маппинг Хранение │
│ │
│ API ←→ Код bytes → dict dict → model model → DB │
│ DB ←→ Код файл → записи чужое → своё │
│ File ←→ Код │
│ │
└────────────────────────────────────────────────────────────────────┘
Что делает: Устанавливает связь с внешней системой (API, БД, файл).
Не делает: Не трансформирует данные, не знает о бизнес-логике.
# library/connectors/base.py
class BaseConnector(ABC):
async def connect() → None
async def disconnect() → None
async def health_check() → bool
# library/connectors/api/ozon/client.py
class OzonConnector(BaseApiConnector):
async def get_orders() → List[Dict] # Сырые данные API
Примеры:
| Класс | Что подключает |
|-------|---------------|
| OzonConnector | OZON Seller API |
| OneC_Connector | 1С OData REST |
| PostgresConnector | PostgreSQL |
| TelegramConnector | Telegram Bot API |
Что делает: Читает формат файла и возвращает структуру данных.
Не делает: Не трансформирует, не валидирует бизнес-данные.
# library/parsers/base.py
class BaseParser(ABC):
def parse(source: bytes|Path) → List[Dict]
def can_parse(filename: str) → bool
# library/parsers/xlsx/parser.py
class XlsxParser(BaseTabularParser):
SUPPORTED_EXTENSIONS = [".xlsx", ".xls"]
def parse(source, header_row=1) → List[Dict]
Примеры:
| Класс | Что читает |
|-------|-----------|
| XlsxParser | Excel файлы |
| CsvParser | CSV файлы |
| PdfParser | PDF документы |
| JsonParser | JSON файлы |
Что делает: Загружает HTML страницы и извлекает данные.
Отличие от Parser: Сам загружает данные (имеет HTTP клиент).
# library/scrapers/base.py
class BaseScraper(ABC):
async def fetch_page(url: str) → str # HTML
async def scrape_product(url: str) → Dict # Данные продукта
async def scrape_catalog() → List[Dict] # Все продукты
# projects/pirotehnika/app/pim/scrapers/jfpyro.py
class JFPyroScraper(BaseScraper):
BASE_URL = "https://jf-pyro.ru"
async def scrape_product(url) → JFProduct
Важно: Scraper = Parser + HTTP клиент. Это подтип Parser для веб-сайтов.
Примеры:
| Класс | Что парсит |
|-------|-----------|
| JFPyroScraper | jf-pyro.ru |
| MaxsemScraper | maxsem.ru |
| PiroffScraper | piroff.ru |
Что делает: Преобразует данные из одного формата в другой.
Не делает: Не загружает данные, не сохраняет.
# library/adapters/base.py
class BaseAdapter(ABC, Generic[InputT, OutputT]):
def adapt(data: InputT) → OutputT
def adapt_many(data: List[InputT]) → List[OutputT]
# projects/pirotehnika/app/pim/adapters/price_adapter.py
class MaxsemPriceAdapter(BaseAdapter[Dict, PimProduct]):
def adapt(raw: Dict) → PimProduct:
return PimProduct(
sku=raw["article"],
name=raw["name"],
cost_price=int(raw["price"] * 100), # руб → коп
)
Примеры:
| Класс | Преобразование |
|-------|---------------|
| OzonOrderAdapter | OZON Order → PimOrder |
| OneCProductAdapter | 1С Номенклатура → PimProduct |
| MaxsemPriceAdapter | Maxsem Price → PimProduct |
library/ projects/pirotehnika/app/pim/
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CONNECTORS
├── BaseConnector (ABC)
│ ├── BaseApiConnector
│ │ ├── OzonConnector
│ │ ├── OneC_Connector → OneC_PimConnector (обёртка)
│ │ ├── TelegramConnector
│ │ └── PochtaConnector
│ ├── BaseDataConnector
│ │ ├── PostgresConnector
│ │ └── NocoDBConnector → PimNocoDBClient (обёртка)
│ └── BaseFileConnector
│ └── S3Connector
PARSERS
├── BaseParser (ABC)
│ ├── BaseTabularParser
│ │ ├── XlsxParser → (наследуют PriceAdapters)
│ │ └── CsvParser
│ └── JsonParser
SCRAPERS
├── BaseScraper (ABC)
│ └── BaseShopScraper
│ ├── (пусто) → JFPyroScraper
│ └── (пусто) → MaxsemScraper
ADAPTERS
├── BaseAdapter (ABC)
│ ├── ConfigurableAdapter
│ └── (пусто) → OneCProductAdapter
│ → OzonOrderAdapter
│ → MaxsemPriceAdapter
SERVICES (L5)
├── BaseService → PimProductService
│ └── BaseSyncService → PimSyncService
INTEGRATIONS (L6)
├── BaseIntegration
│ └── BaseSyncIntegration → OneC_PimIntegration
┌────────────────────────────────────────────────────────────────────┐
│ │
│ LIBRARY (платформа) PROJECT (проект) │
│ ═══════════════════ ═══════════════ │
│ │
│ Универсальный код Специфичный код │
│ Не зависит от проекта Зависит от данных проекта │
│ │
│ BaseConnector JFPyroScraper │
│ OzonConnector MaxsemPriceAdapter │
│ XlsxParser PimProductService │
│ │
└────────────────────────────────────────────────────────────────────┘
$WORKSPACE/
├── library/ ← L4: ПЛАТФОРМА (переиспользуемое)
│ ├── primitives/ ← L2: Типы
│ │ ├── types.py
│ │ ├── enums.py
│ │ └── exceptions.py
│ │
│ ├── functions/ ← L3: Чистые функции
│ │ ├── format/
│ │ ├── validate/
│ │ └── normalize/
│ │
│ ├── connectors/ ← L4: Коннекторы
│ │ ├── base.py ← BaseConnector, BaseApiConnector
│ │ ├── api/
│ │ │ ├── ozon/client.py ← OzonConnector
│ │ │ ├── 1c/client.py ← OneC_Connector
│ │ │ ├── telegram/client.py ← TelegramConnector
│ │ │ └── pochta/client.py ← PochtaConnector
│ │ └── data/
│ │ ├── postgres/client.py ← PostgresConnector
│ │ └── nocodb/client.py ← NocoDBConnector
│ │
│ ├── parsers/ ← L4: Парсеры форматов
│ │ ├── base.py ← BaseParser, BaseTabularParser
│ │ ├── xlsx/parser.py ← XlsxParser
│ │ ├── csv/parser.py ← CsvParser
│ │ └── json/parser.py ← JsonParser
│ │
│ ├── scrapers/ ← L4: Скраперы (NEW!)
│ │ ├── base.py ← BaseScraper, BaseShopScraper
│ │ └── (проектные в projects/)
│ │
│ ├── adapters/ ← L4: Адаптеры
│ │ ├── base.py ← BaseAdapter, ConfigurableAdapter
│ │ └── (проектные в projects/)
│ │
│ ├── storages/ ← L4: Хранилища
│ │ └── base.py ← BaseRepository
│ │
│ ├── services/ ← L5: Сервисы
│ │ └── base.py ← BaseService, BaseSyncService
│ │
│ └── integrations/ ← L6: Интеграции
│ └── base.py ← BaseIntegration
│
└── projects/org/pirotehnika/
└── app/pim/ ← ПРОЕКТ: PIM Пиротехника
├── connectors/ ← Обёртки над library
│ ├── onec.py ← OneC_PimConnector(OneC_Connector)
│ └── nocodb.py ← PimNocoDBClient(NocoDBConnector)
│
├── scrapers/ ← Скраперы производителей
│ ├── base.py ← BasePyroScraper(BaseScraper)
│ ├── jfpyro.py ← JFPyroScraper
│ └── maxsem.py ← MaxsemScraper
│
├── adapters/ ← Адаптеры данных
│ ├── price_adapters/ ← Прайсы → PimProduct
│ │ ├── base.py ← BasePriceAdapter
│ │ ├── maxsem.py ← MaxsemPriceAdapter
│ │ └── jf.py ← JFPriceAdapter
│ └── onec_adapter.py ← 1С → PimProduct
│
├── core/ ← Бизнес-логика
│ ├── products.py ← PimProductService
│ ├── pricing.py ← PimPricingService
│ └── sync.py ← PimSyncService
│
└── models/ ← Модели данных
└── product.py ← PimProduct, PimProductImage
| Компонент | Путь | Назначение |
|---|---|---|
| Connectors | ||
BaseConnector |
library/connectors/base.py |
Абстрактный базовый |
BaseApiConnector |
library/connectors/base.py |
HTTP API |
BaseDataConnector |
library/connectors/base.py |
Базы данных |
BaseFileConnector |
library/connectors/base.py |
Файловые хранилища |
OzonConnector |
library/connectors/api/ozon/client.py |
OZON API |
OneC_Connector |
library/connectors/api/1c/client.py |
1С OData |
TelegramConnector |
library/connectors/api/telegram/client.py |
Telegram Bot |
PostgresConnector |
library/connectors/data/postgres/client.py |
PostgreSQL |
NocoDBConnector |
library/connectors/data/nocodb/client.py |
NocoDB |
| Parsers | ||
BaseParser |
library/parsers/base.py |
Абстрактный базовый |
BaseTabularParser |
library/parsers/base.py |
Табличные данные |
XlsxParser |
library/parsers/xlsx/parser.py |
Excel |
CsvParser |
library/parsers/csv/parser.py |
CSV |
| Scrapers | ||
BaseScraper |
library/scrapers/base.py |
Абстрактный базовый |
BaseShopScraper |
library/scrapers/base.py |
Интернет-магазины |
| Adapters | ||
BaseAdapter |
library/adapters/base.py |
Абстрактный базовый |
ConfigurableAdapter |
library/adapters/base.py |
С конфигом маппинга |
| Компонент | Путь | Наследует от |
|---|---|---|
| Connectors (обёртки) | ||
OneC_PimConnector |
app/pim/connectors/onec.py |
OneC_Connector |
PimNocoDBClient |
app/pim/connectors/nocodb.py |
NocoDBConnector |
| Scrapers | ||
BasePyroScraper |
app/pim/scrapers/base.py |
BaseScraper |
JFPyroScraper |
app/pim/scrapers/jfpyro.py |
BasePyroScraper |
MaxsemScraper |
app/pim/scrapers/maxsem.py |
BasePyroScraper |
| Adapters | ||
BasePriceAdapter |
app/pim/adapters/price_adapters/base.py |
BaseAdapter |
MaxsemPriceAdapter |
app/pim/adapters/price_adapters/maxsem.py |
BasePriceAdapter |
JFPriceAdapter |
app/pim/adapters/price_adapters/jf.py |
BasePriceAdapter |
OneCProductAdapter |
app/pim/adapters/onec_adapter.py |
BaseAdapter |
| Services | ||
PimProductService |
app/pim/core/products.py |
BaseService |
PimSyncService |
app/pim/core/sync.py |
BaseSyncService |
# library/connectors/api/{name}/client.py
"""
{Name} Connector — подключение к {API_NAME}.
"""
from library.connectors.base import BaseApiConnector
class {Name}Connector(BaseApiConnector):
"""
Коннектор к {API_NAME}.
Usage:
async with {Name}Connector(credentials) as conn:
data = await conn.get_something()
"""
BASE_URL = "https://api.example.com"
def _get_auth_headers(self) -> Dict[str, str]:
return {"Authorization": f"Bearer {self.credentials['api_key']}"}
async def get_something(self) -> List[Dict]:
return await self.get("/v1/something")
# library/parsers/{format}/parser.py
"""
{Format} Parser — парсинг {FORMAT} файлов.
"""
from library.parsers.base import BaseParser
class {Format}Parser(BaseParser):
"""
Парсер {FORMAT} файлов.
Usage:
parser = {Format}Parser()
data = parser.parse("/path/to/file.{ext}")
"""
SUPPORTED_EXTENSIONS = [".{ext}"]
def parse(self, source, **options) -> List[Dict]:
content = self._read_source(source)
# ... парсинг
return records
# projects/{project}/app/pim/scrapers/{site}.py
"""
{Site} Scraper — парсинг сайта {domain}.
"""
from library.scrapers.base import BaseScraper
class {Site}Scraper(BaseScraper):
"""
Скрапер сайта {domain}.
Usage:
scraper = {Site}Scraper()
products = await scraper.scrape_catalog()
"""
BASE_URL = "https://{domain}"
DELAY = 2.0 # Секунды между запросами
async def scrape_product(self, url: str) -> Dict:
html = await self.fetch_page(url)
# ... извлечение данных
return product
# projects/{project}/app/pim/adapters/{source}_adapter.py
"""
{Source} → PIM Adapter
"""
from library.adapters.base import BaseAdapter
from app.pim.models import PimProduct
class {Source}Adapter(BaseAdapter[Dict, PimProduct]):
"""
Преобразование данных {source} в PimProduct.
Usage:
adapter = {Source}Adapter()
product = adapter.adapt(raw_data)
"""
def adapt(self, data: Dict) -> PimProduct:
return PimProduct(
sku=data.get("article", ""),
name=data.get("name", ""),
cost_price=self._safe_int(data.get("price", 0) * 100),
)
| Откуда | Куда | Статус |
|---|---|---|
data/adapters/price_adapters.py |
app/pim/adapters/price_adapters/ |
⏳ |
mp1/solution/lib/scraper_jfpyro.py |
app/pim/scrapers/jfpyro.py |
⏳ |
mp1/solution/lib/scraper_maxsem.py |
app/pim/scrapers/maxsem.py |
⏳ |
mp1/solution/lib/connector_1c.py |
library/connectors/api/1c/ |
✅ |
data/connectors/pochta/ |
library/connectors/api/pochta/ |
✅ |
| Компонент | Путь |
|---|---|
BaseScraper |
library/scrapers/base.py |
BaseShopScraper |
library/scrapers/base.py |
BasePyroScraper |
app/pim/scrapers/base.py |
xlsx файл
│
▼
XlsxParser.parse() ← library/parsers/xlsx/
│
▼
List[Dict] (сырые строки)
│
▼
MaxsemPriceAdapter.adapt_many() ← app/pim/adapters/
│
▼
List[PimProduct]
│
▼
PimProductService.save() ← app/pim/core/
│
▼
NocoDB (pim_staging_products)
URL сайта
│
▼
JFPyroScraper.scrape_catalog() ← app/pim/scrapers/
│
▼
List[Dict] (спарсенные товары)
│
▼
JFPyroAdapter.adapt_many() ← app/pim/adapters/
│
▼
List[PimProduct]
│
▼
PimProductService.enrich() ← Обогащение существующих
│
▼
NocoDB (pim_products)
1С OData API
│
▼
OneC_PimConnector.get_products() ← app/pim/connectors/
│
▼
List[Dict] (данные 1С)
│
▼
OneCProductAdapter.adapt_many() ← app/pim/adapters/
│
▼
List[PimProduct]
│
▼
PimSyncService.sync() ← app/pim/core/
│
▼
NocoDB (1c_products → pim_products)
Версия: 1.0.0