Compare commits
25 Commits
994479fd9d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f5e9df751 | |||
| 3266e79e54 | |||
| 55710cc961 | |||
| 63ab6ed108 | |||
| e9d7a2d51f | |||
| 770445feaa | |||
| 656c8d9d9a | |||
| dd1c36b9de | |||
| c9abd97efa | |||
| a841e1f58a | |||
| e5a93e307a | |||
| 74143fd369 | |||
| 05cd85d8b5 | |||
| 707c523b53 | |||
| 83020c3124 | |||
| d0f0ea60a8 | |||
| c7f0ad856a | |||
| 94b608cd6d | |||
| ef453f661a | |||
| 46350c1c09 | |||
| 3e2820c8ce | |||
| b688336161 | |||
| d55e96978e | |||
| 0577f4d65c | |||
| 8f86c51d19 |
@@ -12,7 +12,7 @@ from fastapi.responses import FileResponse
|
||||
|
||||
from config import DOCUMENTS_DIR, APP_TITLE, APP_DESCRIPTION, APP_VERSION
|
||||
from utils import logger
|
||||
from api.schemas import ParserOneRequest, Parserall, Source, DownloadRange
|
||||
from api.schemas import ParserOneRequest, Parserall, Source, DownloadRange, DownloadCountsResponse
|
||||
from parsers import start_pars_one_istochnik, start_pars_two_istochnik, start_pars_all_istochnik
|
||||
import work_parser as wp
|
||||
|
||||
@@ -25,7 +25,7 @@ def setup_routes(app: FastAPI) -> None:
|
||||
# CORS middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=["http://localhost:5173", "https://allowlgroup.ru"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
@@ -50,8 +50,8 @@ def setup_routes(app: FastAPI) -> None:
|
||||
return {"status": "success", "message": "Источник добавлен", "data": result}
|
||||
|
||||
@app.get("/all_sources", summary="Метод получения всех источников")
|
||||
async def get_all_sources():
|
||||
return wp.get_all_sources()
|
||||
async def get_all_sources(category: str = "all"):
|
||||
return wp.get_all_sources(category)
|
||||
|
||||
@app.delete("/delete_sources", summary="Метод удаления источника")
|
||||
async def delete_sources(url: str):
|
||||
@@ -112,36 +112,54 @@ def setup_routes(app: FastAPI) -> None:
|
||||
|
||||
@app.post("/download_all", summary="Скачать все файлы за период")
|
||||
async def download_all(dates: DownloadRange, background_tasks: BackgroundTasks):
|
||||
date_start = dates.data_start
|
||||
date_finish = dates.data_finish
|
||||
date_start_str = dates.data_start
|
||||
date_finish_str = dates.data_finish
|
||||
field_name = getattr(dates, 'field_name', 'status') # Поле для фильтрации (по умолчанию 'status')
|
||||
|
||||
try:
|
||||
start_date = datetime.strptime(date_start, "%Y-%m-%d")
|
||||
finish_date = datetime.strptime(date_finish, "%Y-%m-%d")
|
||||
start_date = datetime.strptime(date_start_str, "%Y-%m-%d")
|
||||
finish_date = datetime.strptime(date_finish_str, "%Y-%m-%d") + timedelta(days=1)
|
||||
except ValueError:
|
||||
return {"error": "Неверный формат даты. Используйте YYYY-MM-DD"}
|
||||
|
||||
if start_date > finish_date:
|
||||
return {"error": "Дата начала не может быть позже даты окончания"}
|
||||
|
||||
all_files = []
|
||||
# 1. Получаем список заголовков из БД
|
||||
start_date_str = start_date.strftime("%Y-%m-%d")
|
||||
finish_date_str = finish_date.strftime("%Y-%m-%d")
|
||||
try:
|
||||
titles_from_db = wp.get_articles_by_filter(field_name, start_date_str, finish_date_str)
|
||||
except Exception as e:
|
||||
return {"error": f"Ошибка при получении данных из БД: {e}"}
|
||||
|
||||
if not titles_from_db:
|
||||
return {"error": "Нет статей с выбранным фильтром за указанный период", "field_name": field_name}
|
||||
|
||||
|
||||
# 2. Собираем все файлы .docx за период
|
||||
all_files = []
|
||||
current_date = start_date
|
||||
while current_date <= finish_date:
|
||||
while current_date <= finish_date :
|
||||
date_path = current_date.strftime("%Y/%m/%d")
|
||||
full_dir_path = os.path.join(DOCUMENTS_DIR, date_path)
|
||||
|
||||
if os.path.exists(full_dir_path):
|
||||
for file in os.listdir(full_dir_path):
|
||||
if file.endswith('.docx'):
|
||||
file_title = file[:-5] # убираем расширение .docx
|
||||
if file_title in titles_from_db:
|
||||
all_files.append(os.path.join(full_dir_path, file))
|
||||
|
||||
current_date += timedelta(days=1)
|
||||
|
||||
if not all_files:
|
||||
return {"error": "Файлы не найдены за указанный период", "date_start": date_start, "date_finish": date_finish}
|
||||
return {"error": "Файлы не найдены за указанный период",
|
||||
"date_start": date_start_str,
|
||||
"date_finish": date_finish_str,
|
||||
"titles_found": len(titles_from_db)}
|
||||
|
||||
archive_name = f"documents_{date_start}_{date_finish}.zip"
|
||||
archive_name = f"documents_{date_start_str}_{date_finish_str}.zip"
|
||||
archive_path = os.path.join(DOCUMENTS_DIR, archive_name)
|
||||
|
||||
try:
|
||||
@@ -160,6 +178,13 @@ def setup_routes(app: FastAPI) -> None:
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось удалить архив: {e}")
|
||||
|
||||
def mark_as_downloaded():
|
||||
try:
|
||||
wp.mark_articles_as_downloaded(titles_from_db)
|
||||
logger.info(f"Статьи помечены как скачанные: {len(titles_from_db)} записей")
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при обновлении download: {e}")
|
||||
|
||||
response = FileResponse(
|
||||
path=archive_path,
|
||||
filename=archive_name,
|
||||
@@ -171,9 +196,27 @@ def setup_routes(app: FastAPI) -> None:
|
||||
response.headers["Access-Control-Expose-Headers"] = "Content-Disposition"
|
||||
|
||||
background_tasks.add_task(cleanup_archive)
|
||||
background_tasks.add_task(mark_as_downloaded)
|
||||
|
||||
return response
|
||||
|
||||
# ==================== Выгрузка (download) ====================
|
||||
|
||||
@app.get("/download_counts", summary="Получить количество статей для выгрузки", response_model=DownloadCountsResponse)
|
||||
async def get_download_counts():
|
||||
"""
|
||||
Возвращает количество статей для каждого поля (tematik, svodka, donesenie, bilutene, status),
|
||||
где значение поля = TRUE и download = FALSE
|
||||
"""
|
||||
return wp.get_download_counts()
|
||||
|
||||
# @app.post("/mark_downloaded", summary="Отметить статьи как скачанные")
|
||||
# async def mark_articles_as_downloaded(titles: List[str]):
|
||||
# """
|
||||
# Обновляет поле download = TRUE для списка заголовков статей
|
||||
# """
|
||||
# return wp.mark_articles_as_downloaded(titles)
|
||||
|
||||
@app.get("/logs", summary="Показать логи")
|
||||
async def get_logs():
|
||||
with open("app.log", "r") as file:
|
||||
|
||||
@@ -31,3 +31,12 @@ class DownloadRange(BaseModel):
|
||||
"""Диапазон дат для скачивания файлов"""
|
||||
data_start: str
|
||||
data_finish: str
|
||||
field_name: str = "status"
|
||||
|
||||
|
||||
class DownloadCountsResponse(BaseModel):
|
||||
"""Ответ с количеством статей для выгрузки по каждому полю"""
|
||||
tematik: int
|
||||
svodka: int
|
||||
donesenie: int
|
||||
bilutene: int
|
||||
|
||||
27
main.py
27
main.py
@@ -4,6 +4,7 @@ Parser API - Точка входа приложения
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
import uvicorn
|
||||
from work_parser import get_true_sources
|
||||
|
||||
from config import (
|
||||
APP_TITLE,
|
||||
@@ -16,10 +17,11 @@ from config import (
|
||||
SCHEDULED_PARSER_2_MINUTE
|
||||
)
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from parsers import scheduled_parser_1, scheduled_parser_2
|
||||
from parsers import scheduled_parser_1, scheduled_parser_2, scheduled_parser_universal
|
||||
from api import setup_routes
|
||||
|
||||
|
||||
|
||||
# Инициализация планировщика
|
||||
scheduler = AsyncIOScheduler()
|
||||
|
||||
@@ -30,12 +32,35 @@ async def lifespan(app: FastAPI):
|
||||
# Startup
|
||||
scheduler.add_job(scheduled_parser_1, "cron", hour=SCHEDULED_PARSER_1_HOUR, minute=SCHEDULED_PARSER_1_MINUTE)
|
||||
scheduler.add_job(scheduled_parser_2, "cron", hour=SCHEDULED_PARSER_2_HOUR, minute=SCHEDULED_PARSER_2_MINUTE)
|
||||
|
||||
# Получаем все источники и распределяем их равномерно по 24 часам
|
||||
sources = get_true_sources().items()
|
||||
num_sources = len(sources)
|
||||
|
||||
if num_sources > 0:
|
||||
total_minutes_per_day = 24 * 60
|
||||
minutes_per_source = total_minutes_per_day / num_sources
|
||||
|
||||
for idx, (url, promt) in enumerate(sources):
|
||||
total_minutes = int(idx * minutes_per_source)
|
||||
scheduled_hour = total_minutes // 60
|
||||
scheduled_minute = total_minutes % 60
|
||||
# Для универсального парсера нужно передавать url и promt как аргументы
|
||||
scheduler.add_job(
|
||||
scheduled_parser_universal,
|
||||
"cron",
|
||||
hour=scheduled_hour,
|
||||
minute=scheduled_minute,
|
||||
args=[url, promt]
|
||||
)
|
||||
|
||||
scheduler.start()
|
||||
yield
|
||||
# Shutdown
|
||||
scheduler.shutdown()
|
||||
|
||||
|
||||
|
||||
# Создание приложения FastAPI
|
||||
app = FastAPI(
|
||||
title=APP_TITLE,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from .base import BaseParser
|
||||
from .source1 import Source1Parser, start_pars_one_istochnik, scheduled_parser_1
|
||||
from .source2 import Source2Parser, start_pars_two_istochnik, scheduled_parser_2
|
||||
from .universal import UniversalParser, start_pars_all_istochnik
|
||||
from .universal import UniversalParser, start_pars_all_istochnik, scheduled_parser_universal
|
||||
|
||||
__all__ = [
|
||||
'BaseParser',
|
||||
@@ -15,5 +15,6 @@ __all__ = [
|
||||
'start_pars_two_istochnik',
|
||||
'scheduled_parser_2',
|
||||
'UniversalParser',
|
||||
'start_pars_all_istochnik'
|
||||
'start_pars_all_istochnik',
|
||||
'scheduled_parser_universal'
|
||||
]
|
||||
|
||||
@@ -158,4 +158,4 @@ def scheduled_parser_1() -> None:
|
||||
"""
|
||||
Функция для автоматического запуска по расписанию
|
||||
"""
|
||||
start_pars_one_istochnik()
|
||||
start_pars_one_istochnik("")
|
||||
|
||||
@@ -130,7 +130,7 @@ class Source2Parser(BaseParser):
|
||||
text, time_text = extract_text_from_url(hrefs)
|
||||
if len(text) >= MIN_ARTICLE_TEXT_LENGTH:
|
||||
response_text = gpt_response_message(text, "source2")
|
||||
print(response_text)
|
||||
# print(response_text)
|
||||
if response_text:
|
||||
update_bd_and_create_document(
|
||||
response_text=response_text,
|
||||
|
||||
@@ -46,7 +46,7 @@ class UniversalParser(BaseParser):
|
||||
"""
|
||||
print(f"Начало парсинга: {self.url} с промтом: {self.promt}")
|
||||
self.start_task(self.url)
|
||||
|
||||
num = 0
|
||||
try:
|
||||
response = requests.get(self.url)
|
||||
# print(response.text)
|
||||
@@ -74,7 +74,7 @@ class UniversalParser(BaseParser):
|
||||
if parsed.netloc != base_domain:
|
||||
continue
|
||||
|
||||
|
||||
# print(num)
|
||||
|
||||
if not check_url(abs_url) and wp.check_error_url(abs_url):
|
||||
try:
|
||||
@@ -85,9 +85,11 @@ class UniversalParser(BaseParser):
|
||||
print("URL:", abs_url)
|
||||
|
||||
if len(article.text) > 200 and article.publish_date:
|
||||
num += 1
|
||||
# Если дата публикации отсутствует - используем текущую
|
||||
if article.publish_date:
|
||||
time_text = article.publish_date.strftime("%Y/%m/%d %H:%M:%S")
|
||||
print(time_text)
|
||||
else:
|
||||
time_text = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
|
||||
print(f"Дата публикации отсутствует, используем текущую: {time_text}")
|
||||
@@ -109,7 +111,8 @@ class UniversalParser(BaseParser):
|
||||
print(f"Ошибка при обработке статьи {abs_url}: {e}")
|
||||
logger.info(f"Ошибка при обработке статьи {abs_url}: {e}")
|
||||
continue
|
||||
|
||||
if num == 0:
|
||||
wp.update_source_status(self.url)
|
||||
self.complete_task()
|
||||
|
||||
|
||||
@@ -119,3 +122,10 @@ def start_pars_all_istochnik(url: str, promt: str) -> None:
|
||||
"""
|
||||
parser = UniversalParser(url, promt)
|
||||
parser.parse()
|
||||
|
||||
|
||||
def scheduled_parser_universal(url: str, promt: str) -> None:
|
||||
"""
|
||||
Функция для автоматического запуска универсального парсера по расписанию
|
||||
"""
|
||||
start_pars_all_istochnik(url, promt)
|
||||
|
||||
@@ -35,14 +35,6 @@ def update_bd_and_create_document(
|
||||
clean_response = response_text.strip().replace('```json', '').replace('```', '').strip()
|
||||
data = json.loads(clean_response)
|
||||
|
||||
# Проверяем наличие обязательных полей от GPT
|
||||
# missing_fields = [f for f in REQUIRED_FIELDS if not data.get(f)]
|
||||
# if missing_fields:
|
||||
# print(f"Ответ GPT не содержит обязательных полей: {missing_fields} для URL: {url}")
|
||||
# logger.warning(f"Ответ GPT не содержит обязательных полей: {missing_fields} для URL: {url}")
|
||||
# print(f"Полученные данные: {data}")
|
||||
# return
|
||||
|
||||
# Нормализация типов: category может приходить как list, а ожидается str
|
||||
if isinstance(data.get('category'), list):
|
||||
data['category'] = ', '.join(data['category'])
|
||||
@@ -60,6 +52,7 @@ def update_bd_and_create_document(
|
||||
data['donesenie'] = False
|
||||
data['bilutene'] = False
|
||||
data['other'] = other
|
||||
data['download'] = False
|
||||
|
||||
# Сохранение в БД через pbd
|
||||
parsed_data = wp.ParsedData(**data)
|
||||
@@ -67,6 +60,7 @@ def update_bd_and_create_document(
|
||||
print("Данные успешно сохранены в БД")
|
||||
|
||||
# Создание DOCX документа
|
||||
# path_day = parsed_at.split()[0].replace('-', '/')
|
||||
path_day = article_date.split()[0]
|
||||
documents_path = os.path.join(DOCUMENTS_DIR, path_day)
|
||||
if not os.path.exists(documents_path):
|
||||
@@ -75,7 +69,7 @@ def update_bd_and_create_document(
|
||||
|
||||
doc = Document()
|
||||
doc.add_heading('Ссылка на статью', level=1)
|
||||
doc.add_paragraph(other)
|
||||
doc.add_paragraph(url)
|
||||
doc.add_heading('Дата и время', level=1)
|
||||
doc.add_paragraph(article_date)
|
||||
doc.add_heading('Обнаруженные тематики текста', level=1)
|
||||
|
||||
254
work_parser.py
254
work_parser.py
@@ -26,6 +26,7 @@ class ParsedData(BaseModel):
|
||||
svodka: Optional[bool] = False
|
||||
donesenie: Optional[bool] = False
|
||||
bilutene: Optional[bool] = False
|
||||
download: Optional[bool] = False
|
||||
other: str
|
||||
category: str
|
||||
translation_text: str
|
||||
@@ -38,8 +39,8 @@ def save_parsed_data_to_db(data: ParsedData):
|
||||
conn = get_connection()
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
INSERT INTO url (url, parsed_at, title, original_text, article_date, status, viewed, tematik, svodka, donesenie, bilutene, other, category, translation_text, short_text)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
INSERT INTO url (url, parsed_at, title, original_text, article_date, status, viewed, tematik, svodka, donesenie, download, bilutene, other, category, translation_text, short_text)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
ON CONFLICT (url) DO UPDATE SET
|
||||
parsed_at = EXCLUDED.parsed_at,
|
||||
title = EXCLUDED.title,
|
||||
@@ -48,6 +49,7 @@ def save_parsed_data_to_db(data: ParsedData):
|
||||
status = EXCLUDED.status,
|
||||
viewed = EXCLUDED.viewed,
|
||||
tematik = EXCLUDED.tematik,
|
||||
download = EXCLUDED.download,
|
||||
svodka = EXCLUDED.svodka,
|
||||
donesenie = EXCLUDED.donesenie,
|
||||
bilutene = EXCLUDED.bilutene,
|
||||
@@ -55,7 +57,7 @@ def save_parsed_data_to_db(data: ParsedData):
|
||||
category = EXCLUDED.category,
|
||||
translation_text = EXCLUDED.translation_text,
|
||||
short_text = EXCLUDED.short_text;
|
||||
""", (data.url, data.parsed_at, data.title, data.original_text, data.article_date, data.status, data.viewed, data.tematik, data.svodka, data.donesenie, data.bilutene, data.other, data.category, data.translation_text, data.short_text))
|
||||
""", (data.url, data.parsed_at, data.title, data.original_text, data.article_date, data.status, data.viewed, data.tematik, data.svodka, data.donesenie, data.download, data.bilutene, data.other, data.category, data.translation_text, data.short_text))
|
||||
conn.commit()
|
||||
return {"status": "success", "message": "Данные успешно сохранены"}
|
||||
except Exception as e:
|
||||
@@ -66,6 +68,79 @@ def save_parsed_data_to_db(data: ParsedData):
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
def get_articles_by_filter(field_name: str, start_date: str, finish_date: str):
|
||||
"""
|
||||
Возвращает список заголовков статей по полю и диапазону дат для выгрузки
|
||||
"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
# Проверка валидности поля
|
||||
allowed_fields = ['tematik', 'svodka', 'donesenie', 'bilutene', 'status']
|
||||
if field_name not in allowed_fields:
|
||||
raise ValueError(f"Недопустимое поле: {field_name}. Разрешено: {allowed_fields}")
|
||||
|
||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||
cur.execute(f"""
|
||||
SELECT title FROM url
|
||||
WHERE {field_name} = TRUE
|
||||
AND article_date BETWEEN %s AND %s
|
||||
ORDER BY article_date DESC;
|
||||
""", (start_date, finish_date))
|
||||
|
||||
rows = cur.fetchall()
|
||||
return [row['title'] for row in rows]
|
||||
except Exception as e:
|
||||
print(f"Ошибка в get_articles_by_filter: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def get_download_counts():
|
||||
"""
|
||||
Возвращает количество статей для каждого поля, где поле = TRUE и download = FALSE
|
||||
"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
allowed_fields = ['tematik', 'svodka', 'donesenie', 'bilutene']
|
||||
|
||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||
counts = {}
|
||||
for field in allowed_fields:
|
||||
cur.execute(f"""
|
||||
SELECT COUNT(*) as count FROM url
|
||||
WHERE {field} = TRUE
|
||||
AND download = FALSE;
|
||||
""")
|
||||
row = cur.fetchone()
|
||||
counts[field] = row['count']
|
||||
|
||||
return counts
|
||||
except Exception as e:
|
||||
print(f"Ошибка в get_download_counts: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def mark_articles_as_downloaded(titles: list):
|
||||
"""
|
||||
Обновляет download = TRUE для списка заголовков
|
||||
"""
|
||||
if not titles:
|
||||
return {"message": "Список заголовков пуст", "updated_rows": 0}
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
UPDATE url
|
||||
SET download = TRUE
|
||||
WHERE title = ANY(%s);
|
||||
""", (titles,))
|
||||
updated_rows = cur.rowcount
|
||||
conn.commit()
|
||||
return {"message": f"Статус download обновлён для {updated_rows} статей", "updated_rows": updated_rows}
|
||||
except Exception as e:
|
||||
print(f"Ошибка в mark_articles_as_downloaded: {e}")
|
||||
raise
|
||||
|
||||
|
||||
# Глобальное подключение к БД
|
||||
conn = None
|
||||
@@ -73,15 +148,39 @@ conn = None
|
||||
def get_connection():
|
||||
"""Получает подключение к БД, создавая новое при необходимости"""
|
||||
global conn
|
||||
try:
|
||||
# Проверяем, активно ли подключение
|
||||
if conn is None or conn.closed:
|
||||
conn = psycopg2.connect(**DB_CONFIG)
|
||||
conn.autocommit = True
|
||||
# Дополнительная проверка на валидность
|
||||
elif conn.info.transaction_status == 2: # TRANSACTION_IN_TRANS
|
||||
# Подключение активно, но в транзакции — закроем и создадим новое
|
||||
try:
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
conn = psycopg2.connect(**DB_CONFIG)
|
||||
conn.autocommit = True
|
||||
return conn
|
||||
except Exception as e:
|
||||
print(f"Ошибка при получении подключения: {e}")
|
||||
# Сбрасываем подключение и пробуем заново
|
||||
conn = None
|
||||
conn = psycopg2.connect(**DB_CONFIG)
|
||||
conn.autocommit = True
|
||||
return conn
|
||||
|
||||
def close_connection():
|
||||
"""Закрывает глобальное подключение к БД"""
|
||||
global conn
|
||||
if conn and not conn.closed:
|
||||
conn.close()
|
||||
conn = None
|
||||
# Проверяет, есть ли указанный URL в базе данных.
|
||||
def check_url_exists(url: str):
|
||||
conn = get_connection()
|
||||
try:
|
||||
conn = get_connection()
|
||||
with conn.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"SELECT 1 FROM url WHERE url = %s LIMIT 1",
|
||||
@@ -91,8 +190,6 @@ def check_url_exists(url: str):
|
||||
return {"exists": bool(result)}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Ошибка при проверке: {e}")
|
||||
finally:
|
||||
pass
|
||||
|
||||
# работа с базой данных показывания задач work_parser
|
||||
def create_table():
|
||||
@@ -113,8 +210,8 @@ def create_table():
|
||||
);
|
||||
""")
|
||||
print("Таблица work_parser создана или уже существует")
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при создании таблицы work_parser: {e}")
|
||||
|
||||
def insert_task(status, source_url=None, source_id=None, priority=0):
|
||||
conn = get_connection()
|
||||
@@ -127,8 +224,9 @@ def insert_task(status, source_url=None, source_id=None, priority=0):
|
||||
""", (status, source_url, priority))
|
||||
task_id = cur.fetchone()[0]
|
||||
return task_id
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при создании задачи: {e}")
|
||||
raise
|
||||
|
||||
def get_tasks_offset(limit, offset):
|
||||
conn = get_connection()
|
||||
@@ -141,8 +239,9 @@ def get_tasks_offset(limit, offset):
|
||||
""", (limit, offset))
|
||||
tasks = cur.fetchall()
|
||||
return tasks
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при получении задач: {e}")
|
||||
raise
|
||||
|
||||
def delete_task(task_id: int):
|
||||
conn = get_connection()
|
||||
@@ -154,8 +253,9 @@ def delete_task(task_id: int):
|
||||
return {"message": f"Задача {task_id} удалена", "deleted_task": dict(deleted_task)}
|
||||
else:
|
||||
return {"message": f"Задача с id {task_id} не найдена"}
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при удалении задачи: {e}")
|
||||
raise
|
||||
|
||||
def update_task(task_id, **fields):
|
||||
conn = get_connection()
|
||||
@@ -174,8 +274,9 @@ def update_task(task_id, **fields):
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"UPDATE work_parser SET {set_sql} WHERE id = %s;", values)
|
||||
return True
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обновлении задачи: {e}")
|
||||
raise
|
||||
|
||||
# Создание и работа с таблицей по созданию и редактированию промтов
|
||||
def create_table_config_gpt():
|
||||
@@ -189,8 +290,8 @@ def create_table_config_gpt():
|
||||
);
|
||||
""")
|
||||
print("Таблица config_gpt создана или уже существует")
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при создании таблицы config_gpt: {e}")
|
||||
|
||||
def update_promt(name: str, promt: str):
|
||||
conn = get_connection()
|
||||
@@ -203,8 +304,9 @@ def update_promt(name: str, promt: str):
|
||||
promt = EXCLUDED.promt
|
||||
""", (name, promt))
|
||||
conn.commit()
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обновлении промта: {e}")
|
||||
raise
|
||||
|
||||
def get_promt(promt_name_url):
|
||||
conn = get_connection()
|
||||
@@ -212,9 +314,12 @@ def get_promt(promt_name_url):
|
||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||
cur.execute("SELECT promt FROM config_gpt WHERE name = %s", (promt_name_url,))
|
||||
promt = cur.fetchone()
|
||||
if promt:
|
||||
return promt['promt']
|
||||
finally:
|
||||
pass
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Ошибка при получении промта: {e}")
|
||||
raise
|
||||
|
||||
def get_all_promt():
|
||||
# Возвращает список всех значений поля name из таблицы config_gpt
|
||||
@@ -226,8 +331,9 @@ def get_all_promt():
|
||||
|
||||
sources = [{"name": row["name"], "promt": row["promt"]} for row in rows]
|
||||
return {"sources": sources}
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при получении всех промтов: {e}")
|
||||
raise
|
||||
|
||||
def get_all_categories_promt():
|
||||
conn = get_connection()
|
||||
@@ -237,8 +343,9 @@ def get_all_categories_promt():
|
||||
rows = cur.fetchall()
|
||||
|
||||
return [row["name"] for row in rows]
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при получении категорий: {e}")
|
||||
raise
|
||||
|
||||
# Создание, сохранение и работа с таблицей ошибочных ссылок (error_url)
|
||||
def create_table_error_url():
|
||||
@@ -253,8 +360,8 @@ def create_table_error_url():
|
||||
);
|
||||
""")
|
||||
print("Таблица error_url создана или уже существует")
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при создании таблицы error_url: {e}")
|
||||
|
||||
def add_error_url(source_url: str, error_sources_url: str):
|
||||
conn = get_connection()
|
||||
@@ -266,8 +373,9 @@ def add_error_url(source_url: str, error_sources_url: str):
|
||||
RETURNING id;
|
||||
""", (source_url, error_sources_url))
|
||||
return cur.fetchone()[0]
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при добавлении error_url: {e}")
|
||||
raise
|
||||
|
||||
def check_error_url(error_sources_url: str) -> bool:
|
||||
conn = get_connection()
|
||||
@@ -281,8 +389,9 @@ def check_error_url(error_sources_url: str) -> bool:
|
||||
row = cur.fetchone()
|
||||
|
||||
return row is None
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при проверке error_url: {e}")
|
||||
return True
|
||||
|
||||
# Создание и работа с таблицей источников sources
|
||||
def create_table_add_sourse():
|
||||
@@ -292,38 +401,85 @@ def create_table_add_sourse():
|
||||
cur.execute("""
|
||||
CREATE TABLE IF NOT EXISTS sourse (
|
||||
url TEXT PRIMARY KEY,
|
||||
promt TEXT
|
||||
promt TEXT,
|
||||
status BOOLEAN DEFAULT FALSE
|
||||
);
|
||||
""")
|
||||
print("Таблица sourse создана или уже существует")
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при создании таблицы sourse: {e}")
|
||||
|
||||
def add_sources(url: str, promt: str):
|
||||
def add_sources(url: str, promt: str, status: bool = False):
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
INSERT INTO sourse (url, promt)
|
||||
VALUES (%s, %s)
|
||||
INSERT INTO sourse (url, promt, status)
|
||||
VALUES (%s, %s, %s)
|
||||
ON CONFLICT (url) DO UPDATE SET
|
||||
promt = EXCLUDED.promt
|
||||
""", (url, promt))
|
||||
promt = EXCLUDED.promt,
|
||||
status = EXCLUDED.status
|
||||
""", (url, promt, status))
|
||||
conn.commit()
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при добавлении источника: {e}")
|
||||
raise
|
||||
|
||||
def get_all_sources():
|
||||
"""Возвращает все записи из таблицы sourse"""
|
||||
def get_all_sources(category: str):
|
||||
"""Возвращает все записи из таблицы sourse. Сначала показываются записи со status=false"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||
cur.execute("SELECT * FROM sourse")
|
||||
if category == "all":
|
||||
cur.execute("""
|
||||
SELECT * FROM sourse
|
||||
ORDER BY status ASC, url ASC
|
||||
""")
|
||||
else:
|
||||
cur.execute("""
|
||||
SELECT * FROM sourse
|
||||
WHERE promt = %s
|
||||
ORDER BY status ASC, url ASC
|
||||
""", (category,))
|
||||
rows = cur.fetchall()
|
||||
sources = [{"url": row["url"], "promt": row["promt"]} for row in rows]
|
||||
sources = [{"url": row["url"], "promt": row["promt"], "status": row["status"]} for row in rows]
|
||||
return {"sources": sources}
|
||||
finally:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка при получении источников: {e}")
|
||||
return {"error": str(e), "sources": []}
|
||||
|
||||
def get_true_sources():
|
||||
"""Возвращает все записи из таблицы sourse. Сначала показываются записи со status=true"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||
cur.execute("""
|
||||
SELECT * FROM sourse
|
||||
WHERE status = false
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
sources = {}
|
||||
for row in rows:
|
||||
sources.update({row["url"]: row["promt"]})
|
||||
return sources
|
||||
except Exception as e:
|
||||
print(f"Ошибка при получении источников: {e}")
|
||||
return {"error": str(e), "sources": []}
|
||||
|
||||
def update_source_status(url: str, status: bool = True):
|
||||
"""Обновляет статус источника по URL"""
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
UPDATE sourse SET status = %s WHERE url = %s
|
||||
""", (status, url))
|
||||
updated = cur.rowcount
|
||||
conn.commit()
|
||||
return {"message": f"Статус обновлён для {url}", "updated_rows": updated}
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обновлении статуса: {e}")
|
||||
return {"error": str(e), "updated_rows": 0}
|
||||
|
||||
def delete_sources(url: str):
|
||||
"""Удаляет источник по URL из таблицы sourse"""
|
||||
@@ -340,8 +496,6 @@ def delete_sources(url: str):
|
||||
except Exception as e:
|
||||
print(f"Ошибка при удалении источника: {e}")
|
||||
return {"error": str(e), "deleted": False}
|
||||
finally:
|
||||
pass
|
||||
|
||||
# Пример использования
|
||||
# if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user