MVP сайта

This commit is contained in:
Даниил
2026-10-01 00:28:16 +10:00
commit 25f4255404
55 changed files with 3325 additions and 0 deletions
+189
View File
@@ -0,0 +1,189 @@
# Parser Lab
Учебный сайт для сбора данных: преподаватель импортирует CSV, JSON или JSONL, выбирает оформление и публикует две версии страниц — обычный HTML с пагинацией и HTML с динамической дозагрузкой. Приложение работает на FastAPI, Jinja2, HTML, CSS и небольших JavaScript-модулях без UI-фреймворков. Один аккаунт преподавателя; студентам вход не нужен.
## Быстрый запуск через Docker
Нужны Docker Engine с Compose либо Docker Desktop. Запускайте команды из папки проекта.
1. Скопируйте `.env.example` в `.env` и соберите образ:
```sh
docker compose build
```
2. Укажите логин и обычный пароль (не менее 10 символов) в `.env`:
```dotenv
LAB_ADMIN_USER=teacher
LAB_ADMIN_PASSWORD='ваш-пароль-для-входа'
```
Одинарные кавычки сохраняют символы `$`, пробелы и `#`. Если пароль содержит кавычки или обратные слеши, используйте `uv run --locked python -m parser_lab.cli init-env` для создания корректного `.env` вместо ручного копирования шаблона.
3. Создайте случайный секрет:
```sh
docker compose run --rm --no-deps app uv run --no-sync python -c "import secrets; print(secrets.token_urlsafe(48))"
```
Поместите значение в `LAB_SECRET`. Не используйте пароль как секрет.
4. Для локальной проверки оставьте `LAB_ORIGINS=http://localhost:8080` и `LAB_SECURE_COOKIE=false`. Запустите:
```sh
docker compose up -d
docker compose ps
```
Откройте [кабинет](http://localhost:8080/admin). Проверка сервиса: [healthz](http://localhost:8080/healthz).
Если меняете порт, например на 8087, измените одновременно `LAB_PORT=8087` и `LAB_ORIGINS=http://localhost:8087`. Имя хоста тоже имеет значение: localhost и 127.0.0.1 — разные origin. Через запятую можно указать несколько разрешённых адресов.
## Размещение в интернете
Разместите приложение в корне отдельного домена, например `https://lab.example.org`. Установите:
```dotenv
LAB_ORIGINS=https://lab.example.org
LAB_SECURE_COOKIE=true
```
Передайте домен на reverse proxy с HTTPS. Пример nginx, если приложение доступно прокси на порту 8080:
```nginx
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
client_max_body_size 26m;
}
```
TLS-сертификат и DNS настраиваются на вашем сервере. Ограничьте прямой доступ к порту приложения средствами сервера или привяжите порт в Compose к 127.0.0.1, если прокси находится на той же машине. Подкаталоги вида `/my-lab/` и несколько реплик приложения не поддерживаются первой версией.
## Как опубликовать данные
1. Войдите в кабинет и нажмите «Загрузить данные».
2. Загрузите файл. При необходимости выберите кодировку и разделитель CSV либо массив записей внутри JSON.
3. Нажмите «Проверить файл»: появятся количество записей, предупреждения и первые 20 строк. Сохраните импорт.
4. Выберите представление и включите его. Отметьте поля, задайте подписи, порядок, поиск и категориальный фильтр. Для карточек назначьте заголовок; для сообщений — текст, при желании автора и дату.
5. Проверьте живой предпросмотр, сохраните черновик и откройте полный предпросмотр.
6. Опубликуйте версию. В разделе публикаций появятся ссылки для студентов.
Один набор может иметь все четыре представления. Изменения черновика не затрагивают опубликованные данные. Повторная публикация создаёт новые ссылки с номером версии; прежние продолжают работать. Снятие версии с публикации делает её недоступной. Уже скачанные студентом данные, разумеется, не исчезают.
При замене исходного файла настройки представлений сбрасываются: их нужно проверить заново. Изменение названия после публикации разрешено, изменение адреса набора — нет. Если набор отредактирован в другой вкладке, сохранение сообщит о конфликте, а не затрёт изменения.
## Форматы файлов
| Формат | Поддержка |
|---|---|
| CSV | Первая строка — уникальные непустые заголовки; UTF-8 или Windows-1251; запятая, точка с запятой или табуляция |
| JSON | Массив объектов, одиночный объект или выбранный вложенный массив объектов |
| JSONL | По одному объекту JSON в каждой непустой строке; UTF-8 |
Лимиты по умолчанию: 25 МиБ, 50 000 записей, глубина JSON 20. Размер файла и количество записей можно изменить через `LAB_MAX_BYTES` и `LAB_MAX_RECORDS`. Большие наборы требуют больше памяти: текущая версия хранит JSON-снимки в SQLite и загружает снимок для выборки. Проверяйте скорость на своих данных до занятия.
Дубликаты и исходный порядок сохраняются. Значения CSV остаются строками, чтобы не потерять ведущие нули и исходное представление чисел. В JSON различаются отсутствие поля, null, пустая строка, false и 0. Ошибки формата блокируют импорт; повреждённые строки не пропускаются молча. Пустой набор не публикуется.
Вложенные поля показаны путями: `/profile/age` означает поле age в объекте profile. Символ `/` в самом имени поля кодируется как `~1`, а `~` — как `~0`. Массивы сохраняются; специальные анкеты могут отображать их как пары вопросов и ответов.
### Заполненные анкеты
Для таблицы с колонками `participant, q1, q2` выберите «Колонки с ответами» и замените подписи q1/q2 текстами вопросов.
Для `examples/data/questionnaires.json` достаточно выбрать файл, нажать «Проверить файл» и «Сохранить и настроить». Приложение распознаёт участников и пары `question`/`answer`, выбирает `/participants` и создаёт готовое представление «Анкеты». Предпросмотр сразу показывает двух участников и их ответы. Распознавание зависит от структуры данных, а не имени файла.
Если структура неоднозначна, выберите уровень записей вручную. Для ранее загруженного целиком документа в редакторе доступна кнопка «Настроить анкеты автоматически»: она исправляет черновик без повторной загрузки исходного файла. Опубликованные версии сохраняются.
Сайт отображает готовые данные; он не собирает ответы и не интерпретирует психологические шкалы. Дополнительные примеры синтетические. Хоккейный CSV перенесён из предыдущего проекта.
## Парсинг
Адреса имеют вид:
```text
/html/study-abc/v/1/table
/interactive/study-abc/v/1/table
```
Для HTML данные находятся в исходном ответе сервера. Пагинация — `?page=2`, следующая ссылка — `a[rel=next]`. Стабильные селекторы:
| Селектор | Значение |
|---|---|
| `.record[data-record-id]` | Запись со стабильным в пределах версии id |
| `.field[data-field]` | Значение выбранного поля |
| `data-value-state` | `missing`, `null` или `value` |
| `data-value-type` | Исходный тип: string, number, boolean, object, array, null или missing |
| `data-role` | Роль значения: title, text, author или date |
| `data-label` | Подпись поля или текст вопроса |
| `.detail-link` | Ссылка на отдельную запись |
Фильтры HTML: `q` — регистронезависимый поиск в назначенном поле, `category` — JSON-представление скалярного значения. Формы на странице формируют правильные параметры автоматически. Поиск не выполняет морфологический анализ. При неправильном номере страницы возвращается 400, при отсутствии страницы или версии — 404.
В динамическом варианте первые записи также находятся в HTML-ответе. Кнопка «Загрузить ещё», поиск и фильтры обновляют данные обычными HTTP-запросами через JavaScript. Для практики браузерного парсинга используйте Playwright. Оба варианта используют один шаблон, стили и favicon.
Примеры решений находятся в `examples/parsers/` и не доступны как файлы через публичный сайт:
```sh
uv sync --locked
uv run --locked playwright install chromium
uv run --locked python examples/parsers/scrape_html.py "http://localhost:8080/html/SLUG/v/1/table" --output html.jsonl
uv run --locked python examples/parsers/scrape_interactive.py "http://localhost:8080/interactive/SLUG/v/1/table" --output interactive.jsonl
```
Вместо загруженного Chromium можно использовать установленный Chrome: добавьте `--channel chrome` второму парсеру.
## Обновление и резервная копия
Данные находятся в named volume `lab-data`, смонтированном в `/data`. `docker compose down` сохраняет том. **Не используйте `down -v`, если данные нужны.**
Обновление:
```sh
docker compose down
docker compose build
docker compose up -d
```
Перед обновлением остановите контейнер и скопируйте весь каталог данных:
```sh
docker compose stop app
docker compose cp app:/data ./backup-data
docker compose start app
```
Храните копию вместе с защищённой копией `.env`. Для восстановления подготовьте отдельный Compose-проект с новым томом, создайте его контейнер (`docker compose create`), скопируйте содержимое backup-data в `/data`, восстановите владельца UID 10001 при необходимости и запустите контейнер. Проверяйте восстановление отдельно от рабочего сайта.
Импортированные файлы и старые снимки не удаляются автоматически. Следите за размером тома. Безвозвратное удаление истории не входит в интерфейс первой версии.
## Локальная разработка
Нужен [uv](https://docs.astral.sh/uv/getting-started/installation/) версии 0.8.14 или новее. Python 3.12 и окружение `.venv` управляются через uv. Запускайте команды из корня проекта:
```sh
uv sync --locked
uv run --locked python -m parser_lab.cli init-env
uv run --locked --env-file .env python -m parser_lab
```
`init-env` один раз создаёт `.env` и запрашивает параметры преподавателя. При последующих запусках достаточно последней команды. Активация окружения не требуется.
Зависимости приложения и группа `dev` описаны в `pyproject.toml`, точные версии зафиксированы в `uv.lock`. Для изменения зависимостей используйте `uv add ИМЯ` или `uv add --dev ИМЯ`; сохраняйте оба файла в Git. Устаревшие `requirements*.lock` больше не используются. Docker собирает production-окружение командой `uv sync --locked --no-dev` и запускает готовое окружение через `uv run --no-sync`.
Параметры окружения имеют приоритет над локальной `.env`. Пароль LAB_ADMIN_PASSWORD и секрет LAB_SECRET обязательны, приложение откажется запускаться без них. После изменения пароля или секрета перезапустите приложение (для Docker: `docker compose up -d --force-recreate`); старые сессии станут недействительны.
Тесты:
```sh
uv run --locked pytest -q
# Дополнительные браузерные тесты: LAB_BROWSER_TESTS=1, при необходимости PLAYWRIGHT_CHANNEL=chrome.
```
Старые документы проектирования в `docs/` сохранены как история. Актуальная архитектура — FastAPI + Jinja2 + HTML/CSS/JavaScript; зависимости Rio полностью удалены. Существующие SQLite-данные и опубликованные ссылки совместимы с новой версией. Фактические результаты проверки и ограничения среды — в `docs/verification.md`.
Справка: [uv в Docker](https://docs.astral.sh/uv/guides/integration/docker/), [запуск команд](https://docs.astral.sh/uv/concepts/projects/run/).
Пароль хранится в `.env` обычным текстом. Внутренний Argon2-хеш для проверки входа приложение создаёт в памяти при запуске. Если `.env` остался от предыдущей версии, замените строку `LAB_ADMIN_HASH=...` на `LAB_ADMIN_PASSWORD='ваш-пароль'`; прежний хеш больше не используется. `.env` исключён из Git и Docker-образа.