Перейти к содержанию

Эталонный сайт /sveden/ на Next.js

Одна пара «парсер + рендерер», управляемая словарём standards/sveden/vocab/base.yaml — состав полей и групп не прописан в коде отдельно на каждый из 14 подразделов, а читается из словаря при каждом запуске. Правка словаря (новый приказ, новое поле) сразу видна и парсеру, и рендереру, без дублирования.

Без данных конкретной организации. В репозитории — только пример (data/example.sveden.json) с вымышленными значениями. Реальные данные организации живут в отдельном (не публичном или отдельном) репозитории и монтируются через переменную SVEDEN_DATA_FILE — см. ниже.

Конвейер

[сайт ОО с /sveden/] ──(1. parse)──► JSON-файл ──(правки руками)──► JSON-файл ──(2. build)──► статический сайт
  1. Разобрать существующий сайт (или локально сохранённые страницы) в JSON:
npm run parse -- https://example-oo.ru data/parsed.sveden.json
# или из папки с сохранёнными страницами common.html, struct.html, ...:
npm run parse -- ./saved-pages data/parsed.sveden.json

Парсер (parser/parse.mjs) обходит все 14 подразделов из словаря, вытаскивает значения по itemprop-атрибутам — той же микроразметке, которую требует Приказ Рособрнадзора № 1493. Работает с любым сайтом, размеченным по этому приказу, не только с одним конкретным.

  1. Править JSON руками — добавлять/исправлять значения, в том числе поля, которых не было на исходном сайте.

  2. Пересобрать сайт:

SVEDEN_DATA_FILE=data/parsed.sveden.json npm run build

Соберёт статический экспорт в out/ (см. next.config.mjs, output: "export") — обычные HTML-файлы, размещаемые где угодно (в т.ч. GitHub Pages). Без SVEDEN_DATA_FILE используется data/example.sveden.json.

Локальная разработка

npm install
npm run dev            # http://localhost:3000, данные — data/example.sveden.json
SVEDEN_DATA_FILE=data/parsed.sveden.json npm run dev   # свои данные

Структура

reference-site/
├── lib/
│   ├── vocab.mjs     # загрузка vocab/base.yaml + _global.yaml (общий для парсера и рендерера)
│   ├── data.mjs       # загрузка JSON-файла данных (SVEDEN_DATA_FILE)
│   └── render.mjs     # правило "отсутствует", разбор {text, href}
├── parser/
│   └── parse.mjs      # HTML (сайт или сохранённые страницы) → JSON
├── app/
│   ├── page.jsx                    # список 14 подразделов
│   └── sveden/[section]/page.jsx   # один шаблон на все 14 подразделов
└── data/
    └── example.sveden.json  # вымышленный пример, не реальная ОО

Запасной путь: раздел разбит по ссылкам без микроразметки

Встречается на реальных сайтах (обнаружено на orgma.ru/employees): itemprop стоит не на данных, а на ссылке-программе («31.02.01 Лечебное дело» → отдельная страница состава преподавателей), а сама связанная страница вообще без микроразметки — обычная HTML-таблица. Это не противоречит духу _global.yaml → split_pages (подраздел вправе разбиваться на доп. страницы).

Парсер такое обнаруживает (элемент группы без единого дочернего поля, но со ссылкой) и переходит по ссылке, а на целевой странице ищет таблицу, чьи заголовки столбцов совпадают с полями группы по ключевым словам (FIELD_LABEL_KEYWORDS в parse.mjs) — эвристика поверх произвольной вёрстки, а не часть контракта микроразметки. Срабатывает только если прямое чтение по itemprop не дало вообще ничего (при частичном успехе доверяем основным данным). Опробовано на orgma.ru: employees.teachingStaff — с 0 до 173 настоящих записей о преподавателях.

Известные ограничения (честно, не скрыто)

  • 153 из ~380 полей словаря ещё помечены from: "TBD" (см. vocab/base.yaml) — для них нет согласованного пути в канонической модели организации (schema/instance.schema.yaml). Парсер и рендерер это не блокирует — они работают напрямую по itemprop, минуя from — но при появлении отдельного инструмента синхронизации с канонической моделью эти 153 поля потребуют дополнительного согласования.
  • Подписи полей на «версии для слабовидящих» — сейчас технические ключи словаря (fullName, teachingDiscipline, ...), а не человекочитаемые названия. Отдельного словаря подписей в standards/sveden/ пока нет.
  • Раздел document (ЛНА) в словаре помечен «структура коллекции vs отдельные поля — открыто» — пример-данные для него оставлены пустыми.
  • Извлечение ссылок ({text, href}) работает, если исходная страница оборачивает itemprop в <a href> или содержит такую ссылку внутри — нестандартную вёрстка (JS-рендер, iframe) парсер не пройдёт.