-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
223 lines (185 loc) · 7.61 KB
/
Copy pathapp.py
File metadata and controls
223 lines (185 loc) · 7.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"""
API de Detección de Fake News - Versión para Producción
Optimizada para Heroku y RapidAPI
"""
import os
import pickle
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from dotenv import load_dotenv
import uvicorn
from typing import Optional
import requests
from bs4 import BeautifulSoup
# Cargar variables de entorno
load_dotenv()
app = FastAPI(
title="Fake News Detection API",
description="API para detectar noticias falsas usando Machine Learning. Proporciona una URL o texto y detecta si es fake news.",
version="1.0.0",
docs_url="/api/docs",
redoc_url="/api/redoc",
openapi_url="/api/openapi.json"
)
# Agregar CORS para permitir peticiones desde diferentes orígenes
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Cargar los modelos pre-entrenados
MODEL_PATH = os.environ.get("MODEL_PATH", "./")
try:
classifier_path = os.path.join(MODEL_PATH, "classifier.pkl")
vectorizer_path = os.path.join(MODEL_PATH, "vectorizer.pkl")
encoder_path = os.path.join(MODEL_PATH, "encoder.pkl")
with open(classifier_path, "rb") as f:
classifier = pickle.load(f)
with open(vectorizer_path, "rb") as f:
vectorizer = pickle.load(f)
with open(encoder_path, "rb") as f:
encoder = pickle.load(f)
print("✅ Modelos cargados exitosamente")
except Exception as e:
print(f"⚠️ Error cargando modelos: {e}")
classifier, vectorizer, encoder = None, None, None
# Definir estructuras de petición
class NewsRequest(BaseModel):
text: str = Field(..., description="Texto del artículo a analizar", example="Título del artículo. Contenido del artículo...")
class URLRequest(BaseModel):
url: str = Field(..., description="URL de la noticia a analizar", example="https://ejemplo.com/noticia")
class NewsResponse(BaseModel):
is_fake: bool
confidence: float
label: str
class_probabilities: Optional[dict] = None
# Rutas
@app.get("/", tags=["Info"])
def welcome():
"""Endpoint de bienvenida"""
return {
"message": "Bienvenido a la API de Detección de Fake News",
"version": "1.0.0",
"endpoints": {
"detect_text": "POST /api/detect - Analizar texto directo",
"detect_url": "POST /api/detect-url - Analizar artículo desde URL",
"health": "GET /api/health - Estado de la API",
"docs": "/api/docs - Documentación interactiva (Swagger)"
}
}
@app.get("/api/health", tags=["Status"])
def health_check():
"""Verifica el estado de la API y carga de modelos"""
models_loaded = classifier is not None and vectorizer is not None
return {
"status": "healthy" if models_loaded else "unhealthy",
"models_loaded": models_loaded
}
@app.post("/api/detect", response_model=NewsResponse, tags=["Detection"])
def detect_fake_news(request: NewsRequest):
"""
Detecta si un texto es fake news o no.
- **text**: El título + contenido del artículo a analizar
Retorna:
- **is_fake**: True si es fake news, False si es real
- **confidence**: Confianza de la predicción (0-1)
- **label**: Etiqueta de clasificación
- **class_probabilities**: Probabilidades para cada clase
"""
if not classifier or not vectorizer:
raise HTTPException(
status_code=503,
detail="Los modelos no están cargados. Sistema no disponible."
)
try:
# Limpiar y vectorizar el texto
text = request.text.strip()
if len(text) < 10:
raise HTTPException(status_code=400, detail="El texto debe tener al menos 10 caracteres")
vectorized_text = vectorizer.transform([text])
# Hacer la predicción
prediction = classifier.predict(vectorized_text)[0]
probabilities = classifier.predict_proba(vectorized_text)[0]
# Mapear predicción a etiqueta
is_fake = bool(prediction == 0)
label = "FAKE NEWS" if is_fake else "NOTICIA REAL"
confidence = float(max(probabilities))
# Obtener probabilidades por clase
class_probs = {
"fake": float(probabilities[0]),
"real": float(probabilities[1]) if len(probabilities) > 1 else 0.0
}
return {
"is_fake": is_fake,
"confidence": round(confidence, 4),
"label": label,
"class_probabilities": class_probs
}
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error al procesar el texto: {str(e)}")
@app.post("/api/detect-url", response_model=NewsResponse, tags=["Detection"])
def detect_fake_news_from_url(request: URLRequest):
"""
Detecta si un artículo desde una URL es fake news.
- **url**: URL del artículo a analizar
Extrae automáticamente el título y contenido de la página web.
"""
if not classifier or not vectorizer:
raise HTTPException(
status_code=503,
detail="Los modelos no están cargados. Sistema no disponible."
)
try:
# Descargar la página
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(request.url, headers=headers, timeout=10)
response.raise_for_status()
# Parsear HTML
soup = BeautifulSoup(response.content, 'html.parser')
# Extraer título y contenido
title = ""
if soup.title:
title = soup.title.string or ""
elif soup.find('h1'):
title = soup.find('h1').get_text() or ""
# Extraer texto del contenido
content = " ".join([p.get_text() for p in soup.find_all('p')])
if not content:
raise HTTPException(status_code=400, detail="No se pudo extraer contenido de la URL")
combined_text = (title + " " + content).strip()
if len(combined_text) < 10:
raise HTTPException(status_code=400, detail="El contenido extraído es muy corto")
# Vectorizar y predecir
vectorized_text = vectorizer.transform([combined_text])
prediction = classifier.predict(vectorized_text)[0]
probabilities = classifier.predict_proba(vectorized_text)[0]
is_fake = bool(prediction == 0)
label = "FAKE NEWS" if is_fake else "NOTICIA REAL"
confidence = float(max(probabilities))
class_probs = {
"fake": float(probabilities[0]),
"real": float(probabilities[1]) if len(probabilities) > 1 else 0.0
}
return {
"is_fake": is_fake,
"confidence": round(confidence, 4),
"label": label,
"class_probabilities": class_probs
}
except requests.RequestException as e:
raise HTTPException(status_code=400, detail=f"Error descargando la URL: {str(e)}")
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error procesando URL: {str(e)}")
# Configuración para ejecutar localmente o en el deploy
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8000))
uvicorn.run("app:app", host="0.0.0.0", port=port)