Эталонный сайт /sveden/ на Next.js¶
Одна пара «парсер + рендерер», управляемая словарём
standards/sveden/vocab/base.yaml — состав полей и групп
не прописан в коде отдельно на каждый из 14 подразделов, а читается из словаря
при каждом запуске. Правка словаря (новый приказ, новое поле) сразу видна и
парсеру, и рендереру, без дублирования.
Без данных конкретной организации. В репозитории — только пример
(data/example.sveden.json) с вымышленными значениями. Реальные данные
организации живут в отдельном (не публичном или отдельном) репозитории и
монтируются через переменную SVEDEN_DATA_FILE — см. ниже.
Конвейер¶
[сайт ОО с /sveden/] ──(1. parse)──► JSON-файл ──(правки руками)──► JSON-файл ──(2. build)──► статический сайт
- Разобрать существующий сайт (или локально сохранённые страницы) в 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. Работает с любым сайтом, размеченным по этому
приказу, не только с одним конкретным.
-
Править JSON руками — добавлять/исправлять значения, в том числе поля, которых не было на исходном сайте.
-
Пересобрать сайт:
Соберёт статический экспорт в 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) парсер не пройдёт.