У меня скопилась большая коллекция технических книг в PDF и DJVU. Хотелось читать их прямо на сайте и делиться с другими. Решил автоматизировать процесс: конвертировать документы в Markdown, обогащать метаданные через поиск и публиковать в новом разделе блога.
Записываю всё как runbook — чтобы в следующий раз не вспоминать и сразу делать.
🗺 Обзор: что получилось #
Раздел /reading на сайте с карточками книг: название, автор, статус чтения, рейтинг, теги. При открытии книги — полный текст с оглавлением. Скрипты для пакетного и одиночного импорта.
Стек: Hugo, Python, MarkItDown, Open Library API.
Часть 1. Структура Hugo-раздела #
Что создаётся #
content/reading/ # контент книг
_index.md # лендинг раздела
pro-git.md # каждая книга — отдельный .md
layouts/reading/
list.html # карточки книг с сеткой
single.html # страница одной книги
archetypes/reading.md # шаблон для hugo new
assets/css/reading.css # стили карточекFront matter книги #
---
title: "Pro Git"
author: "Скотт Чакон, Бен Штрауб"
date: 2026-04-10T12:00:00+03:00
draft: false
summary: "Полное руководство по Git от авторов системы."
description: "Полное руководство по Git от авторов системы."
tags: ["git", "контроль версий", "разработка"]
toc: true
readTime: false
status: "finished" # want-to-read | reading | finished
rating: 0 # 1-5, 0 = без оценки
source: "Pro Git.pdf" # исходный файл
---Поля status и rating управляют отображением карточки. Раздел читает их через .Params.status и .Params.rating в шаблоне.
Добавить раздел в меню #
В hugo.toml:
[[params.menu]]
name = "Чтение"
url = "/reading"Часть 2. Установка MarkItDown #
MarkItDown — инструмент Microsoft для конвертации документов в Markdown. Поддерживает PDF, DOCX, EPUB, PPTX, XLSX, HTML и другие форматы.
Установка в изолированный venv #
Важно: не устанавливать глобально, чтобы не конфликтовать с системным Python macOS.
# Создаём venv рядом со скриптами
python3 -m venv scripts/.venv
# Устанавливаем с поддержкой PDF
scripts/.venv/bin/pip install markitdown unidecode
scripts/.venv/bin/pip install "markitdown[pdf]"Скрипты автоматически подхватывают этот venv через bootstrap-строки в начале файла:
import sys, os
_venv = os.path.join(os.path.dirname(__file__), ".venv", "bin", "python3")
if os.path.exists(_venv) and sys.executable != _venv:
os.execv(_venv, [_venv] + sys.argv)Теперь python3 scripts/import-book.py работает без ручной активации venv.
.gitignore
#
Books/ # не коммитим большие PDF
scripts/.venv/ # не коммитим virtualenvЧасть 3. Одиночный импорт: import-book.py
#
Интерактивный скрипт для добавления одной книги.
Использование #
python3 scripts/import-book.py ~/путь/к/книге.pdfЧто происходит #
- Конвертирует файл через MarkItDown → получает Markdown
- Пытается найти в тексте заголовок (
# ...) и автора (Автор: ...) - Показывает найденное и предлагает подтвердить или исправить
- Запрашивает статус (читаю / прочитал / в очереди), рейтинг, теги, описание
- Генерирует slug через транслитерацию (
unidecode) - Создаёт
content/reading/<slug>.md
Пример сессии #
⏳ Конвертирую Pro Git.pdf... ✅ 947,557 символов
📝 Введи метаданные книги:
Название [Pro Git]: Pro Git
Автор [Scott Chacon]: Скотт Чакон, Бен Штрауб
Статус:
1) 📖 Читаю
2) ✅ Прочитал
3) 🕐 В очереди
Выбор [3]: 2
Рейтинг (0–5) [0]: 5
Теги: git, контроль версий, разработка
Краткое описание: Официальная книга по Git, доступна бесплатно
Черновик? (y/n) [n]: n
✅ Создан файл: content/reading/pro-git.mdЧасть 4. Пакетный импорт: batch-import-books.py
#
Для массового добавления папки с книгами.
Использование #
python3 scripts/batch-import-books.py Books/
# или с указанием статуса:
python3 scripts/batch-import-books.py Books/ --status finished
# проверить без записи:
python3 scripts/batch-import-books.py Books/ --dry-runЧто происходит для каждого файла #
- Конвертация через MarkItDown
- Поиск метаданных в Open Library API (бесплатно, без ключа):
GET https://openlibrary.org/search.json?q=<название>&limit=1 - Из ответа берёт: автора, год, теги (subjects)
- Создаёт
content/reading/<slug>.md
⚠️ Проблема: неверный заголовок #
MarkItDown конвертирует книгу как есть. Первый # заголовок в PDF — это часто не название книги, а команда или раздел из тела. Например:
- OpenStack-книга →
# yum -y update - PostgreSQL →
# book_ref - Командная строка →
# cd /usr/bin
Решение: не доверять автовыбору, всегда проверять. При пакетном импорте — запускать fix-reading-metadata.py с таблицей правильных данных, либо использовать интерактивный import-book.py для каждой книги.
Часть 5. Агентский поиск метаданных 🤖 #
Автоматический поиск через Open Library хорошо работает для известных книг. Для надёжности — дополнительно использовать AI-агента.
Промпт для агента (ИИ-ассистент с веб-поиском):
Найди метаданные для этих книг. Для каждой верни:
- Точное название
- Авторы
- Год первого издания
- 3-5 тегов на русском
- 1-2 предложения описания на русском
Список книг:
1. "Linux на практике" Бреснахэн/Блум (2017)
2. "BPF для мониторинга Linux"
...Агент находит корректные данные даже для книг с плохими метаданными в PDF.
Часть 6. Постобработка: очистка HTML #
Конвертированные PDF содержат встроенный HTML (теги, JavaScript из интерактивных PDF). Hugo минификатор ломается на них.
Симптом:
ERROR: unexpected = in expression on line 4526
failed to render pages: failed to process "/reading/book/index.html"Решение — очистка перед коммитом:
import re
def strip_html(text):
text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.DOTALL|re.IGNORECASE)
text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL|re.IGNORECASE)
# убираем теги кроме базовых markdown-safe
safe = r'br|hr|em|strong|code|pre|b|i|u|s|sub|sup|table|thead|tbody|tr|td|th|ul|ol|li|p|h[1-6]|blockquote|a|img'
text = re.sub(rf'<(?!(?:{safe})[/ >])[^>]+>', '', text)
return textЗапускать после конвертации и перед hugo --minify. Для моих 11 книг убрало от 800 до 370 000 символов лишнего HTML.
Часть 7. Особые случаи #
Отсканированные PDF #
Если PDF создан сканером (картинки страниц), MarkItDown конвертирует в пустую строку — нет извлекаемого текста.
Определение: len(text_content) == 0 при ненулевом размере файла.
Варианты действий:
- OCR через
pytesseract(требует установки Tesseract):brew install tesseract tesseract-lang pip install pytesseract pillow pdf2image - Создать заглушку с описанием вручную и отметить в
summary - Оставить без текста, только front matter с метаданными
DJVU #
MarkItDown не поддерживает DJVU. Варианты:
- Конвертировать в PDF:
brew install djvu2pdf - Или создать заглушку, указав в
summaryссылку на онлайн-версию (если книга открытая)
Очень большие книги #
Pro Git — 947 000 символов, Командная строка — 943 000. Hugo собирает их без проблем. TOC автоматически генерируется из заголовков. Время сборки растёт, но незначительно.
Часть 8. Полный runbook: шаг за шагом #
Первый запуск (один раз) #
# 1. Создать venv
python3 -m venv scripts/.venv
scripts/.venv/bin/pip install markitdown unidecode "markitdown[pdf]"
# 2. Создать структуру раздела (уже в репозитории)
# content/reading/, layouts/reading/, assets/css/reading.css
# 3. Добавить в .gitignore
echo "Books/" >> .gitignore
echo "scripts/.venv/" >> .gitignoreДобавление новой книги (каждый раз) #
# 1. Положить файл в Books/
cp ~/Downloads/новая-книга.pdf Books/
# 2. Импортировать интерактивно
python3 scripts/import-book.py Books/новая-книга.pdf
# → ввести название, автора, статус, рейтинг, теги
# 3. Проверить результат
cat content/reading/<slug>.md | head -20
hugo server # открыть /reading локально
# 4. Закоммитить и задеплоить
git add content/reading/<slug>.md
git commit -m "Добавил книгу: Название книги"
git pushПакетное добавление папки книг #
# 1. Пробный прогон
python3 scripts/batch-import-books.py Books/ --dry-run
# 2. Реальный импорт
python3 scripts/batch-import-books.py Books/ --status finished
# 3. Очистка HTML в конвертированных файлах
python3 -c "
import re
from pathlib import Path
def strip_html(t):
t = re.sub(r'<script.*?</script>', '', t, flags=re.DOTALL|re.I)
t = re.sub(r'<style.*?</style>', '', t, flags=re.DOTALL|re.I)
safe = r'br|hr|em|strong|code|pre|b|i|table|thead|tbody|tr|td|th|ul|ol|li|p|h[1-6]|a|img'
t = re.sub(rf'<(?!(?:{safe})[/ >])[^>]+>', '', t)
return t
for f in Path('content/reading').glob('*.md'):
parts = f.read_text().split('---', 2)
if len(parts)==3:
clean = strip_html(parts[2])
f.write_text('---' + parts[1] + '---' + clean)
print(f.name)
"
# 4. Проверить сборку
hugo --minify
# 5. Вручную исправить метаданные (если нужно)
# открыть файлы с неверными заголовками и поправить title, author
# 6. Закоммитить
git add content/reading/
git commit -m "Добавил X книг в раздел Чтение"
git pushИтог и наблюдения #
За один прогон опубликовал 13 книг. Что работает хорошо:
- MarkItDown отлично справляется с текстовыми PDF: извлекает не только текст, но и структуру (заголовки глав, списки, таблицы)
- Open Library API находит метаданные для известных книг, но для русских переводов иногда возвращает англоязычный оригинал
- Агентский поиск надёжнее — ИИ находит правильные данные даже по транслитерированным именам файлов
Что требует внимания:
- Первый заголовок ≠ название книги — PDFs часто начинаются с команды или технического термина в теле
- Отсканированные PDF не конвертируются без OCR
- DJVU не поддерживается напрямую
- Встроенный HTML в конвертированных файлах ломает Hugo minifier — нужна очистка
Раздел «Чтение» живёт по адресу kushnaren.co/reading. Буду пополнять по мере чтения.