ia integrada
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,2 @@
|
||||
.venv
|
||||
entorno
|
||||
__pypchache__
|
18
Dockerfile
Normal file
18
Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
# Usa una imagen oficial de Python
|
||||
FROM python:3.9
|
||||
|
||||
# Configurar el directorio de trabajo en el contenedor
|
||||
WORKDIR /app
|
||||
|
||||
# Copiar los archivos de la aplicación al contenedor
|
||||
COPY app/ /app/
|
||||
|
||||
# Instalar dependencias si es necesario (ajusta según tu requerimiento)
|
||||
RUN pip install mysql-connector-python schedule
|
||||
|
||||
# Copiar el archivo de crontab y configurarlo
|
||||
COPY crontab.txt /etc/cron.d/crontab
|
||||
RUN chmod 0644 /etc/cron.d/crontab && crontab /etc/cron.d/crontab
|
||||
|
||||
# Iniciar cron y ejecutar el script en segundo plano
|
||||
CMD cron && tail -f /var/log/cron.log
|
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
34
app/database.py
Normal file
34
app/database.py
Normal file
@ -0,0 +1,34 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
# Cargar variables del archivo .env
|
||||
load_dotenv()
|
||||
|
||||
# Configuración de MySQL
|
||||
MYSQL_HOST = os.getenv("MYSQL_HOST", "localhost")
|
||||
MYSQL_USER = os.getenv("MYSQL_USER", "root")
|
||||
MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "manabo")
|
||||
MYSQL_DATABASE = os.getenv("MYSQL_DATABASE", "noticias")
|
||||
MYSQL_PORT = os.getenv("MYSQL_PORT", "3306")
|
||||
|
||||
DATABASE_URL = f"mysql+pymysql://{MYSQL_USER}:{MYSQL_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}"
|
||||
|
||||
# Crear el motor de base de datos
|
||||
engine = create_engine(DATABASE_URL)
|
||||
|
||||
# Crear la sesión
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# Base para los modelos
|
||||
Base = declarative_base()
|
||||
|
||||
# Dependencia para obtener la sesión de la BD
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
12
app/main.py
Normal file
12
app/main.py
Normal file
@ -0,0 +1,12 @@
|
||||
from fastapi import FastAPI
|
||||
from .database import Base, engine
|
||||
from .routes import router
|
||||
|
||||
# Crear las tablas en MySQL si no existen
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Inicializar FastAPI
|
||||
app = FastAPI()
|
||||
|
||||
# Incluir rutas
|
||||
app.include_router(router)
|
14
app/models.py
Normal file
14
app/models.py
Normal file
@ -0,0 +1,14 @@
|
||||
from sqlalchemy import Column, Integer, String, Text, DateTime
|
||||
from datetime import datetime
|
||||
from .database import Base
|
||||
|
||||
class NewsItem(Base):
|
||||
__tablename__ = "news"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True) # ID autoincremental
|
||||
titulo = Column(String(255), unique=True, nullable=False)
|
||||
contenido = Column(Text, nullable=False)
|
||||
autor = Column(String(255), nullable=True)
|
||||
fuente = Column(String(255), nullable=True)
|
||||
fecha = Column(DateTime, default=datetime.utcnow)
|
||||
link = Column(String(500), unique=True, nullable=False)
|
41
app/routes.py
Normal file
41
app/routes.py
Normal file
@ -0,0 +1,41 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from .database import get_db
|
||||
from .models import NewsItem
|
||||
from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Modelo de datos de entrada
|
||||
class NewsItemCreate(BaseModel):
|
||||
titulo: str
|
||||
contenido: str
|
||||
autor: str | None = None
|
||||
fuente: str | None = None
|
||||
fecha: datetime | None = None
|
||||
link: str
|
||||
|
||||
@router.post("/news/")
|
||||
def create_news_item(item: NewsItemCreate, db: Session = Depends(get_db)):
|
||||
# Verificar si el título ya existe
|
||||
existing_item = db.query(NewsItem).filter(NewsItem.titulo == item.titulo).first()
|
||||
if existing_item:
|
||||
raise HTTPException(status_code=400, detail="El título ya existe en la base de datos")
|
||||
|
||||
# Crear nuevo objeto
|
||||
new_item = NewsItem(
|
||||
titulo=item.titulo,
|
||||
contenido=item.contenido,
|
||||
autor=item.autor,
|
||||
fuente=item.fuente,
|
||||
fecha=item.fecha or datetime.utcnow(),
|
||||
link=item.link
|
||||
)
|
||||
|
||||
db.add(new_item)
|
||||
db.commit()
|
||||
db.refresh(new_item)
|
||||
|
||||
return {"message": "Noticia creada con éxito", "id": new_item.id, "titulo": new_item.titulo}
|
||||
|
65
docker-compose.yml
Normal file
65
docker-compose.yml
Normal file
@ -0,0 +1,65 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
container_name: mi_scraper
|
||||
depends_on:
|
||||
- mysql
|
||||
environment:
|
||||
- MYSQL_HOST=mysql
|
||||
- MYSQL_USER=root
|
||||
- MYSQL_PASSWORD=admin123
|
||||
- MYSQL_DATABASE=scraper_db
|
||||
volumes:
|
||||
- ./app:/app
|
||||
restart: always
|
||||
networks:
|
||||
- metanet1
|
||||
|
||||
mysql:
|
||||
image: mysql:8
|
||||
container_name: mi_mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: admin123
|
||||
MYSQL_DATABASE: scraper_db
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
networks:
|
||||
- metanet1
|
||||
|
||||
metabase:
|
||||
image: metabase/metabase:latest
|
||||
container_name: metabase
|
||||
hostname: metabase
|
||||
volumes:
|
||||
- /dev/urandom:/dev/random:ro
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
MB_DB_TYPE: mysql
|
||||
MB_DB_DBNAME: scraper_db
|
||||
MB_DB_PORT: 3306
|
||||
MB_DB_USER: root
|
||||
MB_DB_PASS: admin123
|
||||
MB_DB_HOST: mysql
|
||||
networks:
|
||||
- metanet1
|
||||
depends_on:
|
||||
- mysql
|
||||
healthcheck:
|
||||
test: curl --fail -I http://localhost:3000/api/health || exit 1
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
networks:
|
||||
metanet1:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
@ -1,40 +1,27 @@
|
||||
import torch
|
||||
from transformers import AutoModelForCausalLM, AutoTokenizer
|
||||
import requests
|
||||
import json
|
||||
|
||||
# Carga el modelo y el tokenizador (ajusta la ruta si es local)
|
||||
modelo_nombre = "meta-llama/Llama-3-8B" # O usa un modelo local como "ruta/al/modelo"
|
||||
tokenizer = AutoTokenizer.from_pretrained(modelo_nombre)
|
||||
modelo = AutoModelForCausalLM.from_pretrained(modelo_nombre, torch_dtype=torch.float16, device_map="auto")
|
||||
def is_security_related(prompt):
|
||||
url = "http://localhost:11434/api/generate"
|
||||
data = {
|
||||
"model": "llama3",
|
||||
"prompt": f"Does the following topic relate to national defense, armed forces, police, espionage, or intelligence? Answer only with 'true' or 'false'. Topic: {prompt}",
|
||||
}
|
||||
|
||||
response = requests.post(url, json=data)
|
||||
|
||||
# Umbral de logits (ajusta según pruebas)
|
||||
UMBRAL_LOGITS = -1.0
|
||||
try:
|
||||
# Dividir la respuesta en líneas y parsear cada una
|
||||
for line in response.text.strip().split("\n"):
|
||||
json_data = json.loads(line)
|
||||
if "response" in json_data and json_data["response"].strip():
|
||||
return json_data["response"].strip().lower() == "true"
|
||||
|
||||
def evaluar_seguridad_nacional(texto):
|
||||
prompt = f"Evalúa si el siguiente texto está relacionado con defensa nacional, inteligencia, espionaje, fuerzas de seguridad, policía, ejército o fuerzas armadas. Responde solo con 'sí' o 'no'.\n\nTexto: {texto}\n\nRespuesta:"
|
||||
|
||||
# Tokenización
|
||||
inputs = tokenizer(prompt, return_tensors="pt").to(modelo.device)
|
||||
|
||||
# Inferencia con el modelo
|
||||
with torch.no_grad():
|
||||
outputs = modelo(**inputs)
|
||||
|
||||
# Obtener logits del último token generado
|
||||
logits = outputs.logits[:, -1, :] # Última posición
|
||||
|
||||
# Obtener puntuaciones para "sí" y "no"
|
||||
id_si = tokenizer.convert_tokens_to_ids("sí")
|
||||
id_no = tokenizer.convert_tokens_to_ids("no")
|
||||
|
||||
logit_si = logits[0, id_si].item() if id_si in tokenizer.get_vocab() else -float("inf")
|
||||
logit_no = logits[0, id_no].item() if id_no in tokenizer.get_vocab() else -float("inf")
|
||||
|
||||
# Decidir según los logits
|
||||
if logit_si > logit_no and logit_si > UMBRAL_LOGITS:
|
||||
return True
|
||||
except json.JSONDecodeError as e:
|
||||
print("JSON Decode Error:", e)
|
||||
|
||||
return False
|
||||
|
||||
# Ejemplo de uso
|
||||
texto_ejemplo = "El ejército ha desplegado unidades en la frontera para proteger la soberanía nacional."
|
||||
resultado = evaluar_seguridad_nacional(texto_ejemplo)
|
||||
print(f"¿El texto está relacionado con seguridad nacional? {resultado}")
|
||||
# Prueba con un ejemplo
|
||||
prompt = "La Laguna Tenerife inició la segunda vuelta de la Liga ACB defendiendo la cuarta plaza de la competición y hoy (12.00 horas), ante el Río Breogán, ese será su objetivo: ganar y seguir estando entre los cuatro primeros y vigilando a los otros tres rivales: Real Madrid, Valencia y Unicaja-, a un solo un triunfo de distancia.\nEn el partido de hoy, el conjunto aurinegro afronta un partido complicado, pues el Río Breogán viene jugando a un buen nivel. De los cuatro últimos partidos ha ganado tres -ante el Bilbao (76-71), Coruña (83-80) y Andorra (93-101)- y han caído frente al Baskonia (97-91).\nSituados en el puesto doce, el Breogán no será un rival fácil para los aurinegros que, pese a que también están jugando bien sobre todo en casa, saben que tendrán que estar al máximo nivel para poder sacar el partido adelante.\nGanar en confianza desde la defensa y jugar con equilibrio en ataque serán dos de los aspectos que deben conseguir los aurinegros en un enfrentamiento complicado, ante un rival que va hacia arriba y que sabe también las necesidades suyas para no complicarse en la zona de farolillo.\nEl entrenador del CB Canarias,Txus Vidorreta, tendrá a todos sus efectivos y, si el partido va como se espera, podrá seguir dando minutos en las rotaciones del equipo a Jaime Fernández, que ha podido entrenar.\nA dos jornadas de la disputa de la Copa del Rey en Gran Canaria, La Laguna Tenerife quiere irse preparando para esa cita con un primer enfrentamiento, en cuartos de final, contra el FC Barcelona.\nHoy será uno de esos “ensayos” importantes para saber cómo se encuentra el equipo para esa cita en la isla vecina, aunque ahora mismo el objetivo sigue estándo en la defensa de ese cuarto lugar de la Liga Endesa.\nUn desafío ante el cuadro de Luis Casimiro que necesitará igualmente del siempre entregado público insular, después de que la afición canarista confirmara desde el lunes pasado un nuevo sold out, el vigésimo primero consecutivo en partidos ACB entre el final de la campaña pasada y el vigente curso."
|
||||
print(is_security_related(prompt))
|
||||
|
302
news_data.json
302
news_data.json
File diff suppressed because one or more lines are too long
93
scrapper/autorsearcher.py
Normal file
93
scrapper/autorsearcher.py
Normal file
@ -0,0 +1,93 @@
|
||||
import json
|
||||
import requests
|
||||
import sys
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
def download_html_as_human(url):
|
||||
"""
|
||||
Descarga el HTML de una página web simulando un navegador real y usando cookies de sesión.
|
||||
"""
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
session = requests.Session()
|
||||
|
||||
response = session.get(url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.text
|
||||
else:
|
||||
return None
|
||||
|
||||
def extract_author_from_json(json_data):
|
||||
"""
|
||||
Extrae el autor del JSON-LD, incluso si está en una lista.
|
||||
"""
|
||||
if isinstance(json_data, list):
|
||||
for item in json_data:
|
||||
author = extract_author_from_json(item)
|
||||
if author:
|
||||
return author
|
||||
elif isinstance(json_data, dict):
|
||||
if 'author' in json_data:
|
||||
author_data = json_data['author']
|
||||
if isinstance(author_data, list):
|
||||
for author in author_data:
|
||||
if isinstance(author, dict) and 'name' in author:
|
||||
return author['name']
|
||||
elif isinstance(author_data, dict) and 'name' in author_data:
|
||||
return author_data['name']
|
||||
return None
|
||||
|
||||
def get_author_from_json_ld(soup):
|
||||
"""
|
||||
Extrae el autor de los metadatos JSON-LD, considerando estructuras con listas y objetos.
|
||||
"""
|
||||
scripts = soup.find_all('script', type='application/ld+json')
|
||||
for script in scripts:
|
||||
try:
|
||||
json_data = json.loads(script.string)
|
||||
author = extract_author_from_json(json_data)
|
||||
if author:
|
||||
return author
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
return None
|
||||
|
||||
def get_author_from_meta(soup):
|
||||
"""
|
||||
Extrae el autor de la etiqueta <meta> con el atributo property="nrbi:authors".
|
||||
"""
|
||||
meta_author = soup.find('meta', property='nrbi:authors')
|
||||
if meta_author and 'content' in meta_author.attrs:
|
||||
return meta_author['content']
|
||||
return None
|
||||
|
||||
def get_author_from_url(url):
|
||||
"""
|
||||
Busca el autor en los metadatos JSON-LD y en la etiqueta <meta> de una URL.
|
||||
"""
|
||||
html_content = download_html_as_human(url)
|
||||
if not html_content:
|
||||
print("error")
|
||||
return "No se pudo descargar la página."
|
||||
|
||||
soup = BeautifulSoup(html_content, 'html.parser')
|
||||
|
||||
author = get_author_from_json_ld(soup)
|
||||
if author:
|
||||
return author
|
||||
|
||||
author = get_author_from_meta(soup)
|
||||
if author:
|
||||
return author
|
||||
|
||||
return "Autor no encontrado en los metadatos."
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
url = sys.argv[1]
|
||||
print(get_author_from_url(url))
|
||||
else:
|
||||
print("Uso: python autorsearcher.py <URL>")
|
27
scrapper/iacorrector.py
Normal file
27
scrapper/iacorrector.py
Normal file
@ -0,0 +1,27 @@
|
||||
import requests
|
||||
import json
|
||||
|
||||
def is_security_related(prompt):
|
||||
url = "http://localhost:11434/api/generate"
|
||||
data = {
|
||||
"model": "llama3",
|
||||
"prompt": f"Does the following topic relate to national defense, armed forces, police, espionage, or intelligence? Answer only with 'true' or 'false'. Topic: {prompt}",
|
||||
}
|
||||
|
||||
response = requests.post(url, json=data)
|
||||
|
||||
try:
|
||||
# Dividir la respuesta en líneas y parsear cada una
|
||||
for line in response.text.strip().split("\n"):
|
||||
json_data = json.loads(line)
|
||||
if "response" in json_data and json_data["response"].strip():
|
||||
return json_data["response"].strip().lower() == "true"
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print("JSON Decode Error:", e)
|
||||
|
||||
return False
|
||||
|
||||
# Prueba con un ejemplo
|
||||
prompt = "La Laguna Tenerife inició la segunda vuelta de la Liga ACB defendiendo la cuarta plaza de la competición y hoy (12.00 horas), ante el Río Breogán, ese será su objetivo: ganar y seguir estando entre los cuatro primeros y vigilando a los otros tres rivales: Real Madrid, Valencia y Unicaja-, a un solo un triunfo de distancia.\nEn el partido de hoy, el conjunto aurinegro afronta un partido complicado, pues el Río Breogán viene jugando a un buen nivel. De los cuatro últimos partidos ha ganado tres -ante el Bilbao (76-71), Coruña (83-80) y Andorra (93-101)- y han caído frente al Baskonia (97-91).\nSituados en el puesto doce, el Breogán no será un rival fácil para los aurinegros que, pese a que también están jugando bien sobre todo en casa, saben que tendrán que estar al máximo nivel para poder sacar el partido adelante.\nGanar en confianza desde la defensa y jugar con equilibrio en ataque serán dos de los aspectos que deben conseguir los aurinegros en un enfrentamiento complicado, ante un rival que va hacia arriba y que sabe también las necesidades suyas para no complicarse en la zona de farolillo.\nEl entrenador del CB Canarias,Txus Vidorreta, tendrá a todos sus efectivos y, si el partido va como se espera, podrá seguir dando minutos en las rotaciones del equipo a Jaime Fernández, que ha podido entrenar.\nA dos jornadas de la disputa de la Copa del Rey en Gran Canaria, La Laguna Tenerife quiere irse preparando para esa cita con un primer enfrentamiento, en cuartos de final, contra el FC Barcelona.\nHoy será uno de esos “ensayos” importantes para saber cómo se encuentra el equipo para esa cita en la isla vecina, aunque ahora mismo el objetivo sigue estándo en la defensa de ese cuarto lugar de la Liga Endesa.\nUn desafío ante el cuadro de Luis Casimiro que necesitará igualmente del siempre entregado público insular, después de que la afición canarista confirmara desde el lunes pasado un nuevo sold out, el vigésimo primero consecutivo en partidos ACB entre el final de la campaña pasada y el vigente curso."
|
||||
print(is_security_related(prompt))
|
165
scrapper/webscrapper.py
Normal file
165
scrapper/webscrapper.py
Normal file
@ -0,0 +1,165 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
from googlenewsdecoder import gnewsdecoder
|
||||
from iacorrector import is_security_related # Importa la función desde iacorrector.py
|
||||
from datetime import datetime
|
||||
import pytz
|
||||
|
||||
|
||||
HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||
}
|
||||
|
||||
def get_author_from_script(url):
|
||||
"""
|
||||
Llama a autorsearcher.py con la URL de la noticia y devuelve el autor encontrado.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(["python", "autorsearcher.py", url], capture_output=True, text=True)
|
||||
author = result.stdout.strip()
|
||||
return author if author else "Desconocido"
|
||||
except Exception as e:
|
||||
print(f"Error al obtener el autor para {url}: {e}")
|
||||
return "Desconocido"
|
||||
|
||||
def get_url_from_google_news(url):
|
||||
interval_time = 1
|
||||
try:
|
||||
decoded_url = gnewsdecoder(url, interval=interval_time)
|
||||
|
||||
if decoded_url.get("status"):
|
||||
return decoded_url["decoded_url"]
|
||||
else:
|
||||
return "N/C"
|
||||
except Exception as e:
|
||||
print(f"Error occurred: {e}")
|
||||
|
||||
def get_article_content(url):
|
||||
"""
|
||||
Extrae el texto principal del artículo desde la URL final.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(url, headers=HEADERS)
|
||||
if response.status_code != 200:
|
||||
print(f"Error al acceder a {url}: Código {response.status_code}")
|
||||
return "No se pudo obtener el contenido"
|
||||
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
|
||||
# Buscar los elementos más comunes donde se almacena el contenido del artículo
|
||||
possible_containers = [
|
||||
soup.find("article"), # Etiqueta <article> (común en blogs y periódicos)
|
||||
soup.find("div", class_="post-content"), # Clases comunes en WordPress
|
||||
soup.find("div", class_="entry-content"),
|
||||
soup.find("div", class_="content"),
|
||||
soup.find("div", id="article-body")
|
||||
]
|
||||
|
||||
for container in possible_containers:
|
||||
if container:
|
||||
paragraphs = container.find_all("p")
|
||||
article_text = "\n".join(p.get_text(strip=True) for p in paragraphs)
|
||||
return article_text if article_text else "No se encontró contenido relevante"
|
||||
|
||||
return "No se encontró contenido relevante"
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error al extraer contenido de {url}: {e}")
|
||||
return "Error al extraer contenido"
|
||||
|
||||
def search_news(query):
|
||||
"""
|
||||
Busca noticias relacionadas con una palabra clave en Google News.
|
||||
"""
|
||||
base_url = f"https://news.google.com/rss/search?q={query.replace(' ', '+')}&hl=es&gl=ES&ceid=ES%3Aes"
|
||||
response = requests.get(base_url, headers=HEADERS)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"Error al acceder a la página para la consulta '{query}': {response.status_code}")
|
||||
return []
|
||||
|
||||
soup = BeautifulSoup(response.content, 'xml')
|
||||
articles = soup.find_all("item")
|
||||
news_list = []
|
||||
|
||||
for article in articles[:30]: # Limitar a los primeros 30 artículos
|
||||
try:
|
||||
title = article.title.get_text(strip=True)
|
||||
content = article.description.get_text(strip=True) if article.description else "Sin descripción"
|
||||
link = article.link.get_text(strip=True)
|
||||
source_info = article.source.get_text(strip=True) if article.source else "Desconocido"
|
||||
date = article.pubDate.get_text(strip=True) if article.pubDate else "Fecha no disponible"
|
||||
date_parsed = datetime.strptime(date, '%a, %d %b %Y %H:%M:%S GMT')
|
||||
date_parsed = date_parsed.replace(tzinfo=pytz.UTC)
|
||||
|
||||
# Obtener la URL final del artículo
|
||||
final_url = get_url_from_google_news(link)
|
||||
|
||||
# Obtener el autor usando autorsearcher.py
|
||||
author = get_author_from_script(final_url)
|
||||
content = get_article_content(final_url)
|
||||
|
||||
# Verificar si el artículo es válido usando iacorrector
|
||||
if is_security_related(content): # Solo si el artículo es válido
|
||||
news_item = {
|
||||
"titulo": title,
|
||||
"contenido": content,
|
||||
"autor": author,
|
||||
"fuente": source_info,
|
||||
"fecha": date_parsed.isoformat(),
|
||||
"link": final_url # Guardamos la URL final en lugar de la de Google News
|
||||
}
|
||||
news_list.append(news_item)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error al procesar un artículo para '{query}': {e}")
|
||||
|
||||
return news_list
|
||||
|
||||
def insertar_datos(news_item):
|
||||
API_URL = "http://127.0.0.1:8001/news/"
|
||||
|
||||
response = requests.post(API_URL, json=news_item)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"Noticia '{news_item['titulo']}' creada con éxito.")
|
||||
else:
|
||||
print(f"Error al insertar '{news_item['titulo']}':", response.status_code, response.json())
|
||||
|
||||
|
||||
|
||||
|
||||
def search_from_keywords_file():
|
||||
"""
|
||||
Lee palabras clave del archivo 'keywords.txt' y realiza búsquedas para cada una.
|
||||
"""
|
||||
all_news = [] # Lista para almacenar todas las noticias recolectadas
|
||||
|
||||
try:
|
||||
with open("keywords.txt", "r", encoding="utf-8") as file:
|
||||
keywords = file.readlines()
|
||||
|
||||
# Eliminar posibles saltos de línea y espacios extra
|
||||
keywords = [keyword.strip() for keyword in keywords]
|
||||
|
||||
for keyword in keywords:
|
||||
print(f"\nBuscando noticias sobre: {keyword}")
|
||||
news_list = search_news(keyword)
|
||||
all_news.extend(news_list) # Añadir las noticias encontradas para cada palabra clave
|
||||
time.sleep(2) # Pausa para evitar bloqueos por demasiadas solicitudes en poco tiempo
|
||||
|
||||
# Guardar todas las noticias en un archivo JSON
|
||||
for news in all_news:
|
||||
insertar_datos(news)
|
||||
|
||||
except FileNotFoundError:
|
||||
print("No se encontró el archivo 'keywords.txt'.")
|
||||
except Exception as e:
|
||||
print(f"Error al leer el archivo 'keywords.txt': {e}")
|
||||
|
||||
# Ejecutar la búsqueda desde el archivo
|
||||
search_from_keywords_file()
|
@ -5,6 +5,7 @@ import os
|
||||
import time
|
||||
import subprocess
|
||||
from googlenewsdecoder import gnewsdecoder
|
||||
from iacorrector import is_security_related # Importa la función desde iacorrector.py
|
||||
|
||||
HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||
@ -90,23 +91,25 @@ def search_news(query):
|
||||
source_info = article.source.get_text(strip=True) if article.source else "Desconocido"
|
||||
date = article.pubDate.get_text(strip=True) if article.pubDate else "Fecha no disponible"
|
||||
|
||||
|
||||
# Obtener la URL final del artículo
|
||||
final_url = get_url_from_google_news(link)
|
||||
|
||||
# Obtener el autor usando autorsearcher.py
|
||||
author = get_author_from_script(final_url)
|
||||
content = get_article_content(final_url)
|
||||
news_item = {
|
||||
"titulo": title,
|
||||
"contenido": content,
|
||||
"autor": author,
|
||||
"fuente": source_info,
|
||||
"fecha": date,
|
||||
"link": final_url # Guardamos la URL final en lugar de la de Google News
|
||||
}
|
||||
|
||||
news_list.append(news_item)
|
||||
# Verificar si el artículo es válido usando iacorrector
|
||||
if is_security_related(content): # Solo si el artículo es válido
|
||||
news_item = {
|
||||
"titulo": title,
|
||||
"contenido": content,
|
||||
"autor": author,
|
||||
"fuente": source_info,
|
||||
"fecha": date,
|
||||
"link": final_url # Guardamos la URL final en lugar de la de Google News
|
||||
}
|
||||
news_list.append(news_item)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error al procesar un artículo para '{query}': {e}")
|
||||
|
||||
|
Reference in New Issue
Block a user