Обновил структуру проекта
This commit is contained in:
@@ -1,167 +1,102 @@
|
||||
# Parser Lab
|
||||
|
||||
Учебный сайт для сбора данных: преподаватель импортирует CSV, JSON или JSONL, выбирает оформление и публикует две версии страниц — обычный HTML с пагинацией и HTML с динамической дозагрузкой. Приложение работает на FastAPI, Jinja2, HTML, CSS и небольших JavaScript-модулях без UI-фреймворков. Один аккаунт преподавателя; студентам вход не нужен.
|
||||
Parser Lab — сайт для учебных заданий по веб-парсингу. Преподаватель загружает CSV, JSON или JSONL, настраивает отображение данных и публикует страницы для студентов. Каждая коллекция доступна в двух вариантах: обычный HTML с пагинацией и страница с динамической дозагрузкой.
|
||||
|
||||
## Быстрый запуск через Docker
|
||||
Есть четыре представления: таблица, карточки, сообщения и анкеты. Поля в списке и в раскрытой записи можно настраивать отдельно. Для преподавателя предусмотрен один аккаунт; публичные страницы открываются без входа.
|
||||
|
||||
Нужны Docker Engine с Compose либо Docker Desktop. Запускайте команды из папки проекта.
|
||||
Приложение написано на Python 3.12, FastAPI и Jinja2. Интерфейс — HTML, CSS и JavaScript, данные хранятся в SQLite. Отдельный сервер базы данных не нужен.
|
||||
|
||||
1. Скопируйте `.env.example` в `.env` и соберите образ:
|
||||
## Запуск через Docker Compose
|
||||
|
||||
```sh
|
||||
docker compose build
|
||||
```
|
||||
Нужен Docker с командой `docker compose`: Docker Engine с Compose plugin или Docker Desktop. Python и uv на хосте не требуются. Все команды ниже выполняются из корня репозитория. Примеры рассчитаны на Bash; в Windows можно использовать WSL2.
|
||||
|
||||
2. Укажите логин и обычный пароль (не менее 10 символов) в `.env`:
|
||||
### 1. Подготовьте конфигурацию и соберите образ
|
||||
|
||||
```dotenv
|
||||
LAB_ADMIN_USER=teacher
|
||||
LAB_ADMIN_PASSWORD='ваш-пароль-для-входа'
|
||||
```
|
||||
При первом запуске создайте `.env` из шаблона:
|
||||
|
||||
Одинарные кавычки сохраняют символы `$`, пробелы и `#`. Если пароль содержит кавычки или обратные слеши, используйте `uv run --locked python -m parser_lab.cli init-env` для создания корректного `.env` вместо ручного копирования шаблона.
|
||||
```sh
|
||||
cp .env.example .env
|
||||
docker compose build
|
||||
```
|
||||
|
||||
3. Создайте случайный секрет:
|
||||
Если `.env` уже настроен, повторно копировать шаблон не нужно.
|
||||
|
||||
```sh
|
||||
docker compose run --rm --no-deps app uv run --no-sync python -c "import secrets; print(secrets.token_urlsafe(48))"
|
||||
```
|
||||
### 2. Задайте пароль и секрет
|
||||
|
||||
Поместите значение в `LAB_SECRET`. Не используйте пароль как секрет.
|
||||
Сгенерируйте секрет для сессий:
|
||||
|
||||
4. Для локальной проверки оставьте `LAB_ORIGINS=http://localhost:8080` и `LAB_SECURE_COOKIE=false`. Запустите:
|
||||
```sh
|
||||
docker run --rm parser-lab:1.0 python -c 'import secrets; print(secrets.token_urlsafe(48))'
|
||||
```
|
||||
|
||||
```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`. Установите:
|
||||
Откройте `.env`. Замените значения `LAB_ADMIN_PASSWORD` и `LAB_SECRET` своими:
|
||||
|
||||
```dotenv
|
||||
LAB_ORIGINS=https://lab.example.org
|
||||
LAB_SECURE_COOKIE=true
|
||||
LAB_ADMIN_USER=teacher
|
||||
LAB_ADMIN_PASSWORD='ЗАМЕНИТЕ_НА_СВОЙ_ПАРОЛЬ'
|
||||
LAB_SECRET=ВСТАВЬТЕ_СГЕНЕРИРОВАННЫЙ_СЕКРЕТ
|
||||
LAB_ORIGINS=http://localhost:8080
|
||||
LAB_SECURE_COOKIE=false
|
||||
LAB_PORT=8080
|
||||
```
|
||||
|
||||
Передайте домен на reverse proxy с HTTPS. Пример nginx, если приложение доступно прокси на порту 8080:
|
||||
Пароль должен содержать не менее 10 символов, секрет — не менее 32. Это разные значения. В `.env` пароль хранится обычным текстом; файл исключён из Git и сборки образа.
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
||||
Одинарные кавычки сохраняют `$`, `#` и пробелы в пароле. Правила экранирования описаны в [документации Compose](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/#env-file-syntax). При локальной установке файл можно создать интерактивной командой `init-env`, приведённой ниже.
|
||||
|
||||
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/` и не доступны как файлы через публичный сайт:
|
||||
### 3. Запустите приложение
|
||||
|
||||
```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
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Перед обновлением остановите контейнер и скопируйте весь каталог данных:
|
||||
Кабинет преподавателя: [http://localhost:8080/admin](http://localhost:8080/admin). Войдите с логином и паролем из `.env`.
|
||||
|
||||
## Запуск через Docker без Compose
|
||||
|
||||
Соберите тот же образ:
|
||||
|
||||
```sh
|
||||
docker compose stop app
|
||||
docker compose cp app:/data ./backup-data
|
||||
docker compose start app
|
||||
docker build -t parser-lab:1.0 .
|
||||
```
|
||||
|
||||
Храните копию вместе с защищённой копией `.env`. Для восстановления подготовьте отдельный Compose-проект с новым томом, создайте его контейнер (`docker compose create`), скопируйте содержимое backup-data в `/data`, восстановите владельца UID 10001 при необходимости и запустите контейнер. Проверяйте восстановление отдельно от рабочего сайта.
|
||||
Создайте и заполните `.env`, как описано выше. Команда генерации секрета также работает без Compose.
|
||||
|
||||
Импортированные файлы и старые снимки не удаляются автоматически. Следите за размером тома. Безвозвратное удаление истории не входит в интерфейс первой версии.
|
||||
Создайте том и запустите контейнер:
|
||||
|
||||
## Локальная разработка
|
||||
```sh
|
||||
docker volume create parser-lab-data
|
||||
|
||||
Нужен [uv](https://docs.astral.sh/uv/getting-started/installation/) версии 0.8.14 или новее. Python 3.12 и окружение `.venv` управляются через uv. Запускайте команды из корня проекта:
|
||||
docker run -d \
|
||||
--name parser-lab \
|
||||
--restart unless-stopped \
|
||||
--stop-timeout 30 \
|
||||
-p 8080:8080 \
|
||||
--mount type=volume,source=parser-lab-data,target=/data \
|
||||
--mount "type=bind,source=$(pwd)/.env,target=/app/.env,readonly" \
|
||||
parser-lab:1.0 \
|
||||
uv run --no-sync --env-file /app/.env python -m parser_lab
|
||||
```
|
||||
|
||||
Здесь `.env` подключается [как файл только для чтения](https://docs.docker.com/engine/storage/bind-mounts/) и загружается через uv. Так сохраняется обработка кавычек в пароле. Файл должен существовать и быть доступен на чтение пользователю контейнера с UID 10001.
|
||||
|
||||
Адрес кабинета тот же: [http://localhost:8080/admin](http://localhost:8080/admin). Управление контейнером:
|
||||
|
||||
```sh
|
||||
docker stop parser-lab
|
||||
docker start parser-lab
|
||||
```
|
||||
|
||||
При таком запуске `LAB_PORT` не используется: внешний порт задаётся параметром `-p`. Для порта 8087 укажите `-p 8087:8080` и `LAB_ORIGINS=http://localhost:8087` в `.env`.
|
||||
|
||||
Том `parser-lab-data` из этого примера и том Compose — разные хранилища. Данные между ними автоматически не переносятся.
|
||||
|
||||
## Локальный запуск без Docker
|
||||
|
||||
Установите [uv](https://docs.astral.sh/uv/getting-started/installation/) версии 0.8.14 или новее. Проект использует Python 3.12; uv может установить его и создать виртуальное окружение.
|
||||
|
||||
Из корня репозитория выполните:
|
||||
|
||||
```sh
|
||||
uv sync --locked
|
||||
@@ -169,21 +104,190 @@ uv run --locked python -m parser_lab.cli init-env
|
||||
uv run --locked --env-file .env python -m parser_lab
|
||||
```
|
||||
|
||||
`init-env` один раз создаёт `.env` и запрашивает параметры преподавателя. При последующих запусках достаточно последней команды. Активация окружения не требуется.
|
||||
`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`.
|
||||
Кабинет доступен по адресу [http://localhost:8080/admin](http://localhost:8080/admin). Для остановки нажмите `Ctrl+C`. При следующих запусках достаточно последней команды; активировать `.venv` вручную не нужно.
|
||||
|
||||
Параметры окружения имеют приоритет над локальной `.env`. Пароль LAB_ADMIN_PASSWORD и секрет LAB_SECRET обязательны, приложение откажется запускаться без них. После изменения пароля или секрета перезапустите приложение (для Docker: `docker compose up -d --force-recreate`); старые сессии станут недействительны.
|
||||
По умолчанию данные сохраняются в `./data`. Каталог создаётся автоматически. [Параметры окружения имеют приоритет над значениями из `.env`](https://docs.astral.sh/uv/concepts/configuration-files/#env).
|
||||
|
||||
Тесты:
|
||||
Для разработки с перезапуском при изменении кода:
|
||||
|
||||
```sh
|
||||
uv run --locked pytest -q
|
||||
# Дополнительные браузерные тесты: LAB_BROWSER_TESTS=1, при необходимости PLAYWRIGHT_CHANNEL=chrome.
|
||||
uv run --locked --env-file .env uvicorn parser_lab.app:create_app \
|
||||
--factory --host 127.0.0.1 --port 8080 --reload
|
||||
```
|
||||
|
||||
Старые документы проектирования в `docs/` сохранены как история. Актуальная архитектура — FastAPI + Jinja2 + HTML/CSS/JavaScript; зависимости Rio полностью удалены. Существующие SQLite-данные и опубликованные ссылки совместимы с новой версией. Фактические результаты проверки и ограничения среды — в `docs/verification.md`.
|
||||
При обычном запуске через `python -m parser_lab` адрес сервера — `0.0.0.0:8080`. Переменная `LAB_PORT` его не меняет. Если нужен другой порт, используйте команду uvicorn с нужным `--port` и измените `LAB_ORIGINS`.
|
||||
|
||||
Справка: [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-образа.
|
||||
| Переменная | Назначение |
|
||||
| --- | --- |
|
||||
| `LAB_ADMIN_USER` | Логин преподавателя. Обязателен. |
|
||||
| `LAB_ADMIN_PASSWORD` | Пароль, не менее 10 символов. Обязателен. |
|
||||
| `LAB_SECRET` | Случайный секрет сессий, не менее 32 символов. Обязателен. |
|
||||
| `LAB_ORIGINS` | Разрешённые адреса сайта через запятую, включая протокол и порт. По умолчанию `http://localhost:8080`. |
|
||||
| `LAB_SECURE_COOKIE` | `false` для локального HTTP, `true` для HTTPS. Если не задана, используется `true`. |
|
||||
| `LAB_PORT` | Внешний порт в Compose. По умолчанию 8080. |
|
||||
| `LAB_DATA_DIR` | Каталог данных. По умолчанию `./data`, в Docker — `/data`. Compose задаёт `/data` явно. |
|
||||
| `LAB_MAX_BYTES` | Максимальный размер загружаемого файла. По умолчанию 26214400 байт, то есть 25 МиБ. |
|
||||
| `LAB_MAX_RECORDS` | Максимальное число записей в импорте. По умолчанию 50000. |
|
||||
|
||||
`localhost` и `127.0.0.1` считаются разными адресами. Если используете оба, добавьте оба в `LAB_ORIGINS`:
|
||||
|
||||
```dotenv
|
||||
LAB_ORIGINS=http://localhost:8080,http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
После изменения `.env` перезапустите локальный процесс или пересоздайте контейнер Compose:
|
||||
|
||||
```sh
|
||||
docker compose up -d --force-recreate
|
||||
```
|
||||
|
||||
Для Docker без Compose остановите и удалите контейнер, затем повторите команду `docker run` с тем же томом. Это также заново подключит изменённый `.env`. После смены логина, пароля или секрета прежние сессии перестанут действовать.
|
||||
|
||||
## Развёртывание на сервере
|
||||
|
||||
Приложение рассчитано на один экземпляр и размещение в корне отдельного домена, например `https://lab.example.org`. Запуск в подкаталоге вроде `/parser/` не поддерживается.
|
||||
|
||||
1. Настройте DNS домена на сервер и HTTPS в обратном прокси.
|
||||
2. Запустите приложение одним из способов выше.
|
||||
3. Укажите публичный адрес в `.env`:
|
||||
|
||||
```dotenv
|
||||
LAB_ORIGINS=https://lab.example.org
|
||||
LAB_SECURE_COOKIE=true
|
||||
```
|
||||
|
||||
4. Если nginx работает на том же хосте, ограничьте публикацию порта адресом `127.0.0.1`. В `compose.yaml` замените строку в `ports` на:
|
||||
|
||||
```yaml
|
||||
- "127.0.0.1:${LAB_PORT:-8080}:8080"
|
||||
```
|
||||
|
||||
Для обычного Docker используйте `-p 127.0.0.1:8080:8080`. Для локального процесса — uvicorn с `--host 127.0.0.1`.
|
||||
|
||||
5. Добавьте в HTTPS-блок `server` nginx:
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
||||
|
||||
6. Примените конфигурацию nginx и перезапустите приложение. Проверьте `/healthz`, вход в кабинет и загрузку небольшого файла через домен.
|
||||
|
||||
Этот фрагмент предполагает уже настроенный TLS-сертификат. Если прокси работает в другом контейнере, подключите оба сервиса к общей сети и укажите адрес приложения в ней: `127.0.0.1` внутри контейнера прокси указывает на сам прокси.
|
||||
|
||||
## Работа с коллекциями
|
||||
|
||||
1. Войдите в кабинет и нажмите «Загрузить данные».
|
||||
2. Выберите файл. Для CSV при необходимости укажите кодировку и разделитель; для вложенного JSON — список, из которого нужно брать записи.
|
||||
3. Нажмите «Проверить файл», проверьте первые строки и сохраните импорт.
|
||||
4. Включите нужное представление, выберите поля, подписи и порядок. Для карточек назначьте заголовок, для сообщений — текст.
|
||||
5. Чтобы раскрытая запись содержала больше информации, выберите «Настройка полей → В полной записи» и включите «Отдельные поля полной записи».
|
||||
6. Проверьте результат через «Предпросмотр данных», сохраните черновик и опубликуйте новую версию. Ссылки появятся в разделе «Публикации».
|
||||
|
||||
Предпросмотр показывает текущие изменения. Ссылка «Предпросмотр» открывает сохранённый черновик. Публикации хранятся отдельно: редактирование черновика не меняет страницы, которые уже доступны студентам. Новая публикация получает новый номер и новые ссылки; прежние работают, пока версия не снята с публикации.
|
||||
|
||||
Замена исходного файла сбрасывает настройки представлений. После первой публикации адрес коллекции изменить нельзя.
|
||||
|
||||
Кнопка «Удалить коллекцию» удаляет черновик, все публикации и связанные исходные файлы. Действие требует подтверждения и не имеет отмены. Если файл не удалось удалить, приложение сообщит об этом и повторит очистку при следующем запуске. Промежуточные исходники от замен в старых версиях могли потерять связь с коллекцией; такие файлы автоматически не удаляются.
|
||||
|
||||
### Форматы данных
|
||||
|
||||
| Формат | Требования |
|
||||
| --- | --- |
|
||||
| CSV | Первая строка — уникальные непустые заголовки. Кодировка UTF-8 или Windows-1251. Разделитель — запятая, точка с запятой или табуляция. |
|
||||
| JSON | Массив объектов, один объект или выбранный вложенный массив объектов. |
|
||||
| JSONL | Один объект JSON на строку, кодировка UTF-8. Пустые строки пропускаются. |
|
||||
|
||||
Порядок записей и дубликаты сохраняются. Значения CSV остаются строками, в том числе числа с ведущими нулями. В JSON различаются отсутствующее поле, `null`, пустая строка, `false` и `0`. Максимальная глубина JSON — 20. Ошибки формата блокируют импорт; пустую коллекцию опубликовать нельзя.
|
||||
|
||||
Вложенные поля обозначаются путями: `/profile/age` — поле `age` внутри `profile`. Массивы вопросов с полями `question` и `answer` приложение умеет распознавать как анкеты. Для неоднозначной структуры уровень записей и поля можно выбрать вручную. Сайт отображает загруженные ответы, но не собирает новые.
|
||||
|
||||
## Обновление и резервные копии
|
||||
|
||||
SQLite и исходные файлы находятся в одном каталоге. Локально это `data/`, в контейнере — `/data`. Compose сохраняет его в томе с именем вида `<имя-проекта>_lab-data`.
|
||||
|
||||
Перед обновлением остановите приложение и скопируйте весь каталог данных. Для каждой копии используйте новый каталог. Пример для Compose:
|
||||
|
||||
```sh
|
||||
mkdir -p backups/before-update
|
||||
docker compose stop app
|
||||
docker compose cp app:/data/. ./backups/before-update/
|
||||
docker compose start app
|
||||
```
|
||||
|
||||
Для обычного Docker аналогичные команды — `docker stop parser-lab`, `docker cp parser-lab:/data/. ./backups/before-update/` и `docker start parser-lab`. При локальном запуске остановите процесс и скопируйте `data/`. Отдельно сохраните `.env` в защищённом месте.
|
||||
|
||||
После получения новой версии исходного кода обновите Compose-развёртывание:
|
||||
|
||||
```sh
|
||||
docker compose up -d --build
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
Для Docker без Compose пересоберите образ, остановите и удалите контейнер, затем повторите команду `docker run`, сохранив прежний том `parser-lab-data`. Локально выполните `uv sync --locked` и перезапустите приложение.
|
||||
|
||||
Восстановление копии в **новую установку Compose с пустым томом**:
|
||||
|
||||
```sh
|
||||
docker compose create app
|
||||
docker compose cp ./backups/before-update/. app:/data/
|
||||
docker compose run --rm --no-deps --user root app chown -R 10001:10001 /data
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Перед этим подготовьте образ и `.env`. После восстановления проверьте вход в кабинет и открытие опубликованной коллекции.
|
||||
|
||||
## Для разработки и учебных заданий
|
||||
|
||||
Основные файлы проекта:
|
||||
|
||||
```text
|
||||
src/parser_lab/
|
||||
app.py HTTP-маршруты и запуск приложения
|
||||
admin.py Импорт и настройки коллекций
|
||||
repository.py SQLite, исходные файлы и публикации
|
||||
importing.py Чтение CSV, JSON и JSONL
|
||||
views.py Выбор полей и подготовка записей
|
||||
web/templates/ Шаблоны страниц
|
||||
web/static/ Стили и JavaScript
|
||||
examples/parsers/ Примеры парсеров
|
||||
tests/ Проверки приложения
|
||||
```
|
||||
|
||||
Установка зависимостей и запуск тестов:
|
||||
|
||||
```sh
|
||||
uv sync --locked
|
||||
uv run --locked pytest -q
|
||||
```
|
||||
|
||||
Браузерные тесты запускаются отдельно:
|
||||
|
||||
```sh
|
||||
uv run --locked playwright install chromium
|
||||
LAB_BROWSER_TESTS=1 uv run --locked pytest -q tests/test_browser.py
|
||||
```
|
||||
|
||||
При использовании установленного Chrome задайте `PLAYWRIGHT_CHANNEL=chrome`. Фикстура браузерных тестов также поддерживает `PLAYWRIGHT_EXECUTABLE_PATH` для явного пути к браузеру. Несколько тестов анкет ожидают файл `examples/data/questionnaires.json`; если его нет в рабочей копии, эти проверки завершатся с `FileNotFoundError`.
|
||||
|
||||
Примеры парсеров принимают ссылку на опубликованную коллекцию:
|
||||
|
||||
```sh
|
||||
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
|
||||
```
|
||||
|
||||
Замените `SLUG` и номер версии значениями из ссылки в кабинете. Для второго примера нужен браузер Playwright; установленный Chrome можно выбрать аргументом `--channel chrome`.
|
||||
|
||||
В HTML записи доступны по селектору `.record[data-record-id]`, значения — `.field[data-field]`. Ссылка `.detail-link` открывает полную запись, `a[rel=next]` ведёт на следующую страницу. Атрибуты `data-label`, `data-value-state` и `data-value-type` содержат подпись, состояние и тип значения. В динамическом варианте первые записи тоже находятся в HTML; остальные загружаются кнопкой «Загрузить ещё».
|
||||
|
||||
Reference in New Issue
Block a user