12 KiB
27. Определение глав в загружаемых книгах: стандарты · практика · алгоритмы
Оркестратор №15, 09.08.2026. Веб-ресёрч по заказу владельца 09.08 (контекст: фактограф приёмки пака блокеров показал, что ingest-слой границ глав захардкожен под CJK-форму, выведенную из одной стенд-книги, — не-CJK txt молча становится одной главой, epub режется по spine с игнором оглавления, print-TOC порождает главы-огрызки). Три веб-агента: стандарты форматов · практика инструментов · алгоритмы; каждый факт с URL. Статус: ФАКТУРА СОБРАНА; дизайн-решение владельцем НЕ принято — этот док не ратифицирует стройку. Носители работ — строки бэклога (заводятся лендингом пака блокеров).
§1. Ответы на вопросы владельца
- «Есть ли алгоритмы, умеющие парсить главы, или всё эвристика?» Надёжного не-эвристического алгоритма для голого txt НЕ существует, и это доказано числами: Chapter Captor (EMNLP 2020, 9126 романов Gutenberg) — распознавание ЗАГОЛОВКОВ гибридом regex+BERT даёт F1=0.77, а предсказание границ БЕЗ заголовков (семантическая сегментация) — F1=0.453 [arxiv.org/abs/2011.04163]. Главы — авторская разметка, а не тематический сдвиг; TextTiling/C99/BERT-сегментация спроектированы под другое и как основа непригодны. Все зрелые инструменты (Calibre, chapterize, kaf-cli, AozoraEpub3) — каскад «статистика формата строк → языковые паттерны → структурные fallback'и → пороги правдоподобия».
- «У epub по стандарту оглавления нет?» ЕСТЬ И ОБЯЗАТЕЛЬНО. EPUB 3: публикация «MUST contain an EPUB navigation document» с toc nav — иерархия ol/li/a с заголовками и ссылками файл#якорь [w3.org/TR/epub-33/#sec-nav-toc]. EPUB 2: обязательный NCX (navMap/navPoint, вложенность томов/глав, navLabel+ с xml:lang — единственный стандартный носитель МУЛЬТИЯЗЫЧНЫХ названий глав) [idpf.org/epub/20/spec/OPF_2.0.1_draft.htm]. FB2 (жив, критичен для ru-рынка): дерево глав = сама разметка body/section с рекурсией и title [github.com/gribuser/fb2]. То есть для epub/fb2 границы И НАЗВАНИЯ глав достаются из стандарта вообще без эвристик — наш текущий разрез «по spine с игнором nav» выбрасывает гарантированную спекой структуру.
- «Что с печатным оглавлением?» Отдельное устоявшееся направление (ICDAR Book Structure Extraction 2009–2013): детект TOC-блока → парсинг записей → линковка в тело матчем заголовков. Print-TOC — не помеха, а бесплатный источник границ и КАНОНИЧЕСКИХ названий; TOC-гейты дёшевы (chapterize: кандидаты ближе 4 строк = блок оглавления).
§2. Несущие факты по слоям
EPUB (уточнения сверх §1): резать по spine неверно ПО СТАНДАРТУ — якоря nav легально ведут внутрь файла (несколько глав в одном xhtml) и глава легально живёт в нескольких файлах; правильный алгоритм: поток = spine linear="yes", границы = якоря nav/NCX, спроецированные на поток. Порядку/полноте nav верить нельзя (с EPUB 3.3 порядок — SHOULD, полнота не требовалась никогда) — нормализация: сортировка целей по позиции в spine, непокрытые документы прикреплять к предыдущей главе. Служебные страницы фильтруются каскадом штатных сигналов: manifest properties (nav, cover-image) → spine linear="no" → landmarks (epub:type обязателен: cover/titlepage/toc/bodymatter) → epub:type/DPUB-ARIA в контенте (добровольный — сигнал, не гарантия). Однофайловый epub и битый nav → фолбэк на txt-эвристики.
TXT — состояние искусства (Calibre как эталон): (1) статистический детект формата абзацев (line histogram, доли пустых/отступных строк); (2) списки словесных паттернов — у Calibre только en+de в коде(!), CJK-миры держат свои инструменты: kaf-cli zh «第.{1,8}章» + том «第…[卷部]», AozoraEpub3 ja — богатый список 話/章/篇/部/節/幕/プロローグ/序章/終章/間章; (3) пороги правдоподобия: схема принимается только при min_chapters=ceil(wordcount/7000) ≤ hits < 150, заголовок ≤35 симв. (kaf-cli), ≥3 глав (chapterize); (4) сцены «* * *» — отдельный класс, не главы; (5) конвенция Gutenberg: 4 пустые строки перед главой — пустострочные раны как вторичный сигнал [pgdp.net DP Formatting Guidelines; W3C WCAG T3]. Готовой мультиязычной библиотеки паттернов-как-данных не существует — собирать свою в langpack из проверенных списков этих инструментов (сид: kaf-cli+AozoraEpub3+Calibre+chapterize; regex-набор Chapter Captor — сид для en); прецедент конфиг-схемы — ParserConfig oomol-lab (chapter_patterns/volume_patterns/section_patterns).
Иерархия: том/часть над главой — отдельный уровень паттернов у всех зрелых инструментов; плоский список ломается на «Том 2, Глава 1» (нумерация перезапускается) — в модель структуры нужен уровень volume/part, иначе якоря и банк едут на многотомниках.
LLM-место: полная LLM-разметка книги (LumberChunker, EMNLP 2024) — дороже/медленнее по признанию авторов, цена линейна от объёма, недетерминизм ломает снапшоты — ОТВЕРГНУТЬ как основной путь. Доказанный в проде паттерн (LILAC FSE'24, лог-парсинг): LLM один раз ВЫВОДИТ шаблон, дальше режет детерминированный код — шаблонов на порядки меньше экземпляров; перенос на книги естественен (один дешёвый вызов на семплах строк-кандидатов → regex книги → в снапшот → перепрогоны $0 и воспроизводимы). Для книг такой паттерн не опубликован — будем первыми.
§3. Рекомендуемый класс решения (предложение оркестратора, НЕ ратифицировано)
Каскад по цене источника, LLM никогда не режет сама:
- Структурные источники ($0, без эвристик): epub nav → NCX → fb2 section/title; скрейп-epub (WebToEpub/FanFicFare и т.п.) — границы точны по построению, доверять. Названия глав на языке оригинала — из тех же источников (закрывает и вопрос heading/К-3).
- TXT: детект print-TOC блока (плотность кандидатов) → использовать его как источник имён и сверки; языко-НЕзависимый статистический скорер строки-заголовка (короткая · изолирована пустыми строками · монотонная нумерация · повторяющийся префикс-шаблон · равномерность по файлу · коридор hits/wordcount) + словари маркеров per-language как ДАННЫЕ langpack; несколько конкурирующих схем-кандидатов, выбор по скору.
- Fallback: один дешёвый LLM-вызов выводит шаблон книги, шаблон — артефакт снапшота (LILAC-паттерн).
- Всегда: громкие WARN вместо молчаливой деградации («1 глава на 20 МБ», «скачок нумерации», «глава-огрызок»).
Требование к дизайну (урок владельца 09.08): модель «что бывает заголовком» выводится из КОРПУСА разнообразных книг (языки·авторы·форматы), не из стенд-книги; приёмка детекта — прогоном по корпусу с замером точности границ (полигон). Схема данных проектируется от общего кейса, а не наращиванием заплаток к CJK-форме (история вопроса: пак-2 завёл CJK-сплит до канона общности, фазы общности вынесли маркеры в данные, но форма шаблона осталась в Go и слой выпал из карты общности — D39.64 честно не включал его в критерий).
Цена итераций: правки детекта двигают нарезку → снапшот → реснапшот-класс на прогнанных книгах. Проектировать один раз от корпуса и строить ДО открытия интейка книг пользователям; сейчас единственная платная книга — стенд, окно дешёвое.
§4. Что НЕ брать
Тематическая сегментация как основа (F1=0.453) — максимум tie-breaker · полная LLM-разметка (цена/недетерминизм) · вебновелльные скрейперы как образец детекта (у них глава = страница сайта, задачи нет) · универсальный код без языковых данных (ни один инструмент так не решил).