MIEM Employees Server

Сервис собирает сотрудников МИЭМ с сайта ВШЭ, хранит карточки и историю обновлений в Postgres и показывает минимальную админку.

Архитектура

  • api: FastAPI, REST API, HTML-админка и healthcheck.
  • worker: weekly scheduler, который запускает парсинг по CRAWL_CRON.
  • postgres: основная БД.

Парсер использует фиксированный источник сотрудников, по умолчанию https://miem.hse.ru/persons. Для каждой карточки сохраняются ФИО, должности, год начала работы, контакты, идентификаторы, вкладки профиля, секции, публикации, курсы, ВКР, новости, JSON-снапшот и сжатый HTML-снапшот. Детальные публикации дополнительно нормализуются в отдельную таблицу employee_publications, а новости из блока «В новостях» — в employee_news_links. Ссылки обходятся только из меню профиля самого сотрудника (person-menu), например #sci, #teaching, #main.

Переменные окружения

Скопируйте .env.example в .env и поменяйте секреты:

cp .env.example .env

Основные настройки:

  • DATABASE_URL: строка подключения SQLAlchemy.
  • SOURCE_URL: список сотрудников МИЭМ.
  • CRAWL_CRON: расписание в формате crontab, по умолчанию 0 3 * * 1.
  • CRAWL_LIMIT: опциональный лимит профилей для тестового запуска.
  • ADMIN_USERNAME, ADMIN_PASSWORD: логин и пароль админки.
  • SESSION_SECRET: секрет подписи cookie.
  • PARSER_USE_PLAYWRIGHT: включение Playwright-рендера динамических вкладок.
  • DISMISSAL_CONFIRMATION_RUNS: сколько последовательных проверок недоступности нужно для увольнения, по умолчанию 3.
  • MAX_AUTO_DISMISSALS_PER_RUN: защитный лимит массовых автоматических увольнений за один запуск, по умолчанию 25.

Локальный запуск

python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
uvicorn app.main:app --reload

Админка: http://localhost:8000/admin.

В админке доступны:

  • Dashboard: общая статистика, последний добавленный сотрудник, прогресс текущего/последнего парсинга и ручной запуск.
  • Directory: настраиваемая таблица сотрудников с фильтрами, сортировкой, пагинацией и выбором колонок.
  • Runs: история запусков, ошибки и progress bar.

Docker Compose

docker compose up -d --build --remove-orphans

По умолчанию:

  • API и админка: http://localhost:8000
  • Postgres: localhost:5432

Таблицы создаются приложением при старте. При обновлении существующей базы приложение также добавляет недостающие runtime-колонки, например crawl_runs.skipped_count. SQL-миграции для ручного применения лежат в migrations/.

Наполнение БД

Основная карточка сотрудника хранится в employees: профиль, статус, даты обнаружения/увольнения, текущий JSON current_data, checksum и версия парсера. История успешных изменений сохраняется в employee_snapshots вместе с JSON-снимком и сжатым HTML профиля.

Публикации теперь хранятся в двух видах:

  • краткий список остается внутри employees.current_data.sections[].publications для обратной совместимости;
  • детальные записи сохраняются в employee_publications и связываются с сотрудником через employee_id.

employee_publications содержит publication_id, название, год, тип публикации, язык, статус, ссылку на карточку HSE Publications, DOI, внешние/document-ссылки, citation text, аннотацию, описание, авторов, raw JSON ответа searchPubs и source_hash для безопасного повторного upsert. Уникальность поддерживается по (employee_id, publication_id) и (employee_id, source_hash), поэтому повторный crawl не должен создавать дубликаты.

list_employee_publications сначала читает employee_publications; если детальных строк еще нет, возвращает старые публикации из current_data.

Новости сотрудников также хранятся в двух видах:

  • краткий список остается внутри employees.current_data.sections[].news_links;
  • нормализованные карточки из вкладки «В новостях» сохраняются в employee_news_links.

employee_news_links содержит название новости, ссылку, краткое описание, дату публикации, год публикации, raw JSON карточки и source_hash. Уникальность поддерживается по (employee_id, url) и (employee_id, source_hash), поэтому повторный crawl не создает дубликаты.

Парсинг

Worker запускает обход по CRAWL_CRON. Ручной запуск также доступен в админке на Dashboard и странице Runs или через REST:

curl -X POST http://localhost:8000/api/crawl-runs --cookie "miem_admin_session=..."

Алгоритм обновления:

  • найденные сотрудники получают статус active и обновленный last_seen_at;
  • новые сотрудники добавляются в employees;
  • если профиль перенесен на другой URL, он сопоставляется с прежней записью по единственному точному совпадению ФИО;
  • старые URL сохраняются в истории employee_profile_urls;
  • количество новых сотрудников за запуск сохраняется в crawl_runs.new_count;
  • публикации из HSE Publications записываются в employee_publications, а краткий список остается в JSON профиля;
  • новости из блока «В новостях» записываются в employee_news_links, а краткий список остается в JSON профиля;
  • один 404 старого профиля переводит сотрудника в статус verification_required, а не в dismissed;
  • статус dismissed устанавливается только после нескольких последовательных проверок 404/410;
  • сетевые ошибки и ответы 5xx не считаются подтверждением увольнения;
  • если число кандидатов на увольнение превышает защитный лимит, автоматическое увольнение приостанавливается;
  • кнопка «Проверить уволенных» сверяет только их profile_key с текущим списком источника и возвращает найденных сотрудников в active без обновления содержимого профиля;
  • каждый успешный новый или измененный разбор сохраняет запись в employee_snapshots;
  • неизмененные профили учитываются в crawl_runs.skipped_count и не получают новый snapshot.

Во время выполнения парсинга found_count, parsed_count, skipped_count и error_count обновляются в базе. Админка опрашивает /api/crawl-runs/latest и показывает прогресс как (parsed_count + skipped_count + error_count) / found_count.

Обслуживание

docker compose logs -f api
docker compose logs -f worker
docker compose exec postgres pg_dump -U miem miem_workers > backup.sql
docker compose down

Версия сервиса: 0.7.7. Админка всегда показывает версии backend и frontend в footer.

Description
No description provided
Readme 879 KiB
Languages
Python 76.8%
HTML 15.7%
CSS 5%
JavaScript 2.4%
Dockerfile 0.1%