Includes: FLUJOS app (Node/Flask/Python), FLUJOS_DATOS scripts (scrapers, Keras, Django) Excludes: MongoDB, scraped data, Wikipedia/WikiLeaks dumps, Python venv, node_modules
497 lines
25 KiB
Text
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
|
|
|
|
████████████████████████████████████████████████████████████████████████████████████
|