Каталогизатор документов: разбор папки, тезисы, поиск, GUI

Программа принимает папку с документами, извлекает из них текст и
метаданные и строит каталог: краткое описание, тезисы и ключевые слова
для каждого файла плюс поиск по всему собранному.

Ядро:
- extract: PDF, DJVU, DOC(X), RTF, ODT, CHM, EPUB, FB2, PPT(X), XLS(X), TXT;
  PDF передаётся MuPDF потоком в память (работают длинные пути) и ограничен
  по времени — повреждённый файл иначе чинится минутами;
- summarize: экстрактивное реферирование по TF-IDF, посчитанному на самой
  библиотеке, с лёгким стеммером для русского и английского;
- db: SQLite с полнотекстовым индексом FTS5, морфологический поиск с BM25;
- scanner: инкрементальный многопоточный обход, счётчик попыток защищает
  от файла, обрывающего разбор;
- report: автономный HTML с поиском, Markdown, JSON, CSV.

Интерфейс:
- окно PySide6 в тёмной теме: вкладки разбора и поиска, фоновый поток,
  карточка документа, правка своих ключевых слов;
- командная строка: build, scan, analyze, report, search, show, tag, stats;
- у каждой папки свой каталог, последняя обработанная запоминается.

Сборка: scripts/build_exe.py даёт Catalogizer.exe в Windows и
Catalogizer.app в macOS, иконки .ico и .icns рисуются кодом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 19:20:29 +03:00
commit 49f91247c7
30 changed files with 3934 additions and 0 deletions

254
README.md Normal file
View File

@@ -0,0 +1,254 @@
# Каталогизатор документов
Программа принимает папку с документами, разбирает её и строит каталог:
для каждого файла — **краткое описание**, **тезисы** и **ключевые слова**,
плюс **поиск по каталогу** — в окне программы, в командной строке и в
автономной HTML-странице.
Всё работает локально: файлы только читаются, никаких обращений в сеть.
Программа работает в Windows, macOS и Linux — исходники одни и те же.
## Запуск
**Windows** — двойной щелчок по `CATALOG.bat` или `dist\Catalogizer.exe`.
**macOS** — двойной щелчок по `CATALOG.command` или `dist/Catalogizer.app`.
Ни Python, ни библиотек при этом не требуется.
**Из исходников** (любая система):
```bash
pip install -r requirements.txt
python catalog_gui.py
```
## Окно программы
Две вкладки:
* **Разбор папки** — поле пути и «Обзор…», параметры (потоков чтения, страниц
на документ, «перечитать всё заново»), кнопки «Построить каталог» и
«Остановить», индикатор хода работы и журнал. Разбор идёт в фоновом потоке,
окно не подвисает; остановка безопасна — разобранное уже в базе, следующий
запуск продолжит с того же места.
* **Каталог и поиск** — строка поиска, фильтры по формату, языку и разделу,
таблица найденного и карточка документа с описанием, тезисами, ключевыми
словами и фрагментом текста с подсветкой. Двойной щелчок открывает документ,
«Показать в папке» — проводник на нём.
Кнопки «Пересобрать отчёты» и «Открыть HTML-каталог» — на вкладке разбора.
## Командная строка
Папка — обычный аргумент:
```bash
python catalog.py build "E:\Yandex.Disk\литература"
```
| Команда | Что делает |
|---|---|
| `gui [ПАПКА]` | графическое окно (то же, что запуск без аргументов) |
| `scan ПАПКА` | обход папки, извлечение текста и метаданных в базу |
| `analyze` | ключевые слова, тезисы и описания по всей библиотеке |
| `report` | выгрузка `catalog.html`, `catalog.md`, `catalog.json`, `catalog.csv` |
| `build ПАПКА` | полный цикл: `scan` + `analyze` + `report` |
| `search ЗАПРОС` | поиск по каталогу |
| `show ID\|ИМЯ` | карточка одного документа |
| `tag ДОКУМЕНТ СЛОВА` | свои ключевые слова документа |
| `stats` | сводка: форматы, разделы, частые темы |
Последняя обработанная папка запоминается, поэтому дальше путь можно не
повторять: `python catalog.py search "инвертор"`. Обратиться к другому
каталогу — ключ `-c`: `python catalog.py -c "D:\books" stats`.
У каждой папки своя база (`catalogs/<имя>-<хеш>.db`) и своя папка отчётов
(`out/<имя>-<хеш>/`), каталоги разных папок не смешиваются.
Полезные ключи `scan`/`build`:
```bash
python catalog.py scan --ext pdf djvu # только выбранные форматы
python catalog.py scan --max-pages 40 # читать больше страниц из каждого файла
python catalog.py scan --jobs 8 # потоков чтения: HDD 2-4, SSD 8-16
python catalog.py scan --limit 50 # пробный прогон на 50 файлах
python catalog.py scan --force # перечитать всё заново
python catalog.py scan --retry-broken # снова попробовать «битые» файлы
```
## Поиск
```bash
python catalog.py search "инвертор"
python catalog.py search "цифровая обработка сигналов" -n 30
python catalog.py search stm32 dma --ext pdf
python catalog.py search "операционный усилитель" --lang ru --year-from 2010
python catalog.py search "\"частотное преобразование\"" # точная фраза
python catalog.py search fpga -verilog # минус — исключить слово
python catalog.py search can --section datasheet # только в разделе
```
Поиск морфологический: слова приводятся к основе, поэтому «инвертор» находит
«инверторы» и «инвертора». Ранжирование — BM25 с повышенным весом названия и
ключевых слов; в выдаче показывается фрагмент текста с подсветкой.
## Свои ключевые слова
Кроме слов, вычисленных из текста, к любому документу можно добавить свои —
рубрику, проект, пометку «нужное». Они полноправно участвуют в поиске и даже
весят в нём больше вычисленных.
В окне: выбрать документ, вписать слова через запятую в строку «Свои
ключевые слова» под карточкой и нажать «Сохранить» (или Enter, или Ctrl+S).
При вводе подсказываются слова, уже заведённые в этом каталоге.
В командной строке:
```bash
python catalog.py tag opamp "силовая электроника, к проекту А" # добавить
python catalog.py tag 137 --remove "к проекту А" # убрать
python catalog.py tag 137 --set "только это" # заменить список
python catalog.py tag 137 --set # очистить
python catalog.py tag 137 # показать
python catalog.py tag --all # все слова каталога
python catalog.py search "к проекту А" # найти помеченные
```
Документ указывается номером `id` или частью имени, пути либо названия.
Слова хранятся отдельно от результатов разбора и привязаны к пути файла,
поэтому переживают повторный разбор папки и перестроение каталога. В отчёты
они попадают отдельной строкой («Свои ключевые слова»), в HTML-каталоге
выделены цветом, в CSV — отдельной колонкой.
## Отчёты
После `report` в папке `out/`:
* **`catalog.html`** — автономная страница: поиск по мере ввода, фильтры,
тезисы и ключевые слова у каждого документа, ссылки на файлы. Открывается
двойным щелчком, без сервера;
* `catalog.md` — каталог по разделам для чтения и печати;
* `catalog.json`, `catalog.csv` — выгрузка для других программ (CSV с BOM,
открывается в Excel).
## Как строятся описания и тезисы
Реферирование экстрактивное — фразы берутся из самого документа, ничего не
досочиняется:
1. из файла читаются первые `--max-pages` страниц, оглавление и метаданные;
2. слова приводятся к основе (лёгкий стеммер для русского и английского),
считаются частоты; слова из названия и оглавления получают больший вес;
3. по всей библиотеке считается IDF — так отсеиваются слова, общие для всей
техлитературы, и остаются характерные именно для этого документа;
4. **ключевые слова** — верхние основы по TF-IDF, показанные самой короткой
частой словоформой;
5. **тезисы** — предложения с наибольшей плотностью ключевых слов, с бонусом
началу документа (аннотация, введение) и оборотам вида «рассматривается»,
«предназначено», `this book describes`; близкие по составу фразы отбрасываются;
6. **описание** — тип издания, язык, объём, темы и первый тезис.
Для сканов без текстового слоя тезисы берутся из оглавления PDF, а если его
нет — документ попадает в каталог с пометкой «скан» и описанием по имени файла.
Найти такие файлы: `python catalog.py stats`.
## Форматы и внешние утилиты
Разбираются PDF, DJVU, DOC, DOCX, RTF, ODT, CHM, EPUB, FB2, PPT, PPTX, XLS,
XLSX, TXT, MD. Обязательна только `pymupdf` (PDF); дополнительно, если есть
в PATH:
* `antiword` — качественный разбор старых `.doc` (иначе грубая выборка строк);
* `djvutxt` из DjVuLibre — текстовый слой `.djvu` (иначе для DJVU остаются
только название, число страниц и раздел).
## Сборка исполняемого файла
Сборщик один — `scripts/build_exe.py`; что получится, зависит от системы,
**на которой идёт сборка**: PyInstaller не умеет собирать под чужую
платформу. Программу для macOS собирают на Mac, для Windows — на Windows.
**Windows** → `dist\Catalogizer.exe`:
```bash
BUILD_EXE.bat --setup # один раз: .venv с PySide6, pymupdf, PyInstaller
BUILD_EXE.bat # сборка
```
**macOS** → `dist/Catalogizer.app`:
```bash
chmod +x BUILD_APP.command CATALOG.command # один раз, после переноса файлов
./BUILD_APP.command --setup # один раз: .venv и зависимости
./BUILD_APP.command # сборка
```
Оба сценария можно просто запустить двойным щелчком. Ключи передаются в
`scripts/build_exe.py`: `--onedir` (папка вместо одного файла, запускается
быстрее; в macOS так всегда — `.app` и есть папка), `--console` (оставить
консоль для traceback), `--cli` (собрать ещё и консольную программу
`catalog`). После сборки автоматически выполняется самопроверка `--check`:
окно строится целиком и программа выходит.
Иконки генерируются кодом — `python scripts/make_icon.py` создаёт
`ico/catalog.ico` для Windows и `ico/catalog.icns` для macOS.
Куда программа кладёт базы и отчёты: в Windows и Linux — **рядом с
исполняемым файлом**, в macOS — в `~/Library/Application Support/Catalogizer`,
потому что содержимое `.app` считается неизменяемым.
### Что учтено для macOS
* пути и `\?\`-префикс применяются только в Windows;
* «Показать в папке» открывает Finder на файле (`open -R`), «Открыть
документ» — `open`;
* ссылки на файлы в HTML-каталоге строятся из абсолютного пути и работают
и с `/Users/...`, и с `E:\...`;
* в теме заданы системные шрифты (`SF Pro Text`, `SF Mono`) как замена
windows-овским;
* папка по умолчанию — `~/Documents`, если жёстко прописанной библиотеки нет;
* `.command`-файлы сохранены с переводами строк LF, иначе bash их не примет.
Первый запуск неподписанного `.app` macOS блокирует: правой кнопкой по
`Catalogizer.app` → «Открыть» → «Открыть» в диалоге. Либо снять карантин:
`xattr -dr com.apple.quarantine dist/Catalogizer.app`.
## Устройство
```
CATALOG.bat запуск окна в Windows
CATALOG.command запуск окна в macOS
BUILD_EXE.bat сборка exe в Windows
BUILD_APP.command сборка .app в macOS
catalog_gui.py точка входа окна (её упаковывает PyInstaller)
catalog.py точка входа командной строки
catalogizer/
app.py сборка QApplication, ключ --check
config.py папка по умолчанию, форматы, ограничения разбора
extract.py чтение PDF, DJVU, DOC(X), RTF, CHM, EPUB, FB2, XLS(X), TXT
textutil.py язык, токенизация, стемминг, разбиение на предложения
summarize.py TF-IDF, ключевые слова, тезисы, описание
scanner.py обход папки, многопоточное извлечение
analyze.py проход анализа по всей базе
search.py запросы к полнотекстовому индексу
report.py HTML, Markdown, JSON, CSV
db.py SQLite + FTS5
ui/ тёмная тема, главное окно, вкладки, фоновый поток
scripts/build_exe.py сборка exe/.app — под ту систему, где запущен
scripts/make_icon.py генерация иконок .ico и .icns
```
## Замечания по работе
* Файлы длиннее 260 символов пути читаются через префикс `\\?\`; PDF
передаются MuPDF потоком в память, поэтому нестандартные пути не мешают.
* Кодировка русских `.txt` определяется перебором (utf-8, cp1251, koi8-r, cp866).
* Разбор одного PDF ограничен по времени (`PDF_TIME_BUDGET`, 25 с):
повреждённый файл MuPDF чинит страницу за страницей и может занять минуты.
* Сбой на файле не останавливает обход: причина сохраняется в поле `error`
и видна в `stats`. Файл, трижды оборвавший разбор, дальше пропускается —
вернуть его в очередь можно ключом `--retry-broken`.
* Если папка лежит под управлением клиента синхронизации (Яндекс.Диск,
OneDrive), его драйвер может надолго придерживать открытые файлы. На время
первого разбора синхронизацию лучше приостановить.