сделал ревью системы
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-04-28 22:13:47 +10:00
parent c564140428
commit 25f2c09064
18 changed files with 1169 additions and 712 deletions

20
services/__init__.py Normal file
View File

@@ -0,0 +1,20 @@
"""
Сервисы приложения
"""
from .proxy_manager import (
download_proxies,
get_shuffled_proxies,
fetch_with_proxy,
fetch_with_proxy_retry
)
from .gpt_client import gpt_response_message
from .document_builder import update_bd_and_create_document
__all__ = [
'download_proxies',
'get_shuffled_proxies',
'fetch_with_proxy',
'fetch_with_proxy_retry',
'gpt_response_message',
'update_bd_and_create_document'
]

View File

@@ -0,0 +1,78 @@
"""
Document Builder - создание JSON и DOCX файлов
"""
import json
import os
from docx import Document
from config import DOCUMENTS_DIR
from utils import logger
import parser_bd as pbd
def update_bd_and_create_document(
response_text: str,
article_date: str,
url: str,
parsed_at: str,
original_text: str,
other: str
) -> None:
"""
Обрабатывает ответ от GPT, сохраняет в БД и создаёт DOCX документ
"""
clean_response = ''
if not response_text:
print(f"Пустой ответ от GPT для URL: {url}")
logger.info(f"Пустой ответ от GPT для URL: {url}")
return
try:
clean_response = response_text.strip().replace('```json', '').replace('```', '').strip()
data = json.loads(clean_response)
if data['category']:
data['article_date'] = article_date
data['url'] = url
data['parsed_at'] = parsed_at
data['original_text'] = original_text
data['status'] = False
data['viewed'] = False
data['other'] = other
# Сохранение в БД через pbd
parsed_data = pbd.ParsedData(**data)
pbd.save_parsed_data_to_db(parsed_data)
print("Данные успешно сохранены в БД")
# Создание DOCX документа
path_day = article_date.split()[0]
documents_path = os.path.join(DOCUMENTS_DIR, path_day)
if not os.path.exists(documents_path):
os.makedirs(documents_path)
print(f"Создана папка: {documents_path}")
doc = Document()
doc.add_heading('Ссылка на статью', level=1)
doc.add_paragraph(other)
doc.add_heading('Дата и время', level=1)
doc.add_paragraph(article_date)
doc.add_heading('Обнаруженные тематики текста', level=1)
doc.add_paragraph(data["category"])
doc.add_heading('Заголовок', level=1)
doc.add_paragraph(data["title"])
doc.add_heading('Краткий пересказ', level=1)
doc.add_paragraph(data["short_text"])
doc.add_heading('Переведенный текст статьи в газете', level=1)
doc.add_paragraph(data["translation_text"])
doc.add_heading('Оригинальный текст', level=1)
doc.add_paragraph(original_text)
doc_name = f"{data['title']}.docx"
doc_path = os.path.join(documents_path, doc_name)
doc.save(doc_path)
print(f"Сохранен документ: {doc_path}")
except Exception as ex:
print(f"Ошибка при обработке ответа GPT: {ex}")
logger.info(f"Ошибка при обработке ответа GPT: {ex}")

48
services/gpt_client.py Normal file
View File

@@ -0,0 +1,48 @@
"""
GPT клиент - отправка запросов к нейросети
"""
import time
import requests
from config import GPT_SERVER_URL, GPT_MAX_RETRIES, GPT_TIMEOUT
from utils import logger
import work_parser as wp
def gpt_response_message(content: str, name_promt: str) -> str:
"""
Отправляет текст на обработку GPT серверу
Возвращает ответ или пустую строку при ошибке
"""
contentGPT = wp.get_promt(name_promt).replace('{content}', content)
url = GPT_SERVER_URL
params = {'text': contentGPT}
max_retries = GPT_MAX_RETRIES
retries = 0
while retries < max_retries:
try:
response = requests.get(url, params=params, timeout=GPT_TIMEOUT)
return response.text
except requests.exceptions.ConnectTimeout as e:
print(f"Ошибка подключения (timeout): {e}")
logger.warning(f"gpt_response_message timeout:")
retries += 1
if retries < max_retries:
time.sleep(2 ** (retries - 1))
except requests.exceptions.ConnectionError as e:
print(f"Ошибка соединения: {e}")
logger.warning(f"gpt_response_message connection error: ")
retries += 1
if retries < max_retries:
time.sleep(2 ** (retries - 1))
except Exception as ex:
print(f"Ошибка при запросе к GPT: {ex}")
logger.error(f"gpt_response_message: ")
retries += 1
if retries < max_retries:
time.sleep(2 ** (retries - 1))
logger.info(f"Превышен лимит запросов {max_retries}")
return ""

75
services/proxy_manager.py Normal file
View File

@@ -0,0 +1,75 @@
"""
Менеджер прокси - управление загрузкой и использованием прокси
"""
import random
import requests
from config import PROXIES_URL
def download_proxies(url: str = PROXIES_URL) -> list[str]:
"""
Загружает список прокси из удаленного источника
"""
response = requests.get(url)
if response.status_code == 200:
proxies = response.text.splitlines()
return proxies
else:
return []
def get_shuffled_proxies(proxies_list: list[str]) -> list[str]:
"""
Перемешивает список прокси для случайного начала
"""
shuffled = proxies_list.copy()
random.shuffle(shuffled)
return shuffled
def fetch_with_proxy(url: str, proxy: str, verify: bool = True, timeout: int = 10) -> str | None:
"""
Выполняет запрос к URL через прокси
Возвращает текст ответа или None при ошибке
"""
proxies = {
'http': f'http://{proxy}',
'https': f'http://{proxy}',
}
try:
response = requests.get(url, proxies=proxies, timeout=timeout, verify=verify)
response.encoding = 'utf-8'
if response.status_code == 200:
# Проверяем содержимое - если это ошибка от прокси
if '"message":"Request failed' in response.text or '403' in response.text[:500]:
print(f"Proxy {proxy} - Site returned 403 (inside response)")
return None
print(f"Proxy {proxy} - SUCCESS")
return response.text
elif response.status_code == 403:
print(f"Proxy {proxy} - 403 Forbidden")
return None # Прокси работает, но сайт блокирует
else:
print(f"Proxy {proxy} - Status {response.status_code}")
return None
except:
return None
def fetch_with_proxy_retry(url: str, timeout: int = 10, verify: bool = True) -> str:
"""
Выполняет запрос с перебором прокси до успешного
Возвращает пустую строку если все прокси не сработали
"""
proxies_list = download_proxies(PROXIES_URL)
proxies_list = get_shuffled_proxies(proxies_list)
response = ""
for proxy in proxies_list:
response = fetch_with_proxy(url, proxy=proxy, timeout=timeout, verify=verify)
if response:
break
else:
response = ""
return response