Compare commits
10 Commits
44cf3d3c21
...
d4224de2e1
| Author | SHA1 | Date | |
|---|---|---|---|
| d4224de2e1 | |||
| 60224501ab | |||
| a689e0b9da | |||
| d03f9da044 | |||
| ba523340df | |||
| ad07f20c05 | |||
| 6fcc0753dc | |||
| df1440d926 | |||
| c89e8d493d | |||
| b396da4c13 |
30
.drone.yml
Normal file
30
.drone.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: python:3.11-slim
|
||||||
|
commands:
|
||||||
|
- pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
- name: deploy
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
settings:
|
||||||
|
host: allowlgroup.ru
|
||||||
|
username:
|
||||||
|
from_secret: ssh_username
|
||||||
|
password:
|
||||||
|
from_secret: ssh_password
|
||||||
|
port: 22
|
||||||
|
script:
|
||||||
|
- docker stop parser || true
|
||||||
|
- docker rm parser || true
|
||||||
|
- docker pull gitea.allowlgroup.ru/allowlgroup/parser:latest
|
||||||
|
- docker run -d --name parser -p 8001:8001 -v /opt/parser_data:/app/documents gitea.allowlgroup.ru/allowlgroup/parser:latest
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- custom
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -6,9 +6,10 @@ __pycache__/
|
|||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Виртуальное окружение
|
# Виртуальное окружение
|
||||||
# venv/
|
.venv/
|
||||||
# env/
|
save/
|
||||||
|
2026/
|
||||||
|
2027/
|
||||||
# IDE
|
# IDE
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8001
|
||||||
|
|
||||||
|
# Запуск с uvicorn
|
||||||
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
|
||||||
293
main.py
293
main.py
@@ -1,50 +1,70 @@
|
|||||||
from fastapi import FastAPI, Request, BackgroundTasks, Query
|
# Стандартные библиотеки (stdlib)
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
from pydantic import BaseModel
|
|
||||||
import logging
|
|
||||||
import subprocess
|
|
||||||
import requests
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
from urllib.parse import urljoin
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
from datetime import datetime
|
||||||
|
import random
|
||||||
|
|
||||||
|
# Сторонние библиотеки (third-party)
|
||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from docx import Document
|
||||||
|
from fastapi import BackgroundTasks, FastAPI, Query, Request, Depends
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.responses import FileResponse
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from urllib.parse import urljoin
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
import time
|
import requests
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
# Локальные импорты
|
||||||
import settings_work as sw
|
import settings_work as sw
|
||||||
import work_parser as wp
|
import work_parser as wp
|
||||||
|
|
||||||
|
DOCUMENTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "documents")
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
"""Управление жизненным циклом приложения"""
|
||||||
|
# Startup
|
||||||
|
scheduler.add_job(scheduled_parser_1, "cron", hour=10, minute=0)
|
||||||
|
scheduler.add_job(scheduled_parser_2, "cron", hour=11, minute=0)
|
||||||
|
scheduler.start()
|
||||||
|
yield
|
||||||
|
# Shutdown
|
||||||
|
scheduler.shutdown()
|
||||||
|
|
||||||
app = FastAPI(title="Parser API",
|
app = FastAPI(title="Parser API",
|
||||||
description="API для запуска парсинга в базу данных",
|
description="API для запуска парсинга в базу данных",
|
||||||
version="1.0")
|
version="1.0",
|
||||||
|
lifespan=lifespan)
|
||||||
|
|
||||||
|
# Инициализация планировщика
|
||||||
|
scheduler = AsyncIOScheduler()
|
||||||
|
|
||||||
# Настройка логгера
|
# Настройка логгера
|
||||||
logging.basicConfig(filename="app.log", level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
logging.basicConfig(filename="app.log", level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@app.get("/logs")
|
|
||||||
def get_logs():
|
|
||||||
with open("app.log", "r") as file:
|
|
||||||
lines = file.readlines()[-100:] # последние 100 строк
|
|
||||||
return {"logs": lines}
|
|
||||||
|
|
||||||
# Инициализация таблицы статуса парсинга
|
# Инициализация таблицы статуса парсинга
|
||||||
wp.create_table()
|
wp.create_table()
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"], # или список разрешенных адресов, например ["http://localhost:8080"]
|
allow_origins=["*"], # или список разрешенных адресов, например ["https://allowlgroup.ru","http://localhost:5173", "http://45.129.78.228:8000"]
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
PROXIES_URL = "https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/http.txt"
|
PROXIES_URL = "https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/http.txt"
|
||||||
|
|
||||||
|
|
||||||
def download_proxies(url):
|
def download_proxies(url):
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
@@ -53,20 +73,34 @@ def download_proxies(url):
|
|||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def fetch_with_proxy(url, proxy, verify, timeout):
|
def fetch_with_proxy(url, proxy, verify, timeout):
|
||||||
proxies = {
|
proxies = {
|
||||||
'http': f'http://{proxy}', # или 'socks5://' если SOCKS5 и т.п.
|
'http': f'http://{proxy}',
|
||||||
'https': f'http://{proxy}',
|
'https': f'http://{proxy}',
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, proxies=proxies, timeout=timeout, verify=verify)
|
response = requests.get(url, proxies=proxies, timeout=timeout, verify=verify)
|
||||||
response.encoding = 'utf-8'
|
response.encoding = 'utf-8'
|
||||||
response.raise_for_status()
|
if response.status_code == 200:
|
||||||
return response.text
|
# Проверяем содержимое - если это ошибка от прокси
|
||||||
|
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:
|
except:
|
||||||
return None
|
return None
|
||||||
|
# Перемешивает список прокси для случайного начала
|
||||||
|
def get_shuffled_proxies(proxies_list):
|
||||||
|
shuffled = proxies_list.copy()
|
||||||
|
random.shuffle(shuffled)
|
||||||
|
return shuffled
|
||||||
|
|
||||||
# Общие функции нахождения ссылок
|
# Общие функции нахождения ссылок
|
||||||
def extract_map_area_hrefs(url, verify=True, ist_number=1):
|
def extract_map_area_hrefs(url, verify=True, ist_number=1):
|
||||||
@@ -98,8 +132,8 @@ def extract_map_area_hrefs(url, verify=True, ist_number=1):
|
|||||||
|
|
||||||
# функции парсера первого источника (газета)
|
# функции парсера первого источника (газета)
|
||||||
def extract_text_from_url_one(url, timeout=10, verify=True):
|
def extract_text_from_url_one(url, timeout=10, verify=True):
|
||||||
|
|
||||||
proxies_list = download_proxies(PROXIES_URL)
|
proxies_list = download_proxies(PROXIES_URL)
|
||||||
|
proxies_list = get_shuffled_proxies(proxies_list)
|
||||||
|
|
||||||
response = ""
|
response = ""
|
||||||
for proxy in proxies_list:
|
for proxy in proxies_list:
|
||||||
@@ -111,7 +145,6 @@ def extract_text_from_url_one(url, timeout=10, verify=True):
|
|||||||
|
|
||||||
soup = BeautifulSoup(response, "html.parser")
|
soup = BeautifulSoup(response, "html.parser")
|
||||||
|
|
||||||
|
|
||||||
title_div = soup.find('div', class_='newsdetatit')
|
title_div = soup.find('div', class_='newsdetatit')
|
||||||
title_text = ''
|
title_text = ''
|
||||||
if title_div:
|
if title_div:
|
||||||
@@ -137,7 +170,7 @@ def extract_text_from_url_one(url, timeout=10, verify=True):
|
|||||||
#Функции парсера второго источника (военного)
|
#Функции парсера второго источника (военного)
|
||||||
def extract_text_from_url(url, timeout=10, verify=True):
|
def extract_text_from_url(url, timeout=10, verify=True):
|
||||||
proxies_list = download_proxies(PROXIES_URL)
|
proxies_list = download_proxies(PROXIES_URL)
|
||||||
|
proxies_list = get_shuffled_proxies(proxies_list)
|
||||||
response = ""
|
response = ""
|
||||||
for proxy in proxies_list:
|
for proxy in proxies_list:
|
||||||
response = fetch_with_proxy(url, proxy=proxy, timeout=timeout, verify=verify)
|
response = fetch_with_proxy(url, proxy=proxy, timeout=timeout, verify=verify)
|
||||||
@@ -151,7 +184,7 @@ def extract_text_from_url(url, timeout=10, verify=True):
|
|||||||
# Находим контейнер div.whitecon.article
|
# Находим контейнер div.whitecon.article
|
||||||
container = soup.find("div", class_="whitecon article")
|
container = soup.find("div", class_="whitecon article")
|
||||||
if not container:
|
if not container:
|
||||||
return ""
|
return "", ""
|
||||||
|
|
||||||
# Получение заголовка <time> внутри контейнера
|
# Получение заголовка <time> внутри контейнера
|
||||||
time_text = container.find('span')
|
time_text = container.find('span')
|
||||||
@@ -164,21 +197,28 @@ def extract_text_from_url(url, timeout=10, verify=True):
|
|||||||
# Возвращаем текстовую сводку
|
# Возвращаем текстовую сводку
|
||||||
content_text = []
|
content_text = []
|
||||||
for p in paragraphs:
|
for p in paragraphs:
|
||||||
if p.get('class') != ['before_ir']:
|
if p.get('class') != ['before_ir'] :
|
||||||
content_text.append(p.get_text(strip=True))
|
content_text.append(p.get_text(strip=True))
|
||||||
|
|
||||||
return "\n".join(content_text), time_t
|
return "\n".join(content_text), time_t
|
||||||
|
|
||||||
|
|
||||||
# Общий запрос на GPT
|
# Общий запрос на GPT
|
||||||
def gpt_response_message(content, ist_number=1):
|
def gpt_response_message(content, ist_number=1):
|
||||||
|
|
||||||
Promts = sw.read_settings().sources
|
# Promts = sw.read_settings().sources
|
||||||
|
|
||||||
|
# if ist_number == 1:
|
||||||
|
# contentGPT = Promts[0].prompt.replace('{content}', content)
|
||||||
|
# else:
|
||||||
|
# contentGPT = Promts[1].prompt.replace('{content}', content)
|
||||||
|
|
||||||
|
|
||||||
if ist_number == 1:
|
if ist_number == 1:
|
||||||
contentGPT = Promts[0].prompt.replace('{content}', content)
|
url_ist = "http://epaper.hljnews.cn/hljrb/pc/layout"
|
||||||
else:
|
else:
|
||||||
contentGPT = Promts[1].prompt.replace('{content}', content)
|
url_ist = "https://def.ltn.com.tw/breakingnewslist"
|
||||||
|
|
||||||
|
contentGPT = wp.get_promt(url_ist).replace('{content}', content)
|
||||||
|
|
||||||
url = 'http://45.129.78.228:8484' #10.8.0.14:5500
|
url = 'http://45.129.78.228:8484' #10.8.0.14:5500
|
||||||
params = {'text': contentGPT}
|
params = {'text': contentGPT}
|
||||||
@@ -196,25 +236,18 @@ def gpt_response_message(content, ist_number=1):
|
|||||||
logger.info(f"gpt_response_message: {ex}")
|
logger.info(f"gpt_response_message: {ex}")
|
||||||
|
|
||||||
retries += 1
|
retries += 1
|
||||||
else:
|
|
||||||
restart_service('work_gpt.service')
|
logger.info(f"Привышен лимит запросов {max_retries}")
|
||||||
print(f"\n\n\tПерезапуск GPT\n\n")
|
return ""
|
||||||
try:
|
|
||||||
response = requests.get(url, params=params, timeout=15)
|
|
||||||
return response.text
|
|
||||||
except Exception as ex:
|
|
||||||
print(f"Ошибка при запросе к GPT: {ex}")
|
|
||||||
logger.info(f"gpt_response_message: {ex}")
|
|
||||||
|
|
||||||
retries += 1
|
|
||||||
|
|
||||||
def restart_service(service_name):
|
# перезапуск сервиса GPT при неудачных попытках запроса
|
||||||
try:
|
# def restart_service(service_name):
|
||||||
subprocess.run(['sudo', 'systemctl', 'restart', service_name], check=True)
|
# try:
|
||||||
time.sleep(30)
|
# subprocess.run(['sudo', 'systemctl', 'restart', service_name], check=True)
|
||||||
print(f"Сервис {service_name} успешно перезапущен")
|
# time.sleep(30)
|
||||||
except subprocess.CalledProcessError:
|
# print(f"Сервис {service_name} успешно перезапущен")
|
||||||
print(f"Не удалось перезапустить сервес {service_name}")
|
# except subprocess.CalledProcessError:
|
||||||
|
# print(f"Не удалось перезапустить сервес {service_name}")
|
||||||
|
|
||||||
|
|
||||||
# Общие функции проверки ссылок
|
# Общие функции проверки ссылок
|
||||||
@@ -238,6 +271,56 @@ def create_folder(num):
|
|||||||
num = str(num)
|
num = str(num)
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
# Функция формирования документа
|
||||||
|
def update_bd_and_create_document(response_text, article_date, url, parsed_at, original_text, other):
|
||||||
|
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
|
||||||
|
print(requests.post('http://45.129.78.228:8002/save_parsed_data', json=data))
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
#Функции start первого источника (газета)
|
#Функции start первого источника (газета)
|
||||||
def start_pars_one_istochnik(data_init):
|
def start_pars_one_istochnik(data_init):
|
||||||
@@ -257,13 +340,13 @@ def start_pars_one_istochnik(data_init):
|
|||||||
|
|
||||||
for page_number in range(1, 9):
|
for page_number in range(1, 9):
|
||||||
|
|
||||||
start_url = f'http://epaper.hljnews.cn/hljrb/pc/layout/{current_year}{current_month}/{current_day}/node_0{page_number}.html'
|
url = f'http://epaper.hljnews.cn/hljrb/pc/layout/{current_year}{current_month}/{current_day}/node_0{page_number}.html'
|
||||||
|
|
||||||
wp.update_task(task_id, status='in_progress', source_url=start_url, started_at=datetime.utcnow())
|
wp.update_task(task_id, status='in_progress', source_url=url, started_at=datetime.utcnow())
|
||||||
|
|
||||||
print(f"Сбор href из: {start_url}")
|
print(f"Сбор href из: {url}")
|
||||||
try:
|
try:
|
||||||
hrefs = extract_map_area_hrefs(start_url, ist_number=2)
|
hrefs = extract_map_area_hrefs(url, ist_number=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при извлечении ссылок: {e}")
|
print(f"Ошибка при извлечении ссылок: {e}")
|
||||||
logger.info(f"extract_map_area_hrefs: {e}")
|
logger.info(f"extract_map_area_hrefs: {e}")
|
||||||
@@ -276,24 +359,8 @@ def start_pars_one_istochnik(data_init):
|
|||||||
if len(text) >= 100:
|
if len(text) >= 100:
|
||||||
response_text = gpt_response_message(text, ist_number=2)
|
response_text = gpt_response_message(text, ist_number=2)
|
||||||
print(response_text)
|
print(response_text)
|
||||||
clean_response = ''
|
if response_text:
|
||||||
try:
|
update_bd_and_create_document(response_text=response_text, article_date=f"{current_year}/{current_month}/{current_day}", url=link, parsed_at=str(dt.now()), original_text=text, other=url)
|
||||||
clean_response = response_text.strip().replace('```json', '').replace('```', '').strip()
|
|
||||||
data = json.loads(clean_response)
|
|
||||||
data['article_date'] = f"{current_day}/{current_month}/{current_year}"
|
|
||||||
data['url'] = link
|
|
||||||
data['parsed_at'] = str(dt.now())
|
|
||||||
data['original_text'] = text
|
|
||||||
data['status'] = False
|
|
||||||
data['viewed'] = False
|
|
||||||
data['other'] = start_url
|
|
||||||
|
|
||||||
if data['category']:
|
|
||||||
print(requests.post('http://45.129.78.228:8002/save_parsed_data', json=data))
|
|
||||||
except Exception as ex:
|
|
||||||
print(f"Ошибка при обработке ответа GPT: {ex}")
|
|
||||||
logger.info(f"gpt_response_message: {ex}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
wp.update_task(task_id, status='completed', finished_at=datetime.utcnow())
|
wp.update_task(task_id, status='completed', finished_at=datetime.utcnow())
|
||||||
|
|
||||||
@@ -320,35 +387,21 @@ def start_pars_two_istochnik():
|
|||||||
if len(text) >= 100:
|
if len(text) >= 100:
|
||||||
response_text = gpt_response_message(text)
|
response_text = gpt_response_message(text)
|
||||||
print(response_text)
|
print(response_text)
|
||||||
clean_response = ''
|
if response_text:
|
||||||
try:
|
update_bd_and_create_document(response_text=response_text, article_date=time_text, url=hrefs, parsed_at=str(dt.now()), original_text=text, other=url)
|
||||||
clean_response = response_text.strip().replace('json', '').replace('', '').strip()
|
|
||||||
data = json.loads(clean_response)
|
|
||||||
data['article_date'] = time_text
|
|
||||||
data['url'] = hrefs
|
|
||||||
data['parsed_at'] = str(dt.now())
|
|
||||||
data['original_text'] = text
|
|
||||||
data['status'] = False
|
|
||||||
data['viewed'] = False
|
|
||||||
data['other'] = url
|
|
||||||
# print[date]
|
|
||||||
if data['category']:
|
|
||||||
print(requests.post('http://45.129.78.228:8002/save_parsed_data', json=data))
|
|
||||||
|
|
||||||
except Exception as ex:
|
|
||||||
print(f"Ошибка при обработке ответа GPT: {ex}")
|
|
||||||
logger.info(f"Ошибка при обработке ответа GPT: {ex}")
|
|
||||||
|
|
||||||
continue
|
|
||||||
except:
|
except:
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
wp.update_task(task_id, status='completed', finished_at=datetime.utcnow())
|
wp.update_task(task_id, status='completed', finished_at=datetime.utcnow())
|
||||||
|
|
||||||
|
# Функции для автоматического запуска
|
||||||
|
def scheduled_parser_1():
|
||||||
|
istochnik = "" # пустая строка = текущая дата
|
||||||
|
start_pars_one_istochnik(istochnik.split("."))
|
||||||
|
|
||||||
|
def scheduled_parser_2():
|
||||||
|
start_pars_two_istochnik()
|
||||||
|
|
||||||
|
|
||||||
class ParserOneRequest(BaseModel):
|
class ParserOneRequest(BaseModel):
|
||||||
time: str
|
time: str
|
||||||
|
|
||||||
@@ -371,13 +424,51 @@ def get_tasks_offset(limit: int = Query(10, gt=0), offset: int = Query(0, ge=0))
|
|||||||
# GET метод для получения настроек
|
# GET метод для получения настроек
|
||||||
@app.get("/settings", summary="Метод получения настроек парсера")
|
@app.get("/settings", summary="Метод получения настроек парсера")
|
||||||
def get_settings():
|
def get_settings():
|
||||||
return sw.read_settings()
|
return wp.get_all_promt()
|
||||||
|
|
||||||
# POST метод для установки настроек
|
# POST метод для установки настроек
|
||||||
@app.post("/settings", summary="Метод сохранения настроек парсера")
|
@app.post("/settings", summary="Метод сохранения настроек парсера")
|
||||||
def set_settings(settings: sw.Source):
|
def set_settings(settings: wp.Source):
|
||||||
return sw.update_source(settings)
|
return wp.update_promt(settings)
|
||||||
|
|
||||||
|
@app.delete("/delete_task/{task_id}", summary="Метод удаления задачи")
|
||||||
|
def delete_task(task_id: int):
|
||||||
|
return print(wp.delete_task(task_id))
|
||||||
|
|
||||||
|
# @app.get("/file_download", summary="Метод для скачивания файла")
|
||||||
|
# async def download_file(path: str, title: str):
|
||||||
|
# path = f"./{path}/{title}.docx" #os.path.abspath(path)
|
||||||
|
# return FileResponse(path=path, filename=f'{title}.docx', media_type='multipart/form-data')
|
||||||
|
|
||||||
|
@app.get("/file_download", summary="Метод для скачивания файла")
|
||||||
|
async def download_file(path: str, title: str):
|
||||||
|
file_name = f"{title}.docx"
|
||||||
|
file_path = os.path.join(DOCUMENTS_DIR, path, file_name)
|
||||||
|
logger.warning(f"Файл: {file_path}")
|
||||||
|
|
||||||
|
# Проверяем существование файла
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
logger.warning(f"Файл не найден: {file_path}")
|
||||||
|
return {"error": "Файл не найден", "path": file_path}
|
||||||
|
|
||||||
|
# Возвращаем файл
|
||||||
|
response = FileResponse(
|
||||||
|
path=file_path,
|
||||||
|
filename=file_name,
|
||||||
|
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||||
|
)
|
||||||
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||||
|
response.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
|
||||||
|
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
|
||||||
|
|
||||||
|
logger.warning(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.get("/logs")
|
||||||
|
def get_logs():
|
||||||
|
with open("app.log", "r") as file:
|
||||||
|
lines = file.readlines()[-10:] # последние 10 строк
|
||||||
|
return {"logs": lines}
|
||||||
|
|
||||||
# Запуск сервера для теста
|
|
||||||
# if __name__ == "__main__":
|
# if __name__ == "__main__":
|
||||||
# uvicorn.run("main:app", port=8001, reload=True)
|
# uvicorn.run("main:app", port=8001, reload=True)
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import psycopg2
|
import psycopg2
|
||||||
from psycopg2.extras import RealDictCursor
|
from psycopg2.extras import RealDictCursor
|
||||||
|
from pydantic import BaseModel, HttpUrl
|
||||||
|
|
||||||
# Подключение к БД (укажи свои параметры)
|
# Подключение к БД (укажи свои параметры)
|
||||||
conn = psycopg2.connect(
|
conn = psycopg2.connect(
|
||||||
dbname="parsed_url",
|
dbname="parsed_url",
|
||||||
user="postgres",
|
user="postgres",
|
||||||
password="qwertyqwerty123123",
|
password="qwertyqwerty123123",
|
||||||
host="127.0.0.1"
|
host="45.129.78.228",
|
||||||
|
# host="127.0.0.1"
|
||||||
)
|
)
|
||||||
conn.autocommit = True
|
conn.autocommit = True
|
||||||
|
|
||||||
@@ -37,12 +39,6 @@ def insert_task(status, source_url=None, source_id=None, priority=0):
|
|||||||
task_id = cur.fetchone()[0]
|
task_id = cur.fetchone()[0]
|
||||||
return task_id
|
return task_id
|
||||||
|
|
||||||
def get_task(task_id):
|
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
|
||||||
cur.execute("SELECT * FROM work_parser WHERE id = %s;", (task_id,))
|
|
||||||
task = cur.fetchone()
|
|
||||||
return task
|
|
||||||
|
|
||||||
|
|
||||||
def get_tasks_offset(limit, offset):
|
def get_tasks_offset(limit, offset):
|
||||||
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
@@ -54,6 +50,14 @@ def get_tasks_offset(limit, offset):
|
|||||||
tasks = cur.fetchall()
|
tasks = cur.fetchall()
|
||||||
return tasks
|
return tasks
|
||||||
|
|
||||||
|
def delete_task(task_id: int):
|
||||||
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
|
cur.execute("DELETE FROM work_parser WHERE id = %s RETURNING *;", (task_id,))
|
||||||
|
deleted_task = cur.fetchone()
|
||||||
|
if deleted_task:
|
||||||
|
return {"message": f"Задача {task_id} удалена", "deleted_task": dict(deleted_task)}
|
||||||
|
else:
|
||||||
|
return {"message": f"Задача с id {task_id} не найдена"}
|
||||||
|
|
||||||
def update_task(task_id, **fields):
|
def update_task(task_id, **fields):
|
||||||
# dynamic update query generator
|
# dynamic update query generator
|
||||||
@@ -72,16 +76,59 @@ def update_task(task_id, **fields):
|
|||||||
cur.execute(f"UPDATE work_parser SET {set_sql} WHERE id = %s;", values)
|
cur.execute(f"UPDATE work_parser SET {set_sql} WHERE id = %s;", values)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_table_config_gpt():
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS config_gpt (
|
||||||
|
url TEXT PRIMARY KEY,
|
||||||
|
name VARCHAR(20),
|
||||||
|
promt TEXT
|
||||||
|
);
|
||||||
|
""")
|
||||||
|
print("Таблица config_gpt создана или уже существует")
|
||||||
|
|
||||||
|
class Source (BaseModel):
|
||||||
|
url: HttpUrl
|
||||||
|
name: str
|
||||||
|
promt: str
|
||||||
|
|
||||||
|
def update_promt(data: Source):
|
||||||
|
if isinstance(data, dict):
|
||||||
|
data = Source.model_validate(data)
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO config_gpt (url, name, promt)
|
||||||
|
VALUES (%s, %s, %s)
|
||||||
|
ON CONFLICT (url) DO UPDATE SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
promt = EXCLUDED.promt
|
||||||
|
""", (str(data.url), data.name, data.promt))
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
def get_promt(url):
|
||||||
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
|
cur.execute("SELECT promt FROM config_gpt WHERE url = %s", (url,))
|
||||||
|
promt = cur.fetchone()
|
||||||
|
return promt['promt']
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_promt():
|
||||||
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
||||||
|
cur.execute("SELECT * FROM config_gpt")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
sources = [{"url": row["url"], "name": row["name"], "promt": row["promt"]} for row in rows]
|
||||||
|
return {"sources": sources}
|
||||||
|
|
||||||
|
|
||||||
# Пример использования
|
# Пример использования
|
||||||
# if __name__ == "__main__":
|
# if __name__ == "__main__":
|
||||||
# create_table()
|
# create_table_config_gpt() # <-- раскомментировать эту строку
|
||||||
|
# update_promt({
|
||||||
# task_id = insert_task(status='queued', source_url='http://example.com', priority=5)
|
# "url": "http://epaper.hljnews.cn/hljrb/pc/layout",
|
||||||
# print("Создана задача с id:", task_id)
|
# "name": "source1",
|
||||||
|
# "promt": "Задача: Перевод на русский язык и тематическая фильтрация новостных статей из китайской прессы. \n Необходимо переводить текст статьи и определять, относится ли она к КНР по указанным темам: \n 1. Перевод\n Переведи предоставленный китайский текст на русский язык, сохранив оригинальный смысл, стиль и структуру.\n Текст:\n {content}\n -------------------------------------\n 2. Отбирай исключительно новости, прямо относящиеся к Китаю, его безопасности, соседним странам и территориям, влияющим на интересы Китая.\n Если не относится к Китаю — считаем, что статья НЕ подходит, и отдаем пустой JSON:\n {\"text\": \"\", \"pereskas\": \"\", \"title\": \"\", \"topics\": []}\n Если привязка есть — переходи к шагу 3. \n -------------------------------------\n 3. Тематическая классификация\n Определи, относится ли статья к одной или нескольким темам из списка:\n 1) Военные новости — конфликты, учения, мобилизация, закупки вооружений. \n 2) Пограничная деятельность — охрана границы, пограничные учения, строительство или модернизация пограничной инфраструктуры, техника для пограничников. \n 3) Пункты пропуска на границе с РФ — изменения режима работы, строительство, реконструкция, оборудование, логистика. \n 4) Пограничные реки — состояние рек, экология, инфраструктурные проекты, мониторинг. \n 5) Чрезвычайные ситуации — природные и техногенные происшествия, особенно затрагивающие пограничные реки и прилегающие земли. \n 6) Санитарно-эпидемиологическая обстановка — эпидемии, эпизоотии, эпифитотии, угрозы и меры предотвращения. \n 7) Индустриальные проекты (арктическое/антарктическое направление). \n 8) Индустриальные проекты в приграничных районах — заводы, производства, технопарки, новые технологии. \n 9) Инфраструктурные проекты в приграничных районах — дороги, мосты, транспорт, логистика. \n 10) Культура малочисленных народностей (нанайцы, монголы, уйгуры, нанайцы и хэчжэ) — политика, традиции, бытовая жизнь нанайцев, монголов, уйгуров, и хэчжэ (малочисленных народов).\n\n Отметь только те темы, которым статья действительно соответствует.\n\n -------------------------------------\n 4. Формат ответа \n Вернуть строго JSON без пояснений и дополнительных слов:\n {\n \"translation_text\": \"<перевод текста статьи на русский язык (дословный, точный и без сокращений ) >\",\n \"short_text\": \"<пересказ переведённого текста>\",\n \"title\": \"<краткая суть новости (1–2 предложения)>\",\n \"category\": \"<названий категорий, которым соответствует статья>\"\n }\n Если статья не относится ни к одной теме или не привязана к нужным регионам — вернуть:\n {\"translation_text\": \"\", \"short_text\": \"\", \"title\": \"\", \"category\": \"\"}"
|
||||||
# task = get_task(task_id)
|
# })
|
||||||
# print("Получена задача:", task)
|
|
||||||
|
|
||||||
# update_task(task_id, status='in_progress', started_at=datetime.utcnow(), attempts=1)
|
|
||||||
# task = get_task(task_id)
|
|
||||||
# print("Обновленная задача:", task)
|
|
||||||
Reference in New Issue
Block a user