19 lecturasDatos · Scraping · Calidad

El extractor que devuelve 300 celdas vacías se ve igual que el que funcionó

El script terminó sin errores. El archivo se generó con sus 312 filas. Los nombres estaban, los precios estaban.

Y la columna de disponibilidad decía UNKNOWN en las 312.

En la web, cada producto decía "En stock" con todas sus letras.

El bug tenía cinco caracteres

El selector apuntaba al sitio correcto:

availability = row.css(".stock::text").get()

En el HTML, ese nodo se veía así:

<p class="stock">
    <i class="icon-ok"></i>
    In stock
</p>

::text no devuelve "el texto del elemento". Devuelve los nodos de texto, en orden. Y el primero, antes del <i>, es el salto de línea con su sangría. get() toma el primero y se va.

Así que el extractor recogía, fielmente, cuatro espacios en blanco. Los normalizaba a cadena vacía. Y la cadena vacía no coincidía con ningún valor conocido, así que el normalizador respondía lo único honesto que podía responder: UNKNOWN.

La corrección es getall() y unir:

availability = " ".join(row.css(".stock::text").getall())

Cinco caracteres. Lo interesante no es el bug — es lo que hizo falta para verlo.

Por qué esto es peor que una excepción

Una excepción es un buen ciudadano. Rompe la corrida, deja rastro, aparece en el log, dispara la alerta. Alguien la mira.

Este fallo hizo lo contrario: produjo una salida plausible sin producir un error.

Y una salida plausible es peor que ninguna salida, porque avanza. El archivo se entrega. Alguien lo abre, ve 312 filas pobladas y asume que el trabajo se hizo. La columna rota se descubre semanas después, cuando alguien pregunta por qué el informe dice que ningún competidor tiene inventario.

Para entonces ya se decidió algo con ese dato.

Los tres que se comportan igual

Mientras construía el motor aparecieron otros dos del mismo tipo. Ninguno lanza nada.

El hash que incluía la hora de captura. El detector de cambios compara el contenido de hoy con el de ayer por hash. Si el campo collected_at entra en ese hash, cada producto cambia todas las noches — porque la hora siempre es distinta. El informe de cambios se llena de ruido y deja de leerse. La corrección es una lista blanca explícita de campos que entran al hash, no un dict completo.

El decimal que no era el mismo número. Decimal("799.00") y Decimal("799.0") son iguales al comparar y distintos al serializar. El mismo precio, sin cambio real, aparecía como modificado según de qué página viniera. Se arregla normalizando el exponente antes de hashear.

La letra que se comió una moneda. La lista de símbolos a limpiar incluía "R" por el rand sudafricano. El texto "EUR 45" pasaba por ahí, perdía la R y quedaba "EU 45". El parser no encontraba número válido y devolvía nulo. Un precio menos, en silencio. Ahora los códigos ISO se retiran antes que los símbolos, y en la lista de símbolos no hay letras.

Los cuatro comparten el patrón: el sistema hace exactamente lo que le pediste, y lo que le pediste no era lo que querías. No hay nada que un try/except pueda atrapar.

Lo que sí los detecta

No fue mejor código. Fue cambiar qué se entrega.

Ahora cada corrida emite un informe de verificación junto al archivo:

Filas extraídas        312
Sin precio               0
Sin disponibilidad     312   <-- aquí
Sin SKU                  7

Esa tercera línea es todo el sistema. No hace falta revisar el archivo ni conocer el dominio: si una columna sale con el conteo completo en la fila de faltantes, algo está roto, y se ve antes de entregar.

Y hay una regla que lo acompaña: una corrida que no llega a COMPLETED no se entrega. La máquina de estados se niega a pasar de PARCIAL a ENTREGADO sin una aprobación humana explícita — no un flag en la config, una firma.

La pregunta que quedó

Al cerrar el motor me quedé con una que uso ahora en cualquier proceso que produzca datos:

¿Cómo se ve este sistema cuando falla en silencio?

Si la respuesta es "igual que cuando funciona", todavía no está terminado. Da igual la cobertura de tests: los tests comprueban lo que pensaste comprobar, y este tipo de fallo vive justo en lo que no se te ocurrió.

La versión corta: un error se queja; un dato equivocado espera.

Compartir

Comentarios

Todavía no hay comentarios. Empieza tú.

No se publica. Solo lo uso para responderte.

Los comentarios se revisan antes de publicarse.