Actualización de Fase 1
Se ha cambiado la base de datos a Postgres SQL, además se han realizado algunos cambios de lógica.
This commit is contained in:
parent
6ef0203a7d
commit
a24ba9b4d1
58
Obtener-Datos-1/README.md
Normal file
58
Obtener-Datos-1/README.md
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Fase 1 — Ingesta Automática de Datos (Adicciones ↔ Violencia)
|
||||||
|
|
||||||
|
Esta fase inicial prepara la base de datos PostgreSQL para el análisis posterior.
|
||||||
|
El script **detecta automáticamente delimitadores, codificaciones y nombres de columnas**,
|
||||||
|
creando tablas limpias e importando los datasets CSV del directorio `datasets/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ejecución
|
||||||
|
|
||||||
|
1. Levanta el contenedor de PostgreSQL:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Ejecuta el importador de datos:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 importador.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Descripción Técnica
|
||||||
|
|
||||||
|
- Detecta **codificación** (`UTF-8`, `Latin1`, `Windows-1252`, etc.) con `chardet`.
|
||||||
|
- Identifica el **delimitador CSV** (`;`, `,`, `|`, `\t`).
|
||||||
|
- Limpia nombres de columnas y crea las tablas en **PostgreSQL 15**.
|
||||||
|
- Inserta automáticamente los registros de cada dataset.
|
||||||
|
- Genera el archivo de metadatos `columnas_info.json` con las columnas detectadas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requisitos
|
||||||
|
|
||||||
|
Archivo `requirements.txt`:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
pandas
|
||||||
|
psycopg2-binary
|
||||||
|
chardet
|
||||||
|
```
|
||||||
|
|
||||||
|
Instalación rápida:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Salida
|
||||||
|
|
||||||
|
- Tablas generadas en la base de datos `adicciones` (puerto 5433).
|
||||||
|
- Archivo `columnas_info.json` con las estructuras detectadas.
|
||||||
|
|
||||||
|
*Esta fase deja lista la base de datos para las siguientes fases de procesamiento y análisis estadístico.*
|
||||||
@ -26,7 +26,11 @@
|
|||||||
"prov"
|
"prov"
|
||||||
],
|
],
|
||||||
"condenas_sexo_localidad": [
|
"condenas_sexo_localidad": [
|
||||||
"lugar_de_nacimiento_sexo_comunidades_y_ciudades_au"
|
"lugar_de_nacimiento",
|
||||||
|
"sexo",
|
||||||
|
"comunidades_y_ciudades_aut_nomas",
|
||||||
|
"periodo",
|
||||||
|
"total"
|
||||||
],
|
],
|
||||||
"registro_prohibidos_juego": [
|
"registro_prohibidos_juego": [
|
||||||
"a_o",
|
"a_o",
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
services:
|
services:
|
||||||
mysql:
|
postgres:
|
||||||
image: mysql:8.0
|
image: postgres:15
|
||||||
container_name: adicciones_db
|
container_name: adicciones_postgres
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: root
|
POSTGRES_USER: postgres
|
||||||
MYSQL_DATABASE: adicciones
|
POSTGRES_PASSWORD: postgres
|
||||||
MYSQL_USER: user
|
POSTGRES_DB: adicciones
|
||||||
MYSQL_PASSWORD: pass
|
|
||||||
ports:
|
ports:
|
||||||
- "3307:3306"
|
- "5433:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ./mysql_data:/var/lib/mysql
|
- ./pg_data:/var/lib/postgresql/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
136
Obtener-Datos-1/importador.py
Normal file
136
Obtener-Datos-1/importador.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import os
|
||||||
|
import psycopg2
|
||||||
|
import csv
|
||||||
|
import chardet
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# CONFIGURACIÓN DE CONEXIÓN
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
DB_CONFIG = {
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 5433,
|
||||||
|
"user": "postgres",
|
||||||
|
"password": "postgres",
|
||||||
|
"dbname": "adicciones"
|
||||||
|
}
|
||||||
|
|
||||||
|
DATASETS_DIR = "datasets"
|
||||||
|
OUTPUT_FILE = "columnas_info.json"
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_name(name: str) -> str:
|
||||||
|
"""Limpia nombres de columnas para SQL."""
|
||||||
|
name = name.strip().lower()
|
||||||
|
name = re.sub(r"[^a-z0-9_]+", "_", name)
|
||||||
|
return name[:50]
|
||||||
|
|
||||||
|
|
||||||
|
def detect_encoding(file_path):
|
||||||
|
"""Detecta codificación del archivo con chardet."""
|
||||||
|
with open(file_path, 'rb') as f:
|
||||||
|
raw = f.read(10000)
|
||||||
|
result = chardet.detect(raw)
|
||||||
|
return result['encoding'] or 'utf-8'
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_encoding(enc: str) -> str:
|
||||||
|
"""Convierte nombres de encoding a formato válido para PostgreSQL."""
|
||||||
|
if not enc:
|
||||||
|
return "UTF8"
|
||||||
|
enc = enc.upper().replace("-", "").replace("_", "")
|
||||||
|
if "UTF" in enc:
|
||||||
|
return "UTF8"
|
||||||
|
if "LATIN" in enc or "ISO88591" in enc:
|
||||||
|
return "LATIN1"
|
||||||
|
if "1252" in enc or "WINDOWS" in enc:
|
||||||
|
return "WIN1252"
|
||||||
|
return "UTF8" # fallback por defecto
|
||||||
|
|
||||||
|
|
||||||
|
def detect_delimiter(file_path, encoding):
|
||||||
|
"""Detecta el delimitador probable."""
|
||||||
|
with open(file_path, 'r', encoding=encoding, errors='ignore') as f:
|
||||||
|
sample = f.read(4096)
|
||||||
|
try:
|
||||||
|
dialect = csv.Sniffer().sniff(sample, delimiters=";,|\t")
|
||||||
|
return dialect.delimiter
|
||||||
|
except csv.Error:
|
||||||
|
if ";" in sample:
|
||||||
|
return ";"
|
||||||
|
elif "\t" in sample:
|
||||||
|
return "\t"
|
||||||
|
elif "|" in sample:
|
||||||
|
return "|"
|
||||||
|
else:
|
||||||
|
return ","
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
conn = psycopg2.connect(**DB_CONFIG)
|
||||||
|
cur = conn.cursor()
|
||||||
|
columnas_info = {}
|
||||||
|
|
||||||
|
for filename in os.listdir(DATASETS_DIR):
|
||||||
|
if not filename.endswith(".csv"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
path = os.path.join(DATASETS_DIR, filename)
|
||||||
|
table_name = sanitize_name(os.path.splitext(filename)[0])
|
||||||
|
|
||||||
|
print(f"\n📊 Procesando: {filename} → tabla '{table_name}'")
|
||||||
|
|
||||||
|
encoding_detected = detect_encoding(path)
|
||||||
|
pg_encoding = normalize_encoding(encoding_detected)
|
||||||
|
delimiter = detect_delimiter(path, encoding_detected)
|
||||||
|
|
||||||
|
print(f" → detectado: delimitador '{delimiter}' | codificación '{encoding_detected}' (→ {pg_encoding})")
|
||||||
|
|
||||||
|
# Leer encabezados manualmente
|
||||||
|
with open(path, 'r', encoding=encoding_detected, errors='ignore') as f:
|
||||||
|
reader = csv.reader(f, delimiter=delimiter)
|
||||||
|
headers = next(reader)
|
||||||
|
headers = [sanitize_name(h) for h in headers]
|
||||||
|
|
||||||
|
columnas_info[table_name] = headers
|
||||||
|
|
||||||
|
# Crear tabla limpia
|
||||||
|
cur.execute(f"DROP TABLE IF EXISTS {table_name} CASCADE;")
|
||||||
|
cols_sql = ", ".join([f"{col} TEXT" for col in headers])
|
||||||
|
cur.execute(f"CREATE TABLE {table_name} ({cols_sql});")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
# Intentar importar con encoding normalizado
|
||||||
|
try:
|
||||||
|
with open(path, 'r', encoding=encoding_detected, errors='ignore') as f:
|
||||||
|
next(f) # saltar encabezado
|
||||||
|
cur.copy_expert(
|
||||||
|
sql=f"COPY {table_name} ({', '.join(headers)}) FROM STDIN WITH (FORMAT CSV, DELIMITER '{delimiter}', NULL '', HEADER FALSE, ENCODING '{pg_encoding}');",
|
||||||
|
file=f
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Error al copiar con '{pg_encoding}': {e}")
|
||||||
|
print(" → Reintentando con ENCODING 'UTF8'")
|
||||||
|
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
next(f)
|
||||||
|
cur.copy_expert(
|
||||||
|
sql=f"COPY {table_name} ({', '.join(headers)}) FROM STDIN WITH (FORMAT CSV, DELIMITER '{delimiter}', NULL '', HEADER FALSE, ENCODING 'UTF8');",
|
||||||
|
file=f
|
||||||
|
)
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print(f"✅ Tabla '{table_name}' importada correctamente ({len(headers)} columnas).")
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(columnas_info, f, indent=4, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"\n✅ Importación completada. Estructuras guardadas en '{OUTPUT_FILE}'")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -1,2 +1,3 @@
|
|||||||
pandas
|
pandas
|
||||||
mysql-connector-python
|
psycopg2-binary
|
||||||
|
chardet
|
||||||
Loading…
Reference in New Issue
Block a user