MVP сайта
This commit is contained in:
@@ -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-образа.
|
||||
Reference in New Issue
Block a user