244 lines
9.2 KiB
Python
244 lines
9.2 KiB
Python
"""
|
||
Обработка документов различных форматов
|
||
Поддержка PDF, DOCX, текстовых файлов с обработкой больших файлов (>100MB)
|
||
"""
|
||
import logging
|
||
import io
|
||
from typing import Optional, Tuple
|
||
from pathlib import Path
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class DocumentProcessor:
|
||
"""Обработчик документов различных форматов"""
|
||
|
||
def __init__(self, max_file_size: int = 100 * 1024 * 1024):
|
||
"""
|
||
Инициализация процессора документов
|
||
|
||
Args:
|
||
max_file_size: Максимальный размер файла для прямой обработки (байты)
|
||
"""
|
||
self.max_file_size = max_file_size
|
||
|
||
def process_file(
|
||
self,
|
||
file_content: bytes,
|
||
filename: str,
|
||
mime_type: Optional[str] = None
|
||
) -> Tuple[str, bool]:
|
||
"""
|
||
Обработка файла и извлечение текста
|
||
|
||
Args:
|
||
file_content: Содержимое файла
|
||
filename: Имя файла (для определения типа)
|
||
mime_type: MIME тип файла (опционально)
|
||
|
||
Returns:
|
||
Кортеж (текст, is_large_file) где is_large_file указывает,
|
||
что файл был обработан потоково из-за большого размера
|
||
"""
|
||
file_size = len(file_content)
|
||
is_large_file = file_size > self.max_file_size
|
||
|
||
# Определение типа файла
|
||
file_ext = Path(filename).suffix.lower()
|
||
|
||
try:
|
||
if file_ext == '.pdf' or mime_type == 'application/pdf':
|
||
return self._process_pdf(file_content, is_large_file), is_large_file
|
||
|
||
elif file_ext in ['.docx', '.doc'] or mime_type in ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/msword']:
|
||
return self._process_docx(file_content), False
|
||
|
||
elif file_ext in ['.txt', '.md', '.markdown'] or mime_type in ['text/plain', 'text/markdown']:
|
||
return self._process_text(file_content), False
|
||
|
||
elif file_ext == '.csv' or mime_type == 'text/csv':
|
||
return self._process_csv(file_content), False
|
||
|
||
else:
|
||
logger.warning(f"Неподдерживаемый формат файла: {filename} (тип: {mime_type})")
|
||
# Пытаемся обработать как текст
|
||
try:
|
||
return self._process_text(file_content), False
|
||
except Exception:
|
||
raise ValueError(f"Не удалось обработать файл {filename}: неподдерживаемый формат")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при обработке файла {filename}: {e}")
|
||
raise
|
||
|
||
def _process_pdf(self, content: bytes, is_large: bool) -> str:
|
||
"""
|
||
Обработка PDF файла
|
||
|
||
Args:
|
||
content: Содержимое PDF
|
||
is_large: Флаг большого файла (для потоковой обработки)
|
||
|
||
Returns:
|
||
Извлеченный текст
|
||
"""
|
||
try:
|
||
import pypdf
|
||
|
||
pdf_file = io.BytesIO(content)
|
||
pdf_reader = pypdf.PdfReader(pdf_file)
|
||
|
||
text_parts = []
|
||
total_pages = len(pdf_reader.pages)
|
||
|
||
logger.info(f"Обработка PDF: {total_pages} страниц")
|
||
|
||
# Для больших файлов обрабатываем страницы порциями
|
||
if is_large:
|
||
# Ограничиваем количество страниц для очень больших файлов
|
||
max_pages = min(total_pages, 1000) # Максимум 1000 страниц
|
||
logger.warning(f"Большой PDF файл. Обрабатываются первые {max_pages} из {total_pages} страниц")
|
||
else:
|
||
max_pages = total_pages
|
||
|
||
for page_num in range(max_pages):
|
||
try:
|
||
page = pdf_reader.pages[page_num]
|
||
text = page.extract_text()
|
||
if text.strip():
|
||
text_parts.append(f"--- Страница {page_num + 1} ---\n{text}")
|
||
except Exception as e:
|
||
logger.warning(f"Ошибка при обработке страницы {page_num + 1}: {e}")
|
||
continue
|
||
|
||
result = "\n\n".join(text_parts)
|
||
|
||
if not result.strip():
|
||
raise ValueError("Не удалось извлечь текст из PDF")
|
||
|
||
return result
|
||
|
||
except ImportError:
|
||
raise ImportError(
|
||
"Библиотека pypdf не установлена. Установите: pip install pypdf"
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при обработке PDF: {e}")
|
||
raise
|
||
|
||
def _process_docx(self, content: bytes) -> str:
|
||
"""
|
||
Обработка DOCX файла
|
||
|
||
Args:
|
||
content: Содержимое DOCX
|
||
|
||
Returns:
|
||
Извлеченный текст
|
||
"""
|
||
try:
|
||
import docx
|
||
|
||
doc_file = io.BytesIO(content)
|
||
doc = docx.Document(doc_file)
|
||
|
||
text_parts = []
|
||
|
||
# Извлечение текста из параграфов
|
||
for paragraph in doc.paragraphs:
|
||
if paragraph.text.strip():
|
||
text_parts.append(paragraph.text)
|
||
|
||
# Извлечение текста из таблиц
|
||
for table in doc.tables:
|
||
for row in table.rows:
|
||
row_text = " | ".join(cell.text.strip() for cell in row.cells)
|
||
if row_text.strip():
|
||
text_parts.append(row_text)
|
||
|
||
result = "\n\n".join(text_parts)
|
||
|
||
if not result.strip():
|
||
raise ValueError("Не удалось извлечь текст из DOCX")
|
||
|
||
return result
|
||
|
||
except ImportError:
|
||
raise ImportError(
|
||
"Библиотека python-docx не установлена. Установите: pip install python-docx"
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при обработке DOCX: {e}")
|
||
raise
|
||
|
||
def _process_text(self, content: bytes) -> str:
|
||
"""
|
||
Обработка текстового файла
|
||
|
||
Args:
|
||
content: Содержимое файла
|
||
|
||
Returns:
|
||
Текст с правильной кодировкой
|
||
"""
|
||
# Попытка различных кодировок
|
||
encodings = ['utf-8', 'utf-8-sig', 'cp1251', 'latin-1']
|
||
|
||
for encoding in encodings:
|
||
try:
|
||
return content.decode(encoding)
|
||
except UnicodeDecodeError:
|
||
continue
|
||
|
||
# Если ничего не помогло, используем errors='replace'
|
||
return content.decode('utf-8', errors='replace')
|
||
|
||
def _process_csv(self, content: bytes) -> str:
|
||
"""
|
||
Обработка CSV файла (конвертация в читаемый текст)
|
||
|
||
Args:
|
||
content: Содержимое CSV
|
||
|
||
Returns:
|
||
Текстовая версия CSV
|
||
"""
|
||
import csv
|
||
|
||
text_content = self._process_text(content)
|
||
csv_reader = csv.reader(io.StringIO(text_content))
|
||
|
||
rows = []
|
||
for row_num, row in enumerate(csv_reader, 1):
|
||
rows.append(f"Строка {row_num}: {' | '.join(row)}")
|
||
|
||
return "\n".join(rows)
|
||
|
||
def is_supported_format(self, filename: str, mime_type: Optional[str] = None) -> bool:
|
||
"""
|
||
Проверка поддержки формата файла
|
||
|
||
Args:
|
||
filename: Имя файла
|
||
mime_type: MIME тип
|
||
|
||
Returns:
|
||
True если формат поддерживается
|
||
"""
|
||
file_ext = Path(filename).suffix.lower()
|
||
supported_extensions = {'.pdf', '.docx', '.doc', '.txt', '.md', '.markdown', '.csv'}
|
||
|
||
if file_ext in supported_extensions:
|
||
return True
|
||
|
||
supported_mimes = {
|
||
'application/pdf',
|
||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
'application/msword',
|
||
'text/plain',
|
||
'text/markdown',
|
||
'text/csv'
|
||
}
|
||
|
||
return mime_type in supported_mimes if mime_type else False
|