diff --git a/main.py b/main.py index e792e21..949fa3f 100644 --- a/main.py +++ b/main.py @@ -9,7 +9,6 @@ from datetime import datetime, timedelta import random import zipfile -# from rarfile import RarFile import tempfile # Сторонние библиотеки (third-party) @@ -17,17 +16,18 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from bs4 import BeautifulSoup from contextlib import asynccontextmanager from docx import Document +from newspaper import Article 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 +from pydantic import BaseModel, HttpUrl +from urllib.parse import urljoin, urlparse, urldefrag import uvicorn import requests # Локальные импорты -import settings_work as sw +# import settings_work as sw import work_parser as wp DOCUMENTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "documents") @@ -206,21 +206,9 @@ def extract_text_from_url(url, timeout=10, verify=True): return "\n".join(content_text), time_t # Общий запрос на GPT -def gpt_response_message(content, ist_number=1): +def gpt_response_message(content: str, name_promt: str): - # 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: - url_ist = "http://epaper.hljnews.cn/hljrb/pc/layout" - else: - url_ist = "https://def.ltn.com.tw/breakingnewslist" - - contentGPT = wp.get_promt(url_ist).replace('{content}', content) + contentGPT = wp.get_promt(name_promt).replace('{content}', content) url = 'http://45.129.78.228:8484' #10.8.0.14:5500 params = {'text': contentGPT} @@ -242,15 +230,6 @@ def gpt_response_message(content, ist_number=1): logger.info(f"Привышен лимит запросов {max_retries}") return "" -# перезапуск сервиса GPT при неудачных попытках запроса -# def restart_service(service_name): -# try: -# subprocess.run(['sudo', 'systemctl', 'restart', service_name], check=True) -# time.sleep(30) -# print(f"Сервис {service_name} успешно перезапущен") -# except subprocess.CalledProcessError: -# print(f"Не удалось перезапустить сервес {service_name}") - # Общие функции проверки ссылок def check_url(url): print(url) @@ -342,7 +321,6 @@ def start_pars_one_istochnik(data_init=""): for page_number in range(1, 9): 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=url, started_at=datetime.utcnow()) print(f"Сбор href из: {url}") @@ -358,7 +336,7 @@ def start_pars_one_istochnik(data_init=""): print(f"Страница {page_number} [{i}/{len(hrefs)}] parsing {link}") text = extract_text_from_url_one(link) if len(text) >= 100: - response_text = gpt_response_message(text, ist_number=2) + response_text = gpt_response_message(text, url_ist = "http://epaper.hljnews.cn/hljrb/pc/layout") print(response_text) if response_text: 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) @@ -386,7 +364,7 @@ def start_pars_two_istochnik(): try: text, time_text = extract_text_from_url(hrefs) if len(text) >= 100: - response_text = gpt_response_message(text) + response_text = gpt_response_message(text, url_ist = "https://def.ltn.com.tw/breakingnewslist") print(response_text) if response_text: 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) @@ -395,6 +373,64 @@ def start_pars_two_istochnik(): wp.update_task(task_id, status='completed', finished_at=datetime.utcnow()) +#Функции start любого источника +def start_pars_all_istochnik(url:str, promt:str): + task_id = wp.insert_task(status='queued', source_url=url) + + try: + response = requests.get(url) + response.raise_for_status() + except requests.RequestException: + return set() + + soup = BeautifulSoup(response.text, 'html.parser') + base_domain = urlparse(url).netloc + + # links = set() + for a_tag in soup.find_all('a', href=True): + href = a_tag['href'].strip() + if not href or href.startswith('mailto:') or href.startswith('javascript:'): + continue + + # Приведение к абсолютному URL и удаление якорей (#...) + abs_url = urljoin(url, href) + abs_url, _ = urldefrag(abs_url) + parsed = urlparse(abs_url) + + # Фильтр: ссылка должна быть на тот же домен + if parsed.netloc != base_domain: + continue + + # Фильтрация по ключевым словам (пример для новостных сайтов) + # path_lower = parsed.path.lower() + # if any(keyword in path_lower for keyword in ['/news/', 'article', '2023', '2024', '/blog/', '/post/']): + + print(f"Парсинг {abs_url}") + try: + article = Article(abs_url) + article.download() + article.parse() + + if len(article.text) > 200 and article.publish_date: + time_text = article.publish_date.strftime("%Y/%m/%d %H:%M:%S") + print("URL:", abs_url) + print("Заголовок:", article.title) + print("Дата публикации:", time_text) + print("Текст статьи:", article.text) + response_text = gpt_response_message(str(article.text), promt) + print(response_text) + if response_text: + update_bd_and_create_document(response_text=response_text, article_date=time_text, url=abs_url, parsed_at=str(dt.now()), original_text=article.text, other=url) + + except Exception as e: + print(f"Ошибка при обработке статьи {abs_url}: {e}") + logger.info(f"Ошибка при обработке статьи {abs_url}: {e}") + continue # Продолжаем со следующей статьей + + wp.update_task(task_id, status='completed', finished_at=datetime.utcnow()) + +# start_pars_all_istochnik("https://www.asahi.com", "japan") + # Функции для автоматического запуска def scheduled_parser_1(): start_pars_one_istochnik() @@ -406,16 +442,25 @@ class ParserOneRequest(BaseModel): time: str @app.post("/parser_1", summary="Запуск процесса парсинга первого источника") -async def process_data(data: ParserOneRequest, background_tasks: BackgroundTasks): +async def process_parser_one_ist(data: ParserOneRequest, background_tasks: BackgroundTasks): istochnik = data.time.split("-") - background_tasks.add_task(start_pars_one_istochnik, istochnik) + background_tasks.add_task(start_pars_one_istochnik(istochnik)) return {"message": "Процесс парсинга 1 источника запущен"} @app.post("/parser_2" , summary="Запуск процеса парсинга второго источника") -async def process_data_gpt(background_tasks: BackgroundTasks): +async def process_parser_two_ist(background_tasks: BackgroundTasks): background_tasks.add_task(start_pars_two_istochnik) return {"message": "Процесс парсинга 2 источника запущен"} +class Parserall(BaseModel): + url: HttpUrl + promt: str + +@app.post("/parser_all" , summary="Запуск процеса парсинга любого источника") +async def process_parser_all_ist(url: Parserall, background_tasks: BackgroundTasks): + background_tasks.add_task(start_pars_all_istochnik(str(url.url), url.promt)) + return {"message": "Процесс парсинга любого источника запущен"} + # GET метод для получения @app.get("/get_tasks_offset", summary="Метод получения задач парсинга") def get_tasks_offset(limit: int = Query(10, gt=0), offset: int = Query(0, ge=0)): @@ -536,7 +581,7 @@ async def download_all(dates: DownloadRange, background_tasks: BackgroundTasks): return response -@app.get("/logs") +@app.get("/logs", summary="Показать логи") def get_logs(): with open("app.log", "r") as file: lines = file.readlines()[-10:] # последние 10 строк diff --git a/work_parser.py b/work_parser.py index 8baa4bb..4c8af04 100644 --- a/work_parser.py +++ b/work_parser.py @@ -1,6 +1,6 @@ import psycopg2 from psycopg2.extras import RealDictCursor -from pydantic import BaseModel, HttpUrl +from pydantic import BaseModel # Подключение к БД (укажи свои параметры) conn = psycopg2.connect( @@ -90,7 +90,7 @@ def create_table_config_gpt(): print("Таблица config_gpt создана или уже существует") class Source (BaseModel): - url: HttpUrl + url: str name: str promt: str @@ -105,12 +105,12 @@ def update_promt(data: Source): ON CONFLICT (url) DO UPDATE SET name = EXCLUDED.name, promt = EXCLUDED.promt - """, (str(data.url), data.name, data.promt)) + """, (data.url, data.name, data.promt)) conn.commit() -def get_promt(url): +def get_promt(promt_name_url): with conn.cursor(cursor_factory=RealDictCursor) as cur: - cur.execute("SELECT promt FROM config_gpt WHERE url = %s", (url,)) + cur.execute("SELECT promt FROM config_gpt WHERE url = %s", (promt_name_url,)) promt = cur.fetchone() return promt['promt'] @@ -126,9 +126,10 @@ def get_all_promt(): # Пример использования # if __name__ == "__main__": - # create_table_config_gpt() # <-- раскомментировать эту строку - # update_promt({ - # "url": "http://epaper.hljnews.cn/hljrb/pc/layout", - # "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\": \"\"}" - # }) \ No newline at end of file +# # create_table_config_gpt() # <-- раскомментировать эту строку +# update_promt({ +# "url": "http://korei", +# "name": "Корея", +# "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\": \"\"}" +# }) + # print(get_promt("japan")) \ No newline at end of file