diff --git a/README.md b/README.md index ea18f0b..db34cd1 100644 --- a/README.md +++ b/README.md @@ -1,349 +1,277 @@ -# Django ETL Boilerplate +# Mostovik Backend -Шаблон Django приложения для ETL (Extract, Transform, Load) операций с функциями веб-скрапинга. +Backend-сервис на Django/DRF для сбора, хранения и выдачи данных из государственных источников (Минпромторг, ЕРП, ЕИС закупок, ФНС), с асинхронной обработкой через Celery. -## Технологический стек +## Что умеет проект -- **Python**: 3.11.2 -- **Django**: 3.2.25 -- **Django REST Framework**: 3.14.0 -- **PostgreSQL**: 15.10 -- **Redis**: 7.x -- **Celery**: 5.3.6 -- **Playwright**: 1.52+ (browser automation) -- **Gunicorn**: 21.2.0 -- **Apache**: 2.4.57 +- JWT-аутентификация и профиль пользователя. +- Swagger UI документация API. +- Health/Liveness/Readiness эндпоинты. +- Фоновые задачи с трекингом статуса (`BackgroundJob`). +- Парсеры источников: +- Минпромторг: сертификаты и реестр производителей. +- Единый реестр проверок (`proverki.gov.ru`, включая 248-ФЗ/294-ФЗ). +- Закупки (`zakupki.gov.ru`, SOAP API с токеном). +- ФНС: загрузка и обработка Excel-отчётности (`fin_{id}_{ogrn}.xlsx`). +- Админ-панель на Jazzmin для мониторинга данных и логов загрузок. -## Парсеры данных +## Технологии -Проект включает парсеры для загрузки данных из государственных источников: - -### Минпромторг (minpromtorg.gov.ru) -- **Сертификаты промышленного производства** - `parse_industrial_production` -- **Реестр производителей** - `parse_manufactures` - -### Единый реестр проверок (proverki.gov.ru) -- **Проверки по ФЗ-294** - традиционные проверки -- **Проверки по ФЗ-248** - новые проверки с 2021 года -- **Автоматическая синхронизация** - `sync_inspections` - -### Запуск парсеров - -```python -# Через Celery -from apps.parsers.tasks import ( - parse_industrial_production, - parse_manufactures, - parse_inspections, - sync_inspections, -) - -# Парсинг сертификатов -parse_industrial_production.delay() - -# Парсинг производителей -parse_manufactures.delay() - -# Парсинг проверок за конкретный месяц -parse_inspections.delay(year=2025, month=10, is_federal_law_248=False) - -# Автоматическая синхронизация (с 01.01.2025 до текущего месяца) -sync_inspections.delay() -``` - -### Особенности парсера proverki.gov.ru -- Использует **Playwright** для JS-рендеринга -- Поддержка **потокового парсинга** для больших файлов (>50 МБ) -- Автоматическое определение последнего загруженного периода -- Раздельная загрузка ФЗ-294 и ФЗ-248 +- Python 3.11 +- Django 3.2 +- Django REST Framework +- PostgreSQL 15 +- Redis 7 +- Celery + django-celery-beat + django-celery-results +- drf-yasg (Swagger/OpenAPI) +- uv (управление зависимостями) +- Docker / Docker Compose ## Структура проекта -``` -mostovik-backend/ -├── src/ # Исходный код Django -│ ├── apps/ # Django приложения -│ │ └── user/ # Приложение пользователей -│ ├── core/ # Runtime-конфигурация проекта (urls/asgi/wsgi/celery) -│ ├── settings/ # Django settings (base, dev, production, test) -│ └── manage.py # Управление Django -├── tests/ # Тесты (в корне проекта) -│ ├── apps/user/ # Тесты для user app -│ ├── conftest.py # Конфигурация pytest -│ └── README.md # Документация по тестам -├── docker/ # Docker конфигурации -├── pyproject.toml # Конфигурация проекта и инструментов -├── Makefile # Команды для разработки -├── docker-compose.dev.yml # Docker Compose для разработки -└── docker-compose.prod.yml # Docker Compose для production +```text +. +├── src/ +│ ├── apps/ +│ │ ├── core/ # health, background jobs, openapi, startup checks +│ │ ├── user/ # auth, профиль пользователя +│ │ └── parsers/ # клиенты/сервисы/задачи парсеров +│ ├── core/ # urls/asgi/wsgi/celery +│ ├── settings/ # base/dev/production/test +│ └── manage.py +├── tests/ +├── docker/ +│ ├── Dockerfile +│ └── scripts/ +├── input/ # входящие/обработанные/ошибочные файлы ФНС +├── docker-compose.dev.yml +├── docker-compose.prod.yml +└── Makefile ``` -## Быстрый старт (локальная разработка) +## Переменные окружения -### 1. Установка зависимостей +Ключевые переменные: + +- `DJANGO_SETTINGS_MODULE`: `settings.dev` (локально/dev) или `settings.production` (prod). +- `POSTGRES_*`: доступ к PostgreSQL. +- `REDIS_CACHE_URL`, `CELERY_BROKER_URL`, `CELERY_RESULT_BACKEND`: Redis/Celery. +- `CHECKO_API_KEY`: ключ API Checko. +- `ZAKUPKI_TOKEN`: токен SOAP API ЕИС закупок. +- `COLLECTSTATIC_ON_MIGRATE`: `1`/`0`. +- `STARTUP_CHECKS_ENABLED`: fail-fast проверки DB/Redis перед стартом runtime-процессов. + +Важно: если используете `.env.dev`/`.env.prod`, проверьте, что `DJANGO_SETTINGS_MODULE` указывает на `settings.*`, а не на устаревший `config.settings.*`. + +## Локальный запуск в Docker (рекомендуется) + +### 1. Подготовка ```bash -# Установка uv (если не установлен) -curl -LsSf https://astral.sh/uv/install.sh | sh -source $HOME/.cargo/env - -# Создание виртуального окружения с uv -uv venv .venv -source .venv/bin/activate - -# Установка зависимостей через uv -uv pip install -e ".[dev]" - -# Или через Makefile -make install - -# Настройка окружения разработки (pre-commit hooks) -make setup-dev +# из корня проекта +cp .env.prod.example .env.prod # только если нужен prod compose ``` -### 2. Настройка окружения +Для dev используется `.env.dev`. При необходимости поправьте: -```bash -# Для dev compose уже готов файл .env.dev (можно использовать как есть). -# Для prod compose заполните .env.prod на основе .env.prod.example. +```env +DJANGO_SETTINGS_MODULE=settings.dev ``` -### 3. Запуск с Docker Compose (рекомендуется) +### 2. Старт dev-стека ```bash -# Запуск всех dev сервисов (db, redis, migrate, web, celery) +make dev-up +# или: docker compose -f docker-compose.dev.yml up -d +``` -# Проверка состояния контейнеров +Сервисы: + +- `db` (PostgreSQL) +- `redis` +- `migrate` (однократный запуск миграций) +- `web` (Django runserver) +- `celery_worker` +- `celery_beat` + +### 3. Проверка + +```bash docker compose -f docker-compose.dev.yml ps - -# Просмотр логов docker compose -f docker-compose.dev.yml logs -f web ``` -### 4. Ручная настройка (без Docker) +### 4. Остановка -#### Запуск баз данных: ```bash -# PostgreSQL -sudo systemctl start postgresql - -# Redis -sudo systemctl start redis +make dev-down ``` -#### Миграции и запуск: +## Локальный запуск без Docker + +Вариант для разработки на хосте. + +### 1. Зависимости + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +source "$HOME/.cargo/env" +uv sync --dev +``` + +### 2. Поднять PostgreSQL и Redis + +Любым удобным способом (локально через сервисы или контейнеры). Пример с Docker: + +```bash +docker run -d --name mostovik_pg \ + -e POSTGRES_DB=mostovik \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ + -p 5432:5432 postgres:15.10 + +docker run -d --name mostovik_redis -p 6379:6379 redis:7-alpine +``` + +### 3. Экспорт переменных + +```bash +export DJANGO_SETTINGS_MODULE=settings.dev +export POSTGRES_HOST=127.0.0.1 +export POSTGRES_PORT=5432 +export POSTGRES_DB=mostovik +export POSTGRES_USER=postgres +export POSTGRES_PASSWORD=postgres +export REDIS_HOST=127.0.0.1 +export REDIS_CACHE_URL=redis://127.0.0.1:6379/1 +export CELERY_BROKER_URL=redis://127.0.0.1:6379/0 +export CELERY_RESULT_BACKEND=redis://127.0.0.1:6379/0 +``` + +### 4. Миграции и запуск + ```bash cd src - -# Миграции -python manage.py makemigrations -python manage.py migrate - -# Создание суперпользователя -python manage.py createsuperuser - -# Запуск разработческого сервера -python manage.py runserver - -# Запуск Celery worker (в отдельном терминале) -celery -A core worker --loglevel=info - -# Запуск Celery beat (в отдельном терминале) -celery -A core beat --loglevel=info +uv run python manage.py migrate +uv run python manage.py createsuperuser +uv run python manage.py runserver ``` -## API Endpoints - -Основной префикс: `/api/` - -### Data Processor -- `GET/POST /api/data-sources/` - Источники данных -- `GET/POST /api/data-pipelines/` - ETL пайплайны -- `GET /api/extracted-data/` - Извлеченные данные -- `GET /api/processing-logs/` - Логи обработки - -### Web Scraping -- `GET/POST /api/scraping-jobs/` - Задачи скрапинга -- `GET /api/scraped-items/` - Скрапленные данные -- `GET/POST /api/spider-configurations/` - Конфигурации пауков -- `GET/POST /api/proxy-servers/` - Прокси сервера - -### Аутентификация -- `POST /api/api-token-auth/` - Получение API токена - -## Развертывание - -Используется `docker-compose.prod.yml` и файл окружения `.env.prod`. +В отдельных терминалах: ```bash -# 1) Заполнить .env.prod (можно взять шаблон .env.prod.example) +cd src && DJANGO_SETTINGS_MODULE=settings.dev uv run celery -A core worker --loglevel=INFO +cd src && DJANGO_SETTINGS_MODULE=settings.dev uv run celery -A core beat --loglevel=INFO +``` + +Опционально для `proverki.gov.ru` fallback-режима: + +```bash +uv run playwright install chromium +``` + +## API и доступные эндпоинты + +Базовый URL в dev: `http://localhost:8000` + +- Swagger UI: `GET /` +- Admin: `GET /admin/` +- Health: `GET /health/`, `GET /health/live/`, `GET /health/ready/` + +Версионированный API: `/api/v1/` + +- Пользователи и auth: `/api/v1/users/` +- Фоновые задачи: `/api/v1/jobs/` +- Минпромторг: `/api/v1/minpromtorg/` +- Проверки: `/api/v1/proverki/` +- Закупки: `/api/v1/zakupki/` +- ФНС: `/api/v1/fns/` +- Системные (admin): `/api/v1/system/` + +Основные auth-эндпоинты: + +- `POST /api/v1/users/register/` +- `POST /api/v1/users/login/` +- `POST /api/v1/users/token/refresh/` +- `POST /api/v1/users/token/verify/` +- `GET /api/v1/users/me/` + +ФНС загрузка: + +- `POST /api/v1/fns/upload/` (`multipart/form-data`, поле `files`) +- `GET /api/v1/fns/reports/` +- `GET /api/v1/fns/reports/{id}/` + +## Парсеры и фоновые задачи + +Celery-задачи (основные): + +- `apps.parsers.tasks.parse_industrial_production` +- `apps.parsers.tasks.parse_manufactures` +- `apps.parsers.tasks.parse_inspections` +- `apps.parsers.tasks.sync_inspections` +- `apps.parsers.tasks.parse_procurements` +- `apps.parsers.tasks.sync_procurements` +- `apps.parsers.tasks.process_fns_file` +- `apps.parsers.tasks.scan_fns_directory` + +Планировщик (`celery beat`) по умолчанию: + +- ежедневный парсинг Минпромторга (сертификаты/производители) +- сканирование директории ФНС каждые 5 минут + +Директории ФНС: + +- входящие: `input/fns/` +- успешные: `input/fns/processed/` +- ошибки: `input/fns/failed/` + +## Команды разработки + +```bash +make install +make setup-dev +make test +make test-cov +make test-fast +make lint +make pre-commit +make pre-push +make migrate +make createsuperuser +make shell +``` + +## Production (docker-compose.prod.yml) + +```bash +cp .env.prod.example .env.prod +# обязательно проверить: +# DJANGO_SETTINGS_MODULE=settings.production -# 2) Собрать и запустить сервисы docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build - -# 3) Проверить состояние docker compose -f docker-compose.prod.yml --env-file .env.prod ps ``` -## Мониторинг и логирование +В prod compose поднимаются: -### Логи приложения -```bash -# Логи Django -tail -f logs/django.log +- `migrate` +- `web` (gunicorn) +- `celery_worker` +- `celery_beat` -# Логи Celery -tail -f logs/celery.log +БД/Redis в prod compose не создаются, предполагаются внешние сервисы. -# Системные логи -journalctl -u gunicorn -f -journalctl -u celery-worker -f -``` +## Наблюдаемость и диагностика -### Мониторинг Celery -```bash -# Запуск Flower (в отдельном терминале) -celery -A core flower - -# Доступ через браузер: http://localhost:5555 -``` - -## Разработка - -### Запуск тестов -```bash -# Запуск всех тестов -make test - -# Запуск с покрытием -make test-cov - -# Запуск только быстрых тестов -make test-fast - -# Запуск тестов конкретного модуля -make test TARGET=user - -# Линтинг и форматирование -make lint -make format - -# Проверка типов -make type-check - -# Проверка безопасности -make security-check -``` - -### Создание миграций -```bash -# Через Makefile -make migrate - -# Или напрямую -cd src -python manage.py makemigrations -python manage.py migrate - -# Создание суперпользователя -make createsuperuser -``` - -### Работа с задачами Celery -```python -# В коде Python -from apps.data_processor.tasks import process_extracted_data -from apps.scraping.tasks import run_scraping_job - -# Запуск асинхронно -result = process_extracted_data.delay() -print(result.id) # ID задачи -``` - -## Конфигурация инструментов - -Все конфигурации инструментов разработки централизованы в файле `pyproject.toml`: - -- **pytest**: настройки тестирования -- **coverage**: отчеты о покрытии кода -- **ruff**: линтинг и форматирование -- **black**: форматирование кода -- **isort**: сортировка импортов -- **mypy**: проверка типов -- **bandit**: проверка безопасности - -### Полезные команды Make +- Health-checkи для оркестрации: `/health/live/`, `/health/ready/`, `/health/`. +- Расширенный health: `GET /health/?include_celery=true`. +- Логи контейнеров: ```bash -# Качество кода -make lint # Проверка линтерами -make format # Форматирование кода -make type-check # Проверка типов -make security-check # Проверка безопасности -make pre-commit # Запуск всех pre-commit hooks - -# Тестирование -make test # Все тесты -make test-cov # Тесты с покрытием -make test-fast # Только быстрые тесты - -# Разработка -make shell # Django shell -make migrate # Миграции -make clean # Очистка временных файлов +docker compose -f docker-compose.dev.yml logs -f web +docker compose -f docker-compose.dev.yml logs -f celery_worker +docker compose -f docker-compose.dev.yml logs -f celery_beat ``` -## Безопасность - -- Все секретные ключи хранятся в переменных окружения -- Используется HTTPS в продакшене -- Настроены заголовки безопасности в Apache -- Регулярное обновление зависимостей - -## Поддержка - -Для вопросов и поддержки обращайтесь к документации Django и используемым библиотекам: - -- [Django Documentation](https://docs.djangoproject.com/) -- [Celery Documentation](https://docs.celeryproject.org/) -- [Scrapy Documentation](https://docs.scrapy.org/) -- [Django REST Framework](https://www.django-rest-framework.org/) - -## Лицензия - -MIT License - ---- - -## Changelog - -### 2026-01-21 -#### Добавлено -- **Задача `sync_inspections`** - автоматическая синхронизация проверок с proverki.gov.ru - - Инкрементальная загрузка с последнего сохранённого периода - - Начало с 01.01.2025 если БД пуста - - Раздельная загрузка ФЗ-294 и ФЗ-248 - - Автоматическая остановка при отсутствии данных (2 пустых месяца) -- **Поля в модели InspectionRecord**: - - `is_federal_law_248` - признак проверки по ФЗ-248 - - `data_year` - год загруженных данных - - `data_month` - месяц загруженных данных -- **Потоковый парсинг XML** для файлов >50 МБ (iterparse) -- **Методы в InspectionService**: - - `get_last_loaded_period()` - получение последнего загруженного периода - - `has_data_for_period()` - проверка наличия данных за период - -### 2026-01-20 -#### Добавлено -- **Парсер proverki.gov.ru** с поддержкой Playwright -- Навигация по порталу (клик на вкладку "Скачать") -- Парсинг XML с namespaces -- Извлечение данных из атрибутов и вложенных элементов - -### 2026-01-19 -#### Добавлено -- **Парсеры Минпромторга** (сертификаты, производители) -- **Модуль apps.parsers** с клиентами, сервисами и задачами Celery -- **Django Admin** для управления записями парсеров -- Дедупликация по unique constraints diff --git a/concatenate_files.py b/concatenate_files.py deleted file mode 100644 index 3f245a6..0000000 --- a/concatenate_files.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Скрипт для объединения всех файлов в дереве каталогов в один .docx файл. -Выходной файл будет содержать пути к файлам, за которыми следует их содержимое. -""" - -import os -from pathlib import Path -from docx import Document -from docx.shared import Inches -import mimetypes - - -def is_binary_file(file_path): - """ - Проверяет, является ли файл двоичным, пытаясь прочитать его как текст. - """ - try: - with open(file_path, 'tr', encoding='utf-8') as check_file: - check_file.read(1024) # Читаем первые 1KB для проверки - return False - except UnicodeDecodeError: - return True - - -def should_skip_directory(directory): - """ - Определяет, нужно ли пропустить каталог на основе общих шаблонов игнорирования. - """ - skip_dirs = {'.git', '.venv', '__pycache__', 'node_modules', '.pytest_cache', - 'data', '.idea', '.vscode', 'logs', 'media', 'staticfiles'} - return any(skip_dir in directory.parts for skip_dir in skip_dirs) - - -def should_skip_file(file_path): - """ - Определяет, нужно ли пропустить файл на основе расширений или шаблонов. - """ - skip_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.svg', - '.mp3', '.mp4', '.avi', '.mov', '.pdf', '.zip', '.tar', - '.gz', '.exe', '.so', '.dll', '.doc', '.docx', '.xls', - '.xlsx', '.ppt', '.pptx', '.db', '.sqlite', '.log'} - - file_ext = Path(file_path).suffix.lower() - return file_ext in skip_extensions - - -def concatenate_files_to_docx(): - """ - Основная функция для объединения всех текстовых файлов в один документ .docx. - Обрабатываем только файлы из директорий src/apps и src/config. - """ - print("Начинаем объединение файлов...") - - # Создаем новый документ Word - doc = Document() - - # Добавляем заголовок - doc.add_heading('Объединенные файлы', 0) - - # Определяем целевые директории - target_dirs = [ - Path('./src/apps'), - Path('./src/config') - ] - - # Проходим по файлам в целевых директориях - for target_dir in target_dirs: - if target_dir.exists(): - for file_path in target_dir.rglob('*'): - if file_path.is_file(): - # Пропускаем определенные типы файлов - if should_skip_file(file_path): - continue - - # Пропускаем, если это двоичный файл - if is_binary_file(str(file_path)): - print(f"Пропускаем двоичный файл: {file_path}") - continue - - try: - # Добавляем путь к файлу как заголовок - doc.add_heading(str(file_path), level=1) - - # Читаем содержимое файла - with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: - content = f.read() - - # Удаляем пустые строки из содержимого - lines = [line for line in content.splitlines() if line.strip()] - clean_content = '\n'.join(lines) - - # Добавляем содержимое в документ - if clean_content.strip(): # Добавляем только если есть содержимое - doc.add_paragraph(clean_content) - else: - doc.add_paragraph("[Пустой файл]") - - print(f"Добавлен файл: {file_path}") - - except Exception as e: - error_msg = f"Ошибка чтения файла {file_path}: {str(e)}" - print(error_msg) - doc.add_heading(f"ОШИБКА: {file_path}", level=1) - doc.add_paragraph(error_msg) - else: - print(f"Целевая директория не найдена: {target_dir}") - - # Сохраняем документ - output_filename = "один_файл.docx" - doc.save(output_filename) - print(f"\nУспешно создан {output_filename} с объединенным содержимым!") - - -if __name__ == "__main__": - concatenate_files_to_docx() \ No newline at end of file diff --git a/один_файл.docx b/один_файл.docx deleted file mode 100644 index bf9555b..0000000 Binary files a/один_файл.docx and /dev/null differ