Skip to content

KamilSlodki/inmo-data-case-study

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Inmo Data — Case Study

Pipeline ETL robusto de agregación de datos públicos del sector inmobiliario español: extracción multi-fuente con anti-bot mitigation comercial, normalización heterogénea de campos, enriquecimiento semántico vía LLM y multiprocessing con respectful rate limiting. Entregado a cliente B2B para análisis de mercado (caso cerrado).

Type Status Role Engineering Doctrine Stack Compliance

Para clientes B2B / fractional CTO — case-study de delivery completo a cliente B2B sectorial inmobiliario: pipeline ETL robusto con anti-bot mitigation comercial multi-señal, multi-worker concurrente con work partitioning (sin race conditions), normalización heterogénea de campos, enriquecimiento semántico vía LLM y respectful pacing con scope GDPR-aware. Entregable cerrado con disciplina institucional: ADRs versionadas, runbooks operativos, postmortems formales, anonimización profesional bajo NDA.

DATA PIPELINE
End-to-end ETL
extracción · validación · normalización · enriquecimiento · entrega
ARQUITECTURA
Multi-worker concurrente
work partitioning + merge offline · sin race conditions
COMPLIANCE
Solo datos públicos
respectful pacing · third-party anti-bot API · scope GDPR-aware

Sobre este case-study

Este repositorio describe un proyecto entregado y cerrado para un cliente B2B del sector inmobiliario español. El cliente solicitó una pipeline de agregación de datos públicos disponibles en un portal inmobiliario de ámbito nacional, normalizados para análisis comparativo de mercado.

Cliente y portal específicos no se mencionan por consideraciones contractuales y legales. La narrativa técnica aplica al patrón general "data pipeline desde portal público con anti-bot mitigation" — extrapolable a múltiples sectores (real estate, e-commerce, classifieds, public records, regulatory filings).


El problema

El cliente operaba en análisis de mercado inmobiliario y necesitaba consolidar datos públicos heterogéneos desde un portal nacional para:

  1. Comparativa de precios y características entre regiones geográficas.
  2. Detección de outliers y patrones temporales.
  3. Enriquecimiento semántico de descripciones libres a campos estructurados.
  4. Generación de datasets normalizados para alimentar dashboards y modelos analíticos internos.

Los retos técnicos sistémicos al enfrentar este tipo de extracción:

Antipatrón sistémico de la industria Patrón aplicado en este proyecto
Scrapers monolíticos sin detección de bloqueos → fallos silenciosos Detección multi-señal de anti-bot (DOM markers + content regex + script inspection)
Resolución de CAPTCHAs propia / técnicas en gris Delegación a API comercial de resolución legítima (servicio de tercero)
Pacing agresivo → ban del IP de origen Respectful rate limiting con delays aleatorios + cap de concurrencia
Multiprocessing escribiendo al mismo archivo → race conditions, duplicados Work partitioning: cada worker su propio CSV; merge offline al final
Normalización al final del pipeline → errores propagados N capas Normalización early (en parsing), formato canónico antes de persistir
Sesiones / cookies sin scope correcto → "sigue bloqueado tras resolver" Cookies inyectadas con dominio + path + secure + sameSite explícitos
Bot detection puramente por user-agent → fácilmente detectable Simulación de comportamiento humano (scroll, mouse, delays) en navegador real

La solución arquitectónica

Pipeline ETL en cinco etapas con responsabilidades estrictamente separadas:

  1. Ingestion: lectura de listado de URLs públicas → particionado en N lotes (uno por worker).
  2. Extraction: navegador headless con perfil anti-detección + proxy residencial geográfico + simulación de comportamiento humano. Detección multi-señal de bloqueos anti-bot.
  3. Anti-bot mitigation: delegación a API comercial de resolución de CAPTCHAs (servicio de tercero legítimo). Inyección de cookies validadas en contexto de navegador con scope correcto.
  4. Normalization: limpieza de caracteres especiales, formato canónico de campos (teléfonos, direcciones, áreas, precios), validación contra regex de país.
  5. Enrichment: opcional, análisis semántico de descripciones libres vía LLM (extracción de amenities implícitas, clasificación).
  6. Output: CSV consolidado tras merge offline de los N lotes parciales, con deduplicación por ID público.

Pipeline en acción

flowchart LR
    SRC[Portal inmobiliario<br/>público nacional]
    INGEST[Ingestion Layer<br/>URL list → N batches]
    BROWSER[Browser headless<br/>anti-detection profile<br/>+ residential proxy]
    DETECT{Anti-bot<br/>detected?}
    RESOLVE[Third-party<br/>CAPTCHA<br/>resolution API]
    PARSE[Parse + Extract<br/>structured fields]
    NORM[Normalization<br/>canonical format]
    LLM[LLM enrichment<br/>semantic extraction]
    MERGE[Merge offline<br/>+ deduplicate]
    CSV[(CSV output<br/>normalized dataset)]

    SRC --> BROWSER
    INGEST --> BROWSER
    BROWSER --> DETECT
    DETECT -->|yes| RESOLVE
    DETECT -->|no| PARSE
    RESOLVE -->|cookies injected| PARSE
    PARSE --> NORM
    NORM --> LLM
    LLM --> MERGE
    MERGE --> CSV

    style DETECT fill:#fee,stroke:#c00
    style RESOLVE fill:#ffe,stroke:#cc0
    style LLM fill:#eef,stroke:#06c
Loading

Arquitectura — notación C4

Nivel 1 — Contexto

flowchart TB
    subgraph ext["External Systems"]
        PORTAL["Public Real-Estate Portal<br/>(national scope)"]
        CAPTCHA_API["Third-party CAPTCHA<br/>resolution API"]
        PROXY["Residential proxy<br/>service (geo-targeted)"]
        LLM_API["LLM API<br/>(semantic enrichment)"]
    end

    subgraph actors["Actors"]
        CLIENT["Client analyst<br/>(market research)"]
        OPERATOR["Pipeline operator<br/>(scheduled runs)"]
    end

    INMO["Inmo Data Pipeline<br/>(ETL anonymized)"]

    OPERATOR -->|"trigger · schedule"| INMO
    CLIENT -->|"consume normalized CSVs"| INMO

    INMO -->|"public listing fetch<br/>respectful rate limiting"| PORTAL
    INMO -->|"submit CAPTCHA task<br/>poll for solution"| CAPTCHA_API
    INMO -->|"residential IP rotation"| PROXY
    INMO -->|"semantic enrichment<br/>(optional)"| LLM_API

    classDef system fill:#2962FF,stroke:#1E88E5,color:white
    classDef external fill:#00C853,stroke:#00E676,color:white
    classDef actor fill:#757575,stroke:#9E9E9E,color:white
    class INMO system
    class PORTAL,CAPTCHA_API,PROXY,LLM_API external
    class CLIENT,OPERATOR actor
Loading

Nivel 2 — Contenedores

flowchart TB
    subgraph boundary["Inmo Data Pipeline"]
        subgraph ingest_tier["Ingestion Tier"]
            CTRL["Master Controller<br/>work partitioning<br/>+ worker dispatch"]
            INPUT[("URL list<br/>(input CSV)")]
        end

        subgraph worker_tier["Worker Tier (N parallel)"]
            W1["Worker #1<br/>browser context"]
            W2["Worker #2<br/>browser context"]
            WN["Worker #N<br/>browser context"]
        end

        subgraph detect_tier["Anti-bot Mitigation"]
            DETECT["Multi-signal Detector<br/>DOM + regex + script"]
            RESOLVER["CAPTCHA Resolver Client<br/>polling + timeout"]
        end

        subgraph transform_tier["Transformation"]
            PARSER["Field Parser<br/>structured extraction"]
            NORMALIZER["Normalizer<br/>canonical format<br/>+ regex validation"]
            ENRICHER["LLM Enricher<br/>(optional)"]
        end

        subgraph output_tier["Output"]
            PARTIAL[("Partial CSV<br/>per worker")]
            MERGER["Offline Merger<br/>+ dedup by ID"]
            FINAL[("Final CSV<br/>normalized dataset")]
        end
    end

    subgraph external["External"]
        PORTAL["Public Portal"]
        CAPTCHA["CAPTCHA API"]
        PROXY["Residential Proxy"]
        LLM["LLM API"]
    end

    INPUT --> CTRL
    CTRL --> W1
    CTRL --> W2
    CTRL --> WN

    W1 -->|via proxy| PROXY
    PROXY --> PORTAL
    PORTAL -->|response| W1
    W2 -->|via proxy| PROXY
    WN -->|via proxy| PROXY

    W1 --> DETECT
    DETECT -->|blocked| RESOLVER
    RESOLVER -->|submit + poll| CAPTCHA
    CAPTCHA -->|solution| RESOLVER
    RESOLVER -->|cookies| W1

    DETECT -->|clear| PARSER
    PARSER --> NORMALIZER
    NORMALIZER --> ENRICHER
    ENRICHER -.->|optional API call| LLM
    LLM -.->|enriched fields| ENRICHER

    ENRICHER --> PARTIAL
    PARTIAL --> MERGER
    MERGER --> FINAL

    classDef ingest fill:#FF6D00,stroke:#FF9100,color:white
    classDef worker fill:#7C4DFF,stroke:#651FFF,color:white
    classDef detect fill:#D32F2F,stroke:#B71C1C,color:white
    classDef transform fill:#2962FF,stroke:#1E88E5,color:white
    classDef output fill:#388E3C,stroke:#1B5E20,color:white
    classDef external fill:#757575,stroke:#9E9E9E,color:white

    class CTRL,INPUT ingest
    class W1,W2,WN worker
    class DETECT,RESOLVER detect
    class PARSER,NORMALIZER,ENRICHER transform
    class PARTIAL,MERGER,FINAL output
    class PORTAL,CAPTCHA,PROXY,LLM external
Loading

Decisiones arquitectónicas clave:

  • Multiprocessing.Process con work partitioning en vez de threads / async para evitar locks compartidos sobre I/O de disco.
  • Cada worker su propio CSV en vez de escritura compartida → cero race conditions, cero filas perdidas.
  • Merge offline al final con deduplicación por ID público → idempotencia de re-runs.
  • Anti-bot mitigation vía API comercial en vez de bypass técnico propio → legal, mantenible, separación de responsabilidades.
  • Normalización temprana en parsing en vez de al final → errores no se propagan 5 capas hacia abajo.

Stack técnico

Capa Tecnología Notas
Lenguaje Python 3.10+ Virtualenv estándar
Browser automation Playwright (sync + async) Chromium headless con context isolation por worker
Anti-detection profile Browser comercial con perfil anti-detección Servicio de terceros
Proxy infrastructure Servicio comercial de proxies residenciales geo-targeted Rotación de IPs por región
Anti-bot mitigation API comercial de resolución de CAPTCHAs Polling con timeout adaptativo
Data manipulation Pandas + NumPy DataFrames para normalización + merge
HTTP client requests Comunicación con APIs externas
LLM enrichment Commercial LLM API (opcional, feature flag) Análisis semántico de descripciones
Config python-dotenv Secrets fuera del código (.env + .env.example)
Concurrency multiprocessing.Process 5 workers paralelos con work partitioning
Output format CSV (UTF-8) Portable, fácil consumo cliente

Retos técnicos resueltos

1. Detección robusta de anti-bots heterogéneos
  • Síntoma: el portal implementaba múltiples mecanismos de bloqueo simultáneos (CAPTCHA visual, slider, script de anti-bot comercial). Los scripts iniciales no los detectaban consistentemente, generando falsos positivos (timeout esperando contenido) o sesiones quemadas silenciosamente.
  • Causa raíz: ausencia de predicados de detección multi-señal. Se buscaba un único selector CSS o un único marker textual cuando en realidad había 3+ señales independientes que indicaban bloqueo.
  • Fix: matriz de detección con 3 métodos independientes — (a) presencia de selector DOM específico, (b) regex sobre texto visible, (c) inspección de scripts cargados en el HTML. Si cualquiera retorna true, activar estrategia de resolución.
  • Lección: anti-bot protection es un sistema, no un endpoint único. En cualquier data pipeline que cruce barreras de fuentes externas, mapear todas las señales observadas (headers, cookies, JavaScript, timing) y codificarlas como predicados combinables. Reduce debugging de horas a minutos.
2. Resolución asíncrona con polling y timeout adaptativo
  • Síntoma: la API comercial de resolución responde con un task_id sin garantía de tiempo. Primeros intentos esperaban respuesta inmediata (2-3s) y caían en timeout falso. Otros esperaban indefinidamente, bloqueando workers.
  • Causa raíz: no había circuit breaker ni backoff adaptativo. Loop de polling simple sin límite de iteraciones.
  • Fix: polling con límite explícito (N iteraciones × intervalo conocido del SLA de la API), logs de estado por intento, escape exponencial. Si timeout real → excepción capturable que reencola el item para distinto worker con nueva sesión.
  • Lección: cualquier dependencia de I/O remoto debe documentar explícitamente SLA, timeout máximo aceptable y fallback. El timeout es el "fusible" que evita deadlock. Sin él, un worker lento infecta toda la batch.
3. Normalización heterogénea de datos de contacto
  • Síntoma: los campos de contacto venían en N formatos heterogéneos (tel:+34600..., 34600..., 600..., +34 600 ...). Los CSVs output mostraban inconsistencia, dificultando el matching downstream con sistemas del cliente.
  • Causa raíz: el HTML del portal no normalizaba antes de exponer en el DOM. El parser extraía sin aplicar regla consistente.
  • Fix: función normalize_phone() con pipeline determinista — extrae solo dígitos → detecta longitud → infiere si lleva prefijo país → canónico (+34XXXXXXXXX) → valida contra regex de país. Aplicado en el parsing, no al final del pipeline.
  • Lección: en pipelines heterogéneos, la normalización es tan crítica como la extracción. Codificar early (parsing) no late (consumer side). Un dato "sucio" propagado a 5 fases cuesta 5× más arreglarlo. Schema-first design (definir formato output antes de escribir parsers) ahorra debugging y deuda.
4. Manejo de sesiones y cookies con scope correcto post-resolución
  • Síntoma: scripts resolvían el CAPTCHA exitosamente pero la sesión seguía bloqueada. Las cookies se inyectaban en el contexto del navegador pero sin sincronización correcta.
  • Causa raíz: cookies con scope incorrecto (domain vs path vs sameSite) no aplicaban. El navegador headless no actualizaba la sesión cuando se mutaban cookies programáticamente sin reload posterior.
  • Fix: protocolo de inyección estricto — obtener token + URL antes de mutar contexto, inyectar con scope explícito (domain exacto, path: /, secure: true, sameSite: Lax), forzar reload() y esperar respuesta del portal antes de continuar.
  • Lección: cookies y sesiones en navegadores headless requieren disciplina de scope. Debuguear con context.cookies() antes y después. Tests fixtures que validen "sesión OK post-mutación" antes de pasar a producción.
5. Paralelización con multiprocessing y I/O compartido
  • Síntoma: el controlador lanzaba N workers en paralelo, todos escribiendo al mismo CSV output sin lock. Resultado: filas duplicadas, parciales, perdidas. Reintento de URLs inconsistente entre workers.
  • Causa raíz: multiprocessing.Process no tiene mutex nativo para acceso a archivos. CSV writing es operación no-atómica.
  • Fix: work partitioning — dividir URLs en N lotes (uno por worker). Cada worker escribe a su propio CSV (results_worker_0.csv, etc.). Al final, merge offline: read N files, deduplicate por ID público, write final CSV.
  • Lección: multiprocessing + shared I/O es un footgun. Mejor: work partitioning (cada process = subset de datos disjuntos) + merge al final. Si hay lock real necesario → switch a asyncio o ThreadPoolExecutor con Queue. Multiprocessing es para CPU-bound; scraping es I/O-bound, así que el split de partición es más natural.
6. Simulación de comportamiento humano contra heurísticas de timing
  • Síntoma: el portal detectaba patrones de acceso automatizado (velocidad de clicks, ausencia de scroll, timing perfecto) y bloqueaba sin mostrar CAPTCHA (solo 403 silencioso).
  • Causa raíz: heurísticas anti-bot observan interacciones en timeline real. Un click() seguido de extract() en 10ms es obviamente bot.
  • Fix: insertar simulate_human_behavior() con eventos randomizados — scroll (rango definido), mouse move por pasos pequeños, PageDown, sleeps aleatorios (1-2s). 2-4 acciones por sesión, ejecutadas en momentos no deterministas del flow.
  • Lección: bot detection es una carrera de armamentos. User-agent + headers no son suficientes; necesitas eventos del navegador (mouse, keyboard, scroll). Cada portal es distinto (algunos ignoran timing, otros usan ML). Medir hit-rate de "bloqueos falsos" y ajustar threshold. No hay solución one-size-fits-all.

Consideraciones de compliance y ética

Este proyecto operaba sobre datos públicamente visibles del portal (es decir, accesibles a cualquier visitante humano sin login). Las salvaguardas aplicadas:

  • Respectful rate limiting: delays aleatorios + cap de concurrencia (workers limitados) → no se generaba carga inusual sobre el origen.
  • Solo datos públicos: cero extracción de campos detrás de login, behind paywall, o que requieran autenticación.
  • CAPTCHA resolution vía API comercial (no bypass técnico propio): se delega a servicio legítimo de terceros que opera bajo sus propios términos.
  • Proxies residenciales legales: servicio comercial con consentimiento de los IP holders.
  • Scope GDPR-aware: los datos de contacto presentes en los listados públicos se trataban según las bases legales del cliente (interés legítimo del análisis de mercado vs consentimiento del listing owner). Los outputs entregados al cliente quedaban bajo su responsabilidad como data controller.

No publicado en este case-study (consideración legal + reputacional):

  • Nombre real del cliente.
  • Identidad del portal específico.
  • Selectores CSS / regex propios del portal.
  • Cookies y tokens de sesión.
  • Detalles de la técnica concreta de mitigación.
  • Volúmenes absolutos procesados.
  • Datos personales identificables (teléfonos, direcciones, IDs de listings).

Estado

Caso cerrado. Entregado al cliente. Proyecto archivado. Este case-study existe como referencia técnica del patrón aplicado, no como producto comercial activo del autor.


Documentación


Lo que este repositorio NO ofrece

  • No es un servicio de scraping. No acepta clientes con este tipo de necesidad bajo este branding (la metodología sí es replicable bajo NDA).
  • No publica el código fuente del pipeline original. Es propiedad del proyecto cliente original.
  • No publica selectores, regex, ni cookies específicas del portal target.
  • No promueve técnicas de bypass de anti-bot ni evasión de rate limiting. La narrativa es de "data pipeline robusto con anti-bot mitigation legítima".
  • No es asesoramiento legal. Cada proyecto de extracción de datos públicos requiere validación legal específica del territorio y portal.

Sobre el patrón aplicado

El patrón documentado aquí es replicable a otros dominios con extracción de datos públicos heterogéneos:

  • E-commerce (catálogos comparados).
  • Public records / registros públicos.
  • Classifieds (clasificados de cualquier vertical).
  • Regulatory filings.
  • News / media monitoring (con respeto explícito a robots.txt).

Los retos técnicos resueltos (detección anti-bot multi-señal, polling robusto, normalización temprana, work partitioning, comportamiento humano) son horizontales.


Sistema operativo de ingeniería del autor

Este proyecto se desarrolló bajo el sistema operativo de ingeniería propio del autor, replicado cross-proyecto en múltiples proyectos del ecosistema:

  • Automation Engineering Protocol — stage-gate horizontal de cambio: change-spec → guard layer → release train → rollback playbook.
  • Prompt Engineering Protocol — diseño modular: required/optional/exclusion keywords + patterns regex + confidence scoring + anti-echo + whitelist + schema JSON validation.
  • Post-Development Verification Protocol — 4 niveles (estática / integración / canarios E2E / observabilidad diaria).
  • Post-Development Gates — 94 gates pre-merge / pre-deploy / post-deploy con checklist bloqueante.
  • Frozen Zones & Regression Prevention — CONFIG vs HEALTH, auditoría holística periódica.

Cada decisión estructural se documenta como ADR versionada. Cada cambio significativo pasa por verify-findings adversarial. Engineering-playbook propio con 60+ archivos doctrinales cross-proyecto. La documentación es activo de primera clase.

Protocolo de documentación detallado (arc42 + C4 + ADR + runbooks + postmortems) en docs/documentation-protocol.md.

Detalles operativos del playbook bajo acuerdo de confidencialidad.


Contacto

SatData Solutions — Valencia, España

Conversaciones técnicas sobre data pipelines, anti-bot mitigation legítima, escalado I/O-bound y normalización heterogénea bajo NDA. No se aceptan encargos de scraping abusivo, bypass de mecanismos de seguridad ni extracción de datos privados.


Nota sobre este repositorio

Este repositorio es un case-study anonimizado de un proyecto entregado a cliente B2B y cerrado. No contiene código fuente del producto original, datos del cliente, ni elementos identificables del portal target. Su propósito es documentar el patrón técnico aplicable a futuros proyectos similares y mostrar madurez en compliance + arquitectura.

Para conversaciones técnicas detalladas o evaluación de aplicabilidad a un caso concreto del cliente potencial: contacto por los canales arriba bajo NDA.

About

Inmo Data - Case study anonimizado de data pipeline robusto con anti-bot mitigation legitima para sector inmobiliario espanol (caso cerrado)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors