┌───────────────────────────────────────────────────┐ │ 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 , <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 ████████████████████████████████████████████████████████████████████████████████████