Files
anti-plagiarism/services/worker-indexer/app/extractors/pdf.py
jze9 df2dfcc456
All checks were successful
Deploy / test (push) Successful in 2m41s
Deploy / deploy (push) Successful in 11s
fix(indexer): понятная ошибка, если загруженный "PDF" на самом деле не PDF
Живой репорт: проверка плагиата упала с "Не удалось открыть PDF: code=7: no
objects found" — сырая внутренняя ошибка MuPDF. Разобрал файл из MinIO: это
HTML-страница (веб-интерфейс роутера D-Link DAP-400P), сохранённая с
расширением .pdf — 2049 байт, не PDF вообще. Система корректно отказалась
парсить мусор (это не баг пайплайна — реальный битый/не-PDF файл юзера), но
сообщение об ошибке было нечитаемым.

extract_text_from_pdf теперь проверяет magic-байты (%PDF-) ДО попытки
fitz.open() и даёт понятное "Файл повреждён или не является PDF-документом"
для этого частого случая; для настоящих PDF с внутренней порчей — прежнее
поведение (сырая ошибка MuPDF как detail, для диагностики).

4 юнит-теста (валидный PDF, HTML под видом PDF, пустые байты, битый заголовок
PDF). Тестов всего: 122 (было 118).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-24 18:55:24 +05:00

87 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Извлечение текста из PDF файлов через PyMuPDF."""
import logging
logger = logging.getLogger(__name__)
def extract_text_from_pdf(data: bytes) -> str:
"""
Извлечь текст из PDF файла.
Обрабатывает многостраничные документы.
Удаляет лишние переносы строк и пробелы.
Args:
data: Байты PDF файла
Returns:
Извлечённый текст
Raises:
ValueError: Если не удалось открыть PDF
"""
import fitz # PyMuPDF
# Частый случай: файл с расширением .pdf, но внутри — HTML/другое (например,
# сохранённая веб-страница). Даём понятное сообщение, а не сырую ошибку MuPDF
# вида "code=7: no objects found", которая ничего не скажет пользователю.
if not data.startswith(b"%PDF-"):
raise ValueError("Файл повреждён или не является PDF-документом")
try:
doc = fitz.open(stream=data, filetype="pdf")
except Exception as e:
raise ValueError(f"Не удалось открыть PDF: {e}") from e
texts: list[str] = []
for page_num, page in enumerate(doc):
try:
page_text = page.get_text("text")
if page_text.strip():
texts.append(page_text)
except Exception as e:
logger.warning(f"Ошибка извлечения текста со страницы {page_num}: {e}")
continue
doc.close()
full_text = "\n".join(texts)
# Нормализация: убрать множественные переносы строк
import re
full_text = re.sub(r"\n{3,}", "\n\n", full_text)
full_text = re.sub(r"[ \t]+", " ", full_text)
return full_text.strip()
def extract_metadata_from_pdf(data: bytes) -> dict:
"""
Извлечь метаданные из PDF (title, author, subject и т.д.).
Args:
data: Байты PDF файла
Returns:
Словарь метаданных
"""
import fitz
try:
doc = fitz.open(stream=data, filetype="pdf")
metadata = doc.metadata or {}
doc.close()
return {
"title": metadata.get("title", ""),
"author": metadata.get("author", ""),
"subject": metadata.get("subject", ""),
"keywords": metadata.get("keywords", ""),
"creator": metadata.get("creator", ""),
"pages": doc.page_count if hasattr(doc, "page_count") else 0,
}
except Exception as e:
logger.warning(f"Ошибка извлечения метаданных PDF: {e}")
return {}