2026-10-01 00:28:16 +10:00
2026-10-01 00:28:16 +10:00
2026-10-01 00:28:16 +10:00
2026-10-01 00:28:16 +10:00
2026-10-01 00:28:16 +10:00
2026-10-01 00:28:16 +10:00
2026-10-01 00:28:16 +10:00
2026-10-01 00:28:16 +10:00
2026-10-01 00:28:16 +10:00

Parser Lab

Parser Lab — сайт для учебных заданий по веб-парсингу. Преподаватель загружает CSV, JSON или JSONL, настраивает отображение данных и публикует страницы для студентов. Каждая коллекция доступна в двух вариантах: обычный HTML с пагинацией и страница с динамической дозагрузкой.

Есть четыре представления: таблица, карточки, сообщения и анкеты. Поля в списке и в раскрытой записи можно настраивать отдельно. Для преподавателя предусмотрен один аккаунт; публичные страницы открываются без входа.

Приложение написано на Python 3.12, FastAPI и Jinja2. Интерфейс — HTML, CSS и JavaScript, данные хранятся в SQLite. Отдельный сервер базы данных не нужен.

Запуск через Docker Compose

Нужен Docker с командой docker compose: Docker Engine с Compose plugin или Docker Desktop. Python и uv на хосте не требуются. Все команды ниже выполняются из корня репозитория. Примеры рассчитаны на Bash; в Windows можно использовать WSL2.

1. Подготовьте конфигурацию и соберите образ

При первом запуске создайте .env из шаблона:

cp .env.example .env
docker compose build

Если .env уже настроен, повторно копировать шаблон не нужно.

2. Задайте пароль и секрет

Сгенерируйте секрет для сессий:

docker run --rm parser-lab:1.0 python -c 'import secrets; print(secrets.token_urlsafe(48))'

Откройте .env. Замените значения LAB_ADMIN_PASSWORD и LAB_SECRET своими:

LAB_ADMIN_USER=teacher
LAB_ADMIN_PASSWORD='ЗАМЕНИТЕ_НА_СВОЙ_ПАРОЛЬ'
LAB_SECRET=ВСТАВЬТЕ_СГЕНЕРИРОВАННЫЙ_СЕКРЕТ
LAB_ORIGINS=http://localhost:8080
LAB_SECURE_COOKIE=false
LAB_PORT=8080

Пароль должен содержать не менее 10 символов, секрет — не менее 32. Это разные значения. В .env пароль хранится обычным текстом; файл исключён из Git и сборки образа.

Одинарные кавычки сохраняют $, # и пробелы в пароле. Правила экранирования описаны в документации Compose. При локальной установке файл можно создать интерактивной командой init-env, приведённой ниже.

3. Запустите приложение

docker compose up -d
docker compose ps

Кабинет преподавателя: http://localhost:8080/admin. Войдите с логином и паролем из .env.

Запуск через Docker без Compose

Соберите тот же образ:

docker build -t parser-lab:1.0 .

Создайте и заполните .env, как описано выше. Команда генерации секрета также работает без Compose.

Создайте том и запустите контейнер:

docker volume create parser-lab-data

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 подключается как файл только для чтения и загружается через uv. Так сохраняется обработка кавычек в пароле. Файл должен существовать и быть доступен на чтение пользователю контейнера с UID 10001.

Адрес кабинета тот же: http://localhost:8080/admin. Управление контейнером:

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 версии 0.8.14 или новее. Проект использует Python 3.12; uv может установить его и создать виртуальное окружение.

Из корня репозитория выполните:

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. Если файл уже существует, команда откажется его перезаписывать — в этом случае пропустите её и проверьте текущие настройки.

Кабинет доступен по адресу http://localhost:8080/admin. Для остановки нажмите Ctrl+C. При следующих запусках достаточно последней команды; активировать .venv вручную не нужно.

По умолчанию данные сохраняются в ./data. Каталог создаётся автоматически. Параметры окружения имеют приоритет над значениями из .env.

Для разработки с перезапуском при изменении кода:

uv run --locked --env-file .env uvicorn parser_lab.app:create_app \
  --factory --host 127.0.0.1 --port 8080 --reload

При обычном запуске через python -m parser_lab адрес сервера — 0.0.0.0:8080. Переменная LAB_PORT его не меняет. Если нужен другой порт, используйте команду uvicorn с нужным --port и измените LAB_ORIGINS.

Настройки

Переменная Назначение
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:

LAB_ORIGINS=http://localhost:8080,http://127.0.0.1:8080

После изменения .env перезапустите локальный процесс или пересоздайте контейнер Compose:

docker compose up -d --force-recreate

Для Docker без Compose остановите и удалите контейнер, затем повторите команду docker run с тем же томом. Это также заново подключит изменённый .env. После смены логина, пароля или секрета прежние сессии перестанут действовать.

Развёртывание на сервере

Приложение рассчитано на один экземпляр и размещение в корне отдельного домена, например https://lab.example.org. Запуск в подкаталоге вроде /parser/ не поддерживается.

  1. Настройте DNS домена на сервер и HTTPS в обратном прокси.

  2. Запустите приложение одним из способов выше.

  3. Укажите публичный адрес в .env:

    LAB_ORIGINS=https://lab.example.org
    LAB_SECURE_COOKIE=true
    
  4. Если nginx работает на том же хосте, ограничьте публикацию порта адресом 127.0.0.1. В compose.yaml замените строку в ports на:

    - "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:

    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:

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-развёртывание:

docker compose up -d --build
docker compose ps

Для Docker без Compose пересоберите образ, остановите и удалите контейнер, затем повторите команду docker run, сохранив прежний том parser-lab-data. Локально выполните uv sync --locked и перезапустите приложение.

Восстановление копии в новую установку Compose с пустым томом:

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. После восстановления проверьте вход в кабинет и открытие опубликованной коллекции.

Для разработки и учебных заданий

Основные файлы проекта:

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/               Проверки приложения

Установка зависимостей и запуск тестов:

uv sync --locked
uv run --locked pytest -q

Браузерные тесты запускаются отдельно:

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.

Примеры парсеров принимают ссылку на опубликованную коллекцию:

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; остальные загружаются кнопкой «Загрузить ещё».

S
Description
Конструктор сайта для парсинга из csv, json, jsonl
Readme
369 KiB
Languages
Python 73.6%
HTML 13.4%
JavaScript 12.4%
Dockerfile 0.6%