FLUJOS/FLUJOS_DATOS/NOTICIAS/DOCS/arquitectura_main.txt
CAPITANSITO a40b946163 Initial commit - FLUJOS codebase (production branch)
Includes: FLUJOS app (Node/Flask/Python), FLUJOS_DATOS scripts (scrapers, Keras, Django)
Excludes: MongoDB, scraped data, Wikipedia/WikiLeaks dumps, Python venv, node_modules
2026-03-31 14:10:02 +02:00

497 lines
25 KiB
Text

┌───────────────────────────────────────────────────┐
│ main() │
└──────────────┬────────────────────────────────────┘
Lista de URLs de medios y leaks
┌───────────────────────────────────────────────────────────────┐
│ register_processed_notifications(base_folder, urls) │
│ - Crea/lee processed_articles.txt │
│ - Filtra duplicados │
└──────────────┬───────────────────────────────────────────────┘
│ urls_to_process
┌─────────────────┴───────────────────────────┐
│ │
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ explore_and_extract_articles │ │ explore_wayback_machine │
│ (crawling + extracción) │ │ (consulta Wayback y extrae) │
└────────────┬─────────────────┘ └────────────┬─────────────────┘
│ │
│ guarda │ guarda
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ articulos/ (txt) │ │ articulos/ (txt) │
└────────────────────┘ └────────────────────┘
▲ ▲
│ │
▼ ▼
┌────────────────────┐ ┌────────────────────┐
│ archivos/ (bin) │ ← descarga │ (no aplica) │
└────────────────────┘ └────────────────────┘
┌────────────────────────────────────────────────┐
│ process_files(archivos/ → tokenized/) │
│ tokenize_all_articles(articulos/ → tokenized/) │
└──────────────┬─────────────────────────────────┘
┌────────────────────┐
│ tokenized/ (ids) │ ← ids BERT (máx 512)
└────────────────────┘
┌──────────────────────────┐
│ get_folder_info + logs │
└──────────────────────────┘
Entrada: url raíz, folders, processed_urls, size_limit, depth=0..6
┌──────────────────────────┐
│ HTMLSession().get(url) │
│ response.html.render() │ ← render JS (headless)
└─────────────┬────────────┘
Conjunto de absolute_links
┌────────────────┴─────────────────┐
│ │
Si extensión conocida Si página HTML genérica
(.pdf .csv .txt .xlsx .docx (sin extensión/otra cosa)
.html .md .zip) │
│ ▼
▼ ┌──────────────────────────────┐
┌──────────────────────────┐ │ extract_and_save_article() │
│ download_and_save_file() │ │ - parse <title>, <p> │
│ → archivos/ │ │ - translate → clean → txt │
└───────────┬──────────────┘ │ - guardar en articulos/ │
│ └──────────────┬───────────────┘
│ (tras cada acción) │
▼ ▼ (recursivo)
get_folder_info(articulos/, archivos/) explore_and_extract_articles(link, depth+1)
¿total_size >= 50GB? ──► Sí: detener │ No: continuar
archivos/ ──────────────────────────────────────────────────────┐
Para cada archivo según extensión:
┌─────────────────────────────────────────────────────────────┐
│ .pdf → read_pdf() → texto │
│ .csv → read_csv() → texto │
│ .txt → open().read() → texto │
│ .docx → read_docx() → texto │
│ .xlsx → read_xlsx() → texto │
│ .zip → read_zip() → texto concatenado │
│ .html/.md → read_html_md() → format_content()(*) → texto │
└─────────────────────────────────────────────────────────────┘
translate_text(deep_translator → 'es')
clean_text( BeautifulSoup strip + minúsculas
+ quita URLs + solo letras/espacios
+ colapsa espacios + STOPWORDS_ES )
tokenize_and_save(texto_limpio, filename, tokenized/)
tokenized/ contiene IDs BERT (máx 512)
(*) Nota: `format_content()` está vacío en tu snippet; hoy actúa como no-op.
articulos/ (txt limpios/es) ─────► para cada .txt:
tokenizer.encode(text, max_length=512, add_special_tokens=True)
'id id id ...' → escribe en tokenized/ con
mismo nombre de archivo
clean_filename(name)
- reemplaza \ / * ? : " < > | por "_"
- espacios → '_'
- corta a 100 chars
register_processed_notifications(base_folder, urls)
- lee base_folder/processed_articles.txt (si existe)
- devuelve solo URLs no vistas
- añade nuevas al final (append)
explore_wayback_machine(url)
- GET http://archive.org/wayback/available?url=...
- si hay 'closest' → extract_and_save_article(archive_url)
translate_text(text) ──► GoogleTranslator(auto→'es') ──► texto_en_es
clean_text():
1) quita CDATA variantes: <! [ CDATA [ ... ] ] >
2) BeautifulSoup → .get_text()
3) lower()
4) elimina URLs (regex http\S+)
5) deja solo letras españolas y espacio (regex)
6) colapsa espacios
7) filtra STOPWORDS (lista extensa ES)
/var/www/theflows.net/flujos/FLUJOS_DATOS/NOTICIAS/
├── articulos/ (txt limpios en ES, de páginas HTML/Wayback)
├── archivos/ (descargas crudas: pdf, csv, xlsx, docx, zip, html, md, txt)
├── tokenized/ (mismos nombres, contenido = IDs BERT separados por espacio)
└── processed_articles.txt (histórico de URLs procesadas)
logging.basicConfig(level=INFO)
- Traza etapas (descarga, extracción, tokenización)
- Resumen final:
* nº ficheros en articulos/, archivos/, tokenized/
* tamaños totales (MB)
- Cortafuegos de tamaño: 50 GB entre articulos/ + archivos/
[ main() ]
register_processed_notifications()
│ → filtra URLs ya procesadas
+---------------------------+
| urls_to_process (nuevas) |
+---------------------------+
explore_and_extract_articles()
├─► Si enlace a archivo (.pdf, .csv, .txt, .xlsx, .docx, .html, .md, .zip)
│ └─► download_and_save_file() → guarda en /archivos
├─► Si enlace HTML → extract_and_save_article()
│ ├─ traduce (translate_text)
│ ├─ limpia (clean_text)
│ └─ guarda .txt en /articulos
└─► Recursivo hasta max_depth o límite 50 GB
explore_wayback_machine() → descarga versión archivada si existe
process_files(/archivos → /tokenized)
│ ├─ read_pdf/csv/docx/xlsx/zip/html_md/txt
│ ├─ translate_text()
│ ├─ clean_text()
│ └─ tokenize_and_save() con BERT
tokenize_all_articles(/articulos → /tokenized)
│ └─ encode con BERT en IDs separados por espacio
get_folder_info() → logs resumen final
===========================================================
Flujo simplificado de procesamiento de un archivo
===========================================================
archivo descargado
read_*() según extensión
translate_text() → GoogleTranslator(auto→es)
clean_text()
├─ elimina CDATA y HTML
├─ minúsculas, sin URLs, solo letras/es
├─ colapsa espacios
└─ filtra stopwords ES
tokenize_and_save()
├─ tokenizer.encode(max 512 tokens)
└─ guarda IDs BERT en /tokenized
===========================================================
Estructura de carpetas
===========================================================
NOTICIAS/
├── articulos/ ← .txt limpios en español
├── archivos/ ← binarios crudos descargados
├── tokenized/ ← tokens BERT (IDs)
└── processed_articles.txt ← historial de URLs
===========================================================
Control y límites
===========================================================
- Profundidad máxima de crawling: max_depth = 6
- Tamaño combinado artículos+archivos: límite 50 GB
- Evita duplicados con processed_articles.txt
- Logs detallados en consola
████████████████████████████████████ FLUJOS: ESQUEMA TÉCNICO (ASCII) ████████████████████████████████████
[ ENTORNO / DEPENDENCIAS ]
- GoogleTranslator (deep_translator) → API web de Google Translate (auto→es)
- requests / requests_html.HTMLSession → HTTP + renderizado JS (chromium/headless)
- BeautifulSoup (bs4) → Parseo HTML, extracción de texto
- PyPDF2.PdfReader → Extracción texto de PDFs (si embebido/copiable)
- openpyxl → Lectura de .xlsx
- python-docx (docx) → Lectura de .docx
- zipfile → Descompresión y lectura (texto) de ficheros en ZIP
- html2text (no usado aquí en format_content)→ [placeholder]
- transformers.BertTokenizer → Tokenizador BERT ES (dccuchile/bert-base-spanish-wwm-cased)
- tqdm, logging, os, re, json, time, csv, hashlib, urllib.parse (urlparse/urljoin) → utilidades
[ ESTRUCTURA DE CARPETAS (I/O) ]
/var/www/theflows.net/flujos/FLUJOS_DATOS/NOTICIAS/
├── articulos/ (TXT limpios en español, derivados de HTML/Wayback)
├── archivos/ (descargas crudas: .pdf .csv .txt .xlsx .docx .html .md .zip)
├── tokenized/ (TXT con IDs de tokens BERT, máx 512 tokens por archivo)
└── processed_articles.txt (histórico de URLs ya procesadas → evita duplicados)
=============================================================================================================
[ main() ]
- Inicializa: URLs objetivo, rutas base, límite de 50GB, carpetas si no existen
- Flujo:
1) urls_to_process = register_processed_notifications(base_folder, urls)
2) Para url en urls_to_process:
a) explore_and_extract_articles(url, articulos/, archivos/, processed_urls, size_limit)
b) explore_wayback_machine(url, articulos/)
3) process_files(archivos/ → tokenized/)
4) tokenize_all_articles(articulos/ → tokenized/)
5) get_folder_info() sobre cada carpeta y logging resumen
- Side effects: Escritura en articulos/, archivos/, tokenized/, processed_articles.txt; logs INFO
=============================================================================================================
[ register_processed_notifications(base_folder, urls) ]
- Lee/crea processed_articles.txt
- Devuelve: lista de URLs no presentes (nuevas)
- Efectos:
* append de nuevas URLs al fichero
- Riesgos:
* Fichero grande con el tiempo (puede optimizarse a DB/Set persistente)
* No bloquea concurrencia (carreras si hay procesos paralelos)
=============================================================================================================
[ explore_and_extract_articles(url, articulos/, archivos/, processed_urls, size_limit, depth=0..6) ]
- Hace GET + render JS:
session = HTMLSession(); response = session.get(url); response.html.render()
- Obtiene absolute_links (con JS resuelto)
- Para cada link:
* Si ya está en processed_urls → skip
* Marca link como procesado (set in-memory)
* Detecta extensión: [.pdf .csv .txt .xlsx .docx .html .md .zip]
- Coincide → download_and_save_file(link, archivos/)
- mailto:/tel: → ignora
- Otro/HTML → extract_and_save_article(link, articulos/); recursión depth+1
- Control de tamaño:
* get_folder_info(articulos/) + get_folder_info(archivos/) → si ≥ 50GB → cortar
- Notas:
* render() requiere Chromium instalado y recursos; costoso en CPU/RAM
* Cuidado con sitios SPA/anti-bot; timeouts (30s)
* max_depth=6 limita explosión de enlaces; se puede poner filtro de dominio
=============================================================================================================
[ download_and_save_file(url, archivos/) ]
- Descarga streaming (chunk 8192) con requests.get(url, stream=True, timeout=30)
- Filename = clean_filename(último segmento URL) || 'archivo_descargado'
- Escribe binario en archivos/
- Errores:
* response.status_code != 200 → log
* timeouts/conexión → log
- Seguridad:
* No ejecuta nada; sólo guarda
* Riesgo: HTML/JS guardado como .html/.md puede contener scripts (pero se procesa como texto después)
=============================================================================================================
[ extract_and_save_article(url, articulos/) ]
- GET simple (requests.get, timeout=30)
- Parse HTML: <title> y todos los <p> → concatena texto
- Procesa:
* translate_text() (auto→es)
* clean_text()
- Nombre archivo:
* title → clean_filename(title) + '.txt'
* fallback: último segmento de path URL + '.txt'
- Guarda .txt en articulos/
- Riesgos:
* Páginas con contenido en divs/aria/role no capturado por <p> → menos texto
* Limitaciones del traductor (cuotas, longitudes, errores temporales)
* Si content vacío → log y skip
=============================================================================================================
[ explore_wayback_machine(url, articulos/) ]
- Consulta API: http://archive.org/wayback/available?url={url}
- Si hay 'closest' → archive_url → extract_and_save_article(archive_url)
- Usos:
* Resiliencia ante 404/robots o contenido rotativo
- Riesgos:
* No todas las páginas están archivadas
* Rate limits
=============================================================================================================
[ process_files(archivos/, tokenized/) ]
- Itera archivos descargados por extensión:
.pdf → read_pdf() (PdfReader.extract_text por página)
.csv → read_csv() (csv.reader → " ".join(row))
.txt → open().read() (texto tal cual)
.docx → read_docx() (docx.Document → concat párrafos)
.xlsx → read_xlsx() (openpyxl → concat celdas por fila)
.zip → read_zip() (abre cada entrada, decode utf-8 ignore)
.html/.md → read_html_md() → format_content() [*format_content está vacía → no-op]
- Para cada contenido (si hay texto):
translate_text() → clean_text() → tokenize_and_save(cleaned, filename, tokenized/)
- Notas:
* Archivos binarios dentro del ZIP no-UTF8 se ignoran por decode errors (ignore)
* PDF sin capa de texto → extract_text() puede devolver None
* XLSX grande → memoria/tiempo; iter_rows() es razonable
=============================================================================================================
[ tokenize_all_articles(articulos/, tokenized/) ]
- Para cada .txt en articulos/:
tokenizer.encode(text, truncation=True, max_length=512, add_special_tokens=True)
→ 'ids' separados por espacio → guarda con mismo filename en tokenized/
- Notas:
* Truncation a 512 tokens: se pierde contenido largo (considerar sliding windows)
* add_special_tokens=True añade [CLS]/[SEP]
=============================================================================================================
[ tokenize_and_save(text, filename, tokenized/) ]
- Encapsula la llamada a tokenizer.encode(...) con truncation=512
- Crea tokenized/ si no existe
- Escribe "id id id ..." en archivo de salida
- Riesgos:
* Diferente encoding de entrada → normalizado por clean_text()
* Si filename colisiona con otro (mismo nombre) → se sobrescribe
=============================================================================================================
[ translate_text(text) ]
- GoogleTranslator(source='auto', target='es').translate(text)
- Devuelve texto traducido o el original si error (catch + log)
- Limitaciones:
* Longitudes excesivas → errores (“Text length need to be between 0 and 5000”)
- Solución futura: fragmentar en bloques y recomponer
* Rate limits/cambios API
=============================================================================================================
[ clean_text(text) ]
1) Quita CDATA: regex '<!\[\s*CDATA\s*\[.*?\]\]>' con flags=re.S
2) BeautifulSoup(text, 'html.parser').get_text(separator=" ")
3) lower()
4) Elimina URLs: regex r'http\S+'
5) Deja sólo letras españolas y espacios: r'[^a-záéíóúñü\s]' → ''
6) Colapsa espacios: r'\s+' → ' ' + strip()
7) Filtra STOPWORDS (set ES) palabra a palabra
- Resulta en texto normalizado listo para BERT
- Notas:
* Pierde números, signos y acentos raros fuera de set
* STOPWORDS puede ajustarse por dominio (noticias vs. técnico)
=============================================================================================================
[ read_pdf(pdf_path) ]
- Abre en binario, PdfReader(f)
- Recorre páginas → page.extract_text() → concat + '\n'
- Devuelve string (puede estar vacío)
- Limitaciones:
* PDFs escaneados → sin OCR (no texto)
* Layouts complejos → texto desordenado
[ read_csv(csv_path) ]
- csv.reader → por cada fila ' '.join(row) + '\n'
- Simple y robusto; no maneja tipos/formato especial
[ read_docx(docx_path) ]
- docx.Document → concat paragraph.text + '\n'
- Pierde estilos/tablas; conserva sólo texto base
[ read_xlsx(xlsx_path) ]
- openpyxl.load_workbook → por cada hoja → por cada fila
- ' '.join(str(cell.value or '')) + '\n'
- Pierde formato/tipos; sólo valores en orden de fila
[ read_zip(zip_path) ]
- zipfile.ZipFile → recorre cada entry
- z.open(filename).read().decode('utf-8', errors='ignore')
- Concatena todo a un sólo string
- Peligros: ZIP enorme → memoria; entries binarias → ignoradas por decode
[ read_html_md(file_path) ]
- open(file, 'utf-8', errors='replace').read()
- Retorna string crudo (sin limpieza HTML aquí)
- format_content() se invoca después (actualmente vacío)
[ format_content(html_content) ]
- [PLACEHOLDER] En el snippet está sin implementar.
- Potencial:
* html2text → Markdown plano
* Limpieza de scripts/estilos/menus
* Normalización de espacios/entidades
- Hoy actúa como NO-OP (debería rellenarse)
=============================================================================================================
[ get_page_title(url) ]
- GET(url, timeout=10) → BeautifulSoup → <title>.text.strip()
- Devuelve None si falla o no hay <title>
- Usado para nombrar archivos de artículos
[ clean_filename(name) ]
- Reemplaza caracteres prohibidos [\/*?:"<>|] por "_"
- Espacios → "_"; corta a 100 chars
- Evita errores en FS; normaliza nombres
[ get_folder_info(path) ]
- Recorre recursivo → suma tamaño de ficheros y cuenta
- Devuelve (total_size_bytes, total_files)
- Usado para métricas y para detener por límite
=============================================================================================================
[ LOGGING / MÉTRICAS / LÍMITES ]
- logging.INFO por etapas (descargar, extraer, traducir, limpiar, tokenizar)
- Límite de tamaño: 50GB (archivos + artículos) → detiene crawling
- Resumen final:
* Artículos descargados (# y MB)
* Archivos descargados (# y MB)
* Archivos tokenizados (# y MB)
- Sugerencias:
* Añadir manejo de reintentos/backoff a requests
* Cache de traducciones por hash (ahorro de coste/tiempo)
* Paralelización controlada (cola + límites I/O/CPU)
* Particionar tokenized/ por subcarpetas si #ficheros crece
=============================================================================================================
[ DATA FLOW (RESUMEN) ]
URLs ──► register_processed_notifications ──► explore_* (HTMLSession/render/links)
└────────► extract_and_save_article ──► translate_text ─► clean_text ─► articulos/*.txt
└────────► download_and_save_file ─────────────────────────────────────► archivos/*
archivos/* ──► process_files (read_* → translate → clean → tokenize) ──► tokenized/*
articulos/*.txt ──► tokenize_all_articles ───────────────────────────────► tokenized/*
tokenized/*, articulos/*, archivos/* ──► get_folder_info + logs
████████████████████████████████████████████████████████████████████████████████████