diff --git a/rag/utils/web_utils.py b/rag/utils/web_utils.py index 48b1e2d..734b977 100644 --- a/rag/utils/web_utils.py +++ b/rag/utils/web_utils.py @@ -2,15 +2,7 @@ """ Утилиты для загрузки веб-страниц и PDF, извлечения основного текста, рекурсивного обхода ссылок. - -Использует aiohttp для асинхронных запросов, BeautifulSoup для парсинга, -readability-lxml для извлечения контента (опционально). - -Параметры (max_pages, max_depth) берутся из конфига при вызове из xmpp_client. - -Импортируется в: -- indexing_worker.py (fetch_any_url, crawl_url) -- возможно, в других функциях при обработке URL. +Добавлено ограничение размера загружаемого контента. """ import asyncio @@ -35,24 +27,20 @@ except ImportError: logger = logging.getLogger(__name__) -async def fetch_any_url(url: str, timeout: int = 30) -> Tuple[Optional[str], Optional[str]]: +async def fetch_any_url(url: str, timeout: int = 30, max_size_mb: int = 5) -> Tuple[Optional[str], Optional[str]]: """ - Универсальная загрузка контента по URL. + Универсальная загрузка контента по URL с ограничением размера. Аргументы: - url (str): целевой адрес. - timeout (int): таймаут запроса в секундах. + url: целевой адрес. + timeout: таймаут запроса в секундах. + max_size_mb: максимальный размер загружаемого контента в МБ. Возвращает: Tuple[Optional[str], Optional[str]]: (заголовок, текст). Для HTML: заголовок страницы и очищенный основной текст. Для PDF: имя файла (базовая часть URL) и извлечённый текст. В случае ошибки возвращает (None, None). - - Примечания: - - Для PDF используется временный файл, который удаляется после чтения. - - Поддерживаются только HTTP/HTTPS. - - Таймаут применяется к запросу в целом (чтение + соединение). """ try: async with aiohttp.ClientSession() as session: @@ -60,14 +48,22 @@ async def fetch_any_url(url: str, timeout: int = 30) -> Tuple[Optional[str], Opt if resp.status != 200: logger.error(f"HTTP {resp.status} для {url}") return None, None + content_type = resp.headers.get('Content-Type', '').lower() + limit = max_size_mb * 1024 * 1024 + + try: + data = await resp.content.read(limit=limit) + except aiohttp.ContentTooLong: + logger.error(f"Содержимое {url} превышает лимит {max_size_mb} МБ") + return None, None + # Обработка PDF if 'application/pdf' in content_type or url.lower().endswith('.pdf'): - # Сохраняем PDF во временный файл with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp: tmp_path = tmp.name async with aiofiles.open(tmp_path, 'wb') as f: - await f.write(await resp.read()) + await f.write(data) try: from pypdf import PdfReader reader = PdfReader(tmp_path) @@ -81,7 +77,7 @@ async def fetch_any_url(url: str, timeout: int = 30) -> Tuple[Optional[str], Opt os.unlink(tmp_path) else: # Обычная HTML-страница - html = await resp.text() + html = data.decode('utf-8', errors='ignore') title, text = extract_text_from_html(html, url) return title, text except asyncio.TimeoutError: @@ -93,28 +89,8 @@ async def fetch_any_url(url: str, timeout: int = 30) -> Tuple[Optional[str], Opt def extract_text_from_html(html: str, url: str = "") -> Tuple[Optional[str], str]: - """ - Извлекает заголовок и основной текст из HTML. - - Аргументы: - html (str): исходный HTML-код. - url (str): URL страницы (используется для формирования заголовка, если теги отсутствуют). - - Возвращает: - Tuple[Optional[str], str]: (заголовок, очищенный текст). - Заголовок может быть None, если не удалось извлечь. - - Алгоритм извлечения заголовка (по приоритету): - 1.