|
| 1 | +# 🏗 Архитектура и схема работы |
| 2 | + |
| 3 | +## Общая архитектура |
| 4 | + |
| 5 | +Проект построен на **ETL-архитектуре** (Extract → Transform → Load → Analyze): |
| 6 | + |
| 7 | +``` |
| 8 | +[HH.ru API] → Extract → [Raw JSON] → Transform → [Processed DataFrame] |
| 9 | + ↓ |
| 10 | + Load → [SQLite/PostgreSQL] → Analyze → [Dashboard/Reports] |
| 11 | +``` |
| 12 | + |
| 13 | +## Компоненты системы |
| 14 | + |
| 15 | +### 1. Парсер (Extract) |
| 16 | + |
| 17 | +**Файлы:** |
| 18 | +- `optimized_parser.py` — основной парсер |
| 19 | +- `src/api_client.py` — клиент HH API |
| 20 | +- `src/collector.py` — сборщик вакансий |
| 21 | + |
| 22 | +**Поток данных:** |
| 23 | +``` |
| 24 | +User запускает парсер |
| 25 | + ↓ |
| 26 | +Проверка email (403 если не настроен) |
| 27 | + ↓ |
| 28 | +Для каждого запроса: |
| 29 | + 1. Проверка кэша (api_cache.db) |
| 30 | + 2. Если нет в кэше → запрос к HH API |
| 31 | + 3. Сохранение в кэш (24 часа TTL) |
| 32 | + 4. Сохранение в raw JSON |
| 33 | + ↓ |
| 34 | +Векторизованная обработка навыков |
| 35 | + ↓ |
| 36 | +Загрузка в БД (upsert) |
| 37 | +``` |
| 38 | + |
| 39 | +**Кэширование:** |
| 40 | +```python |
| 41 | +# APICache в optimized_parser.py |
| 42 | +class APICache: |
| 43 | + # Таблица: api_cache |
| 44 | + # Поля: url, response_json, timestamp |
| 45 | + # Автоочистка: >7 дней |
| 46 | +``` |
| 47 | + |
| 48 | +**Rate Limiting:** |
| 49 | +- Задержка между запросами: `API_REQUEST_DELAY=1.0s` |
| 50 | +- Retry logic: tenacity (экспоненциальный backoff) |
| 51 | +- Обработка 429 ошибок |
| 52 | + |
| 53 | +### 2. Процессор (Transform) |
| 54 | + |
| 55 | +**Файлы:** |
| 56 | +- `src/processor.py` — VacancyProcessor |
| 57 | +- `config.yaml` — словари навыков |
| 58 | + |
| 59 | +**Что делает:** |
| 60 | +```python |
| 61 | +class VacancyProcessor: |
| 62 | + def process_vacancies(df): |
| 63 | + # 1. Извлечение hard_skills из description |
| 64 | + # 2. Извлечение soft_skills |
| 65 | + # 3. Извлечение tools/технологий |
| 66 | + # 4. Подсчёт skill_count |
| 67 | + # 5. Нормализация зарплат |
| 68 | + return processed_df |
| 69 | +``` |
| 70 | + |
| 71 | +### 3. Хранилище (Load) |
| 72 | + |
| 73 | +**Файлы:** |
| 74 | +- `src/storage.py` — VacancyStorage |
| 75 | + |
| 76 | +**Таблицы:** |
| 77 | +```sql |
| 78 | +-- vacancies (основная) |
| 79 | +id, vacancy_id (UNIQUE), vacancy_name, published_at, |
| 80 | +all_skills, hard_skills, soft_skills, tools, |
| 81 | +skill_count, hard_skill_count, soft_skill_count, tools_count, |
| 82 | +salary_from, salary_to, salary_currency, salary_gross, |
| 83 | +employer_name, employer_id, employer_url, vacancy_url, |
| 84 | +experience, employment, schedule, area, |
| 85 | +created_at, updated_at |
| 86 | + |
| 87 | +-- parser_runs (журнал парсингов) |
| 88 | +id, started_at, completed_at, status, |
| 89 | +keywords (JSON), max_pages, days_back, |
| 90 | +is_incremental, use_cache, |
| 91 | +vacancies_collected, vacancies_new, vacancies_updated, |
| 92 | +errors_count, error_message, created_at |
| 93 | + |
| 94 | +-- app_settings (синглтон, id=1) |
| 95 | +id=1, last_parse_at, last_successful_parse_at, |
| 96 | +total_parses, total_vacancies_collected, |
| 97 | +app_version, config_version, updated_at |
| 98 | +``` |
| 99 | + |
| 100 | +**Индексы:** |
| 101 | +```sql |
| 102 | +idx_vacancy_id, idx_employer, idx_area, |
| 103 | +idx_published_at, idx_experience, |
| 104 | +idx_salary_from, idx_created_at |
| 105 | +``` |
| 106 | + |
| 107 | +**Режимы сохранения:** |
| 108 | +- `save_dataframe()` — полная замена (if_exists='replace') |
| 109 | +- `save_vacancies_incremental()` — upsert логика (INSERT OR UPDATE) |
| 110 | + |
| 111 | +### 4. Аналитика (Analyze) |
| 112 | + |
| 113 | +**Файлы:** |
| 114 | +- `src/analyzer.py` — базовая аналитика |
| 115 | +- `src/advanced_analyzer.py` — расширенная аналитика |
| 116 | + |
| 117 | +**Что считает:** |
| 118 | +```python |
| 119 | +class VacancyAnalyzer: |
| 120 | + # Частота навыков (hard/soft/tools) |
| 121 | + # Статистика зарплат (по опыту, регионам, занятости) |
| 122 | + # Excel-отчёты с графиками (openpyxl) |
| 123 | + |
| 124 | +class AdvancedAnalytics: |
| 125 | + # Группировки: LLM, Vector DB, RAG, ML libs, Cloud, Docker, CI/CD |
| 126 | + # Карта связей "вакансия-навык" |
| 127 | + # Детальный Excel-отчёт (6 листов) |
| 128 | +``` |
| 129 | + |
| 130 | +### 5. Веб-приложение (Serve) |
| 131 | + |
| 132 | +**Файлы:** |
| 133 | +- `web/app/main.py` — FastAPI сервер |
| 134 | +- `web/static/index.html` — Vue.js 3 SPA |
| 135 | +- `web/run.py` — точка входа (uvicorn) |
| 136 | + |
| 137 | +**Endpoints:** |
| 138 | +``` |
| 139 | +GET / → index.html |
| 140 | +GET /analytics → analytics.html |
| 141 | +GET /api/health → {"status": "ok"} |
| 142 | +GET /api/dashboard → KPI, навыки, вакансии, sparklines |
| 143 | +GET /api/vacancies → список с фильтрами/сортировкой |
| 144 | +GET /api/analytics/kpi → 8 KPI метрик с трендами |
| 145 | +GET /api/analytics/top-skills → топ навыков (hard/soft/tools) |
| 146 | +GET /api/analytics/distribution → опыт, занятость, зарплаты, регионы |
| 147 | +GET /api/analytics/advanced → технологии, hard/soft skills |
| 148 | +GET /api/parser/status → статус парсера |
| 149 | +POST /api/parser/start → запуск парсера (требует email!) |
| 150 | +GET /api/parser/stop → остановка |
| 151 | +GET /api/parser/last-run → последний запуск |
| 152 | +GET /api/parser/history → история запусков |
| 153 | +GET /api/user/email → статус email |
| 154 | +POST /api/user/email → сохранить email |
| 155 | +GET /api/app/settings → настройки приложения |
| 156 | +GET /api/reports/list → список отчётов |
| 157 | +GET /api/export/vacancies → экспорт (xlsx/csv) |
| 158 | +POST /api/export/analytics → экспорт аналитики |
| 159 | +GET /api/professions/* → каталог профессий |
| 160 | +GET /api/autocomplete/* → автодополнение |
| 161 | +``` |
| 162 | + |
| 163 | +**Фоновые задачи:** |
| 164 | +- Парсер запускается через `BackgroundTasks` FastAPI |
| 165 | +- Автоматическое обновление статуса каждые 2 сек (polling) |
| 166 | +- Генерация отчётов в фоне |
| 167 | + |
| 168 | +## Поток запроса пользователя |
| 169 | + |
| 170 | +``` |
| 171 | +1. Пользователь открывает http://localhost:8000 |
| 172 | +2. FastAPI отдаёт index.html (статический файл) |
| 173 | +3. Vue.js делает fetch('/api/dashboard') |
| 174 | +4. FastAPI читает из SQLite → возвращает JSON |
| 175 | +5. Vue.js рендерит графики Chart.js |
| 176 | +6. Парсинг: POST /api/parser/start → BackgroundTask → in-progress polling |
| 177 | +``` |
| 178 | + |
| 179 | +## Безопасность |
| 180 | + |
| 181 | +- **Email required**: 403 при попытке запустить парсер без настроенного email |
| 182 | +- **Rate limiting**: задержка 1с между запросами к HH API |
| 183 | +- **.env**: не коммитится в Git (секреты) |
| 184 | +- **CORS**: разрешены все origins (для dev, изменить для prod) |
0 commit comments