# Покрытие страниц `data/raw` датасетом: аудит сборщика Отчёт о том, как сборщик (`tools/collect.py` → `tools/harvest/harvest_core.py` → `src/prepare_data.py`) превращает сохранённые страницы в обучающий датасет, какие форматы строк он терял и что исправлено. Цифры получены на всех страницах `data/raw` (35 385 шт.), а не на выборке. Инструмент замера — `tools/coverage_audit.py` (пишет `reports/coverage_audit.md`, `data/raw/_coverage_pages.csv`, `data/raw/_coverage_lines.csv`). Строка считается «дорожкой» независимым от парсеров детектором (префикс «Перевод/Озвучка/Аудио…», слово типа озвучки или блок `|студия|`), а «покрытой» — если для неё есть строка `labels.csv` этого релиза (`raw` / `audio_raw` / `role_raw`). ## Как устроен сборщик и где терялись данные Пять стадий, у каждой свой кэш: | стадия | вход → выход | кэш / версия | |---|---|---| | `collect` | браузер → `data/raw/<трекер>/.json` + `.html` | `_state.json` | | `build` | `data/raw` → `data/pages/.txt` + строки `data/labels.csv` | `_built.json`, `PARSE_VERSION` | | `meta` | страницы → `data/meta.csv` | `_meta_cache.json`, `META_VERSION` | | `prepare_data` | `labels.csv` → `data/processed/dataset.json` | — | | `train` | `dataset.json` → `models/best`, `reports/` | — | До этого аудита датасет собирался из 28 155 релизов (56 131 строка), хотя страниц в `data/raw` было 35 385: **7 371 страница не дала ни одной строки**. Часть из них законно не содержит озвучки (русский оригинал, «Перевод: не требуется», немое кино, музыка/сканы), но не менее **1 259 страниц с настоящими дорожками** в датасет не попадали. Причины — в порядке убывания потерь: 1. **legacy-страницы не переразбирались.** Импорт старого harvest дал 2 633 страницы только с текстом поста (`.legacy.json`); `build` брал лишь пары `json+html`, поэтому их метки остались от старых регулярок и больше не обновлялись. 2. **Форматы строк, которых не понимали префиксы.** `[*]Перевод (1):`, `►Перевод:`, `Перевод №2:`, `Перевод 01-02:`, `Аудио RUS 1-3:`, `Озвучка:`, `Озвучивание:` (nnm/rutracker) не совпадали с `RT_RE_FIELD` / `NNM_RE_PEREVOD`, потому что те ждали ровно «Перевод:» или «Перевод 2:». 3. **Дорожки, названные только строкой «Аудио»** (nnm/rutracker, аниме-раздачи): `Аудио 4: AC3 … (внешние) (Русский, Anilibria)`, `| Rus | Звук: Zendos & Eladiel`, `Аудио RUS: … Озвучка: двухголосая (… )`. Студия была в скобках после языка или в хвосте «Звук:/Озвучка:», а разметка такие строки не читала: у nnm требовался «(MVO, Студия)», а язык — только «Русский/Английский/Украинский». 4. **Студия без типа озвучки** («Перевод: Dreamers Team», «Перевод: CrunchyRoll», «Озвучка: Persona99») выбрасывалась: `translation_type` давал «неизвестно», и строка отбрасывалась вместе с автором. Для головы автора это потеря: на странице автора назвали. 5. **Несколько студий в одной строке** («Профессиональный (многоголосый) — Jaskier, TVShows, HDrezka Studio, LostFilm», «| ОРТ, KidsCo»): в метки шла одна студия или склеенное «Jaskier, TVShows». 6. **Строка «Язык озвучки: Русский (Berofu, Takera, Euji [AniMaunt])»** (nnm, аниме) вообще не считалась дорожкой. 7. **kinozal читал только одну вкладку.** Если в «Релизе» была строка «Перевод:», «Техданные» игнорировались (и наоборот) — терялись типы или студии второй вкладки. 8. **Кодек-строки мимо правил сервиса** (`segmenter.RE_AUDIO_TRACK`, `release.RE_CODEC_TRACK`): «Аудио pic RUS:», «Аудио #1 Русский:», «Аудио N Rus:», «Звук N:», «Аудиодорожка 01:», «Audio #3:», кириллические «АVO/МVO». Это дыра не только в метках сегментатора, но и в запасном пути инференса. ## Что исправлено `tools/harvest/harvest_core.py` (`PARSE_VERSION` 4 → 6), `src/html_extract.py`, `src/release.py`, `src/segmenter.py`, `src/trackinfo.py`, `tools/collect.py`, `src/prepare_data.py`, `src/studio_norm.py`: * общий разбор префикса дорожки: декоративные `[*] ► •`, номер `(1)`, `№2`, диапазоны `01-02`, «Озвучка/Озвучивание/Дубляж» — для rutracker, nnm и kinozal; * `build` переразбирает legacy-страницы (метки обновляются, страница не перезаписывается); * nnm: студии из `(Русский, Anilibria)`, `| Rus | Звук: …`, язык `RUS/Rus/RU`; строка «Язык озвучки»; несколько студий в «Перевод:» → по строке на студию; студия без типа сохраняется с типом «неизвестно»; * rutracker: разбор `(Русский, Студия)` и `[студия]`, хвостов «Озвучка:»/«Звук:», списков студий; те же правила для «Перевод/Озвучка/Озвучивание»; * kinozal: обе вкладки, несколько студий в строке, «Отсутствует/Без перевода» не считаются автором; * кодек-строки: префиксы «Аудио #N Русский:», «Аудио pic RUS:», «Звук N:», «Аудиодорожка 01:», «Audio #3:», «Track #2:», «Title :»; тип-слова с кириллическими двойниками (АVO/МVO/DVO); если у релиза нет ни одной строки, из кодек-строки берётся дорожка без имени (тип без студии); * чистка имён: «Русский, - AlexFilm» → AlexFilm, «X по заказу Y» → X, «озвучил - N» → N; мусор (коды/каналы/языки/«по умолчанию»/«в составе контейнера»/«Отсутствует»/эпизоды) отбрасывается; в `prepare_data` строки-отказы от озвучки удаляются отдельным счётчиком. ## Результат | | было | стало | |---|---|---| | страниц в `data/raw` | 35 385 | 35 385 | | релизов в `labels.csv` | 28 155 | 30 401 | | строк в `labels.csv` | 56 131 | 71 339 | | страниц с метками | 28 014 | 30 260 (85.5 %) | | страниц без меток, но с дорожкой (по детектору) | 1 259 | 223 | | `data/pages` (страниц для сегментатора) | 28 198 | 30 444 | | строк после `prepare_data` | 46 894 | 60 944 (train 54 686 / test 6 258) | | строк со студией (спан найден) | 38 583 (82.3 %) | 50 477 (82.8 %) | | уникальных текстов | 25 614 | 30 761 | | дорожек 18+ | 1 006 | 1 156 | По трекерам (строки `labels.csv` → строки после подготовки, включая аугментацию): * kinozal: 21 773 → 23 969 (15 188 после подготовки); * nnm: 18 384 → 27 092 (24 453); * rutracker: 15 974 → 20 278 (21 303). Остаток 223 страниц — почти весь музыка/OST-раздачи, где «дорожкой» оказался трек-лист, плюс единичные нестандартные строки («Аудио 6: двухголосая закадровая Flowers Media» без кодек-параметров, «Аудио ShinkaDan: …», «Аудио #1: Russian … | Original | Censor»). Они перечислены в `data/raw/_coverage_lines.csv` и годятся для следующей итерации правил. Непокрытые строки на страницах **с** метками (12 908 из 56 921) — в основном не потерянные дорожки, а дубли: строка «Аудио N:» той же дорожки, что уже размечена из «Перевод:», дорожки оригинала/другого языка и техданные. Их доля видна в `reports/coverage_audit.md`. ## Как воспроизвести ```powershell # замер покрытия (кэш текстов ускоряет повтор) .venv\Scripts\python tools\coverage_audit.py --cache data\raw\_coverage.pkl # переиндексация: страницы + метки + meta (после правки парсеров поднять PARSE_VERSION) .venv\Scripts\python tools\collect.py build --rewrite --relabel # подготовка датасета и аудит .venv\Scripts\python -m src.prepare_data ``` Переиндексация всех 35 тыс. страниц одним процессом занимает ~30 мин (основное время — запись новых страниц и `meta.csv`); на нескольких процессах — в разы быстрее (`Runner(workers)`), но в песочнице DSH процессы с именованными каналами недоступны. ## Что это дало модели Переобучение на расширенном датасете (held-out test 6 258 строк, обе модели оценены на одном тесте): | модель | type acc | type macro-F1 | span exact | joint exact | |---|---|---|---|---| | прежняя `models/best` (02.11) | 0.949 | 0.880 | 0.643 | 0.615 | | новая `models/best` (09.11) | 0.976 | 0.906 | 0.674 | 0.658 | E2E по релизам (пары тип + студия, precision / recall; те же 400 страниц на трекер): прежняя модель — kinozal 0.752 / 0.830, rutracker 0.729 / 0.700, nnm 0.669 / 0.604; новая — 0.805 / 0.867, 0.771 / 0.740, 0.752 / 0.693. Абсолютный `span exact` на новом тесте ниже прежних 0.922 из-за того, что в тест попали форматы, которых раньше в датасете не было; сравнение «на одном тесте» показывает рост по всем головам.