Capítulo 1: Comencemos Nuestro Viaje de Automatización
Parte 3: Extracción de Datos y el Módulo Parse
En muchas tareas automatizadas, necesitamos tratar texto de entrada estructurado en un formato conocido y extraer la información relevante. Aprenderemos técnicas manuales y también una poderosa herramienta de terceros llamada parse.
Extraer Datos de Cadenas Estructuradas
Imaginemos que necesitamos analizar registros de ventas con la siguiente estructura:
[timestamp] - SALE - PRODUCT: id - PRICE: $precio
Un registro específico podría verse así:
[2018-05-05T10:58:41.504054] - SALE - PRODUCT: 1345 - PRICE: $09.99
Cómo Hacerlo
1. Importar las herramientas necesarias:
>>> import delorean >>> from decimal import Decimal
2. Dividir el log en sus partes usando ' - ' como separador:
>>> log = '[2018-05-05T11:07:12.267897] - SALE - PRODUCT: 1345 - PRICE: $09.99' >>> divide_it = log.split(' - ') >>> timestamp_string, _, product_string, price_string = divide_it
3. Parsear cada elemento al tipo apropiado:
# Timestamp - eliminar corchetes y parsear >>> timestamp = delorean.parse(timestamp_string.strip('[]')) # Product ID - dividir por ':' y convertir a int >>> product_id = int(product_string.split(':')[-1]) # Precio - dividir por '$' y convertir a Decimal >>> price = Decimal(price_string.split('$')[-1])
4. Resultado final:
Clase PriceLog
Podemos encapsular esta lógica en una clase reutilizable:
class PriceLog: def __init__(self, timestamp, product_id, price): self.timestamp = timestamp self.product_id = product_id self.price = price def __repr__(self): return f'<PriceLog ({self.timestamp}, {self.product_id}, {self.price})>' @classmethod def parse(cls, text_log): divide_it = text_log.split(' - ') tmp_string, _, product_string, price_string = divide_it timestamp = delorean.parse(tmp_string.strip('[]')) product_id = int(product_string.split(':')[-1]) price = Decimal(price_string.split('$')[-1]) return cls(timestamp=timestamp, product_id=product_id, price=price)
float para precios! Los números float tienen problemas de precisión. Por ejemplo: 0.1 + 0.1 + 0.1 = 0.30000000000000004. Usa siempre Decimal o centavos como enteros.
Usar una Herramienta de Terceros: Parse
Aunque el análisis manual funciona bien para cadenas simples, puede ser laborioso ajustar la fórmula para variaciones de entrada. El módulo parse nos permite invertir cadenas de formato de forma elegante.
Preparación
Agregar parse al archivo requirements.txt:
delorean==1.0.0 requests==2.22.0 parse==1.14.0
$ pip install -r requirements.txt
Cómo Hacerlo
1. Importar y definir el log:
>>> from parse import parse >>> LOG = '[2018-05-06T12:58:00.714611] - SALE - PRODUCT: 1345 - PRICE: $09.99'
2. Definir el formato como lo harías al imprimir:
>>> FORMAT = '[{date}] - SALE - PRODUCT: {product} - PRICE: ${price}'
3. Ejecutar parse (resultado como strings):
>>> result = parse(FORMAT, LOG) >>> result['date'] '2018-05-06T12:58:00.714611' >>> result['product'] '1345' >>> result['price'] '09.99'
Definir Tipos
Podemos especificar tipos para conversión automática:
>>> FORMAT = '[{date:ti}] - SALE - PRODUCT: {product:d} - PRICE: ${price:05.2f}' >>> result = parse(FORMAT, LOG) >>> result['date'] datetime.datetime(2018, 5, 6, 12, 58, 0, 714611) >>> result['product'] 1345 >>> result['price'] 9.99
Tipos Personalizados
Para evitar problemas con float en precios, definimos un tipo personalizado:
>>> from decimal import Decimal >>> def price(string): ... return Decimal(string) >>> FORMAT = '[{date:ti}] - SALE - PRODUCT: {product:d} - PRICE: ${price:price}' >>> parse(FORMAT, LOG, {'price': price}) # result['price'] = Decimal('9.99')
parse invierte el método format: lo que normalmente usarías para crear una cadena formateada, ahora lo usas para extraer valores. El especificador :ti es para timestamps ISO.
Clase PriceLog con Parse
import parse from decimal import Decimal import delorean class PriceLog: @classmethod def parse(cls, text_log): def price(string): return Decimal(string) def isodate(string): return delorean.parse(string) FORMAT = ('[{timestamp:isodate}] - SALE - PRODUCT: {product:d} - ' 'PRICE: ${price:price}') formats = {'price': price, 'isodate': isodate} result = parse.parse(FORMAT, text_log, formats) return cls(timestamp=result['timestamp'], product_id=result['product'], price=result['price'])