Publicado el 1 comentario

EXTRAER DATOS DE FACTURA XML (formato del SRI EC) USANDO PYTHON

Estoy creando un flujo de trabajo semiautomatizado para el control de gastos e ingresos. Así que lo primero fue establecer las tecnologías ha utilizar.

El presupuesto que establecí es de cero dólares ($0). Y la idea es tener un núcleo tecnológico desde el nacimiento de la empresa. Y pues si te lo preguntas, tener un núcleo tecnológico en tu empresa no significa pagar licencias o instalar software pirata. Significa que tu equipo sea capaz de generar soluciones con los recursos a tu alcance.

Nos decidimos por Python, ya que podemos tener el prototipo de una aplicación básica en poco tiempo. Y con jupyter notebooks, es prácticamente como tener una hoja de cálculo en Excel.

El código este tutorial simple, lo puedes encontrar en nuestro repositorio de github:

Para poder realizar este ejercicio vamos a necesitar importar las siguientes librerías, por comodidad de este tutorial usaremos un notebook.

#librerias a utilizar
#libreria parse XML
import xml.etree.ElementTree as ET
#expresiones regulares
import re
#pandas para dataframes
import pandas as pd
#acceder a path de directorios locales
import os

La librería xml.etree.ElementTree en específico nos ofrece métodos muy sencillos para poder extraer los datos de los nodos de un XML, e incluso soporta búsquedas xpath.

Empecemos por descargar uno de los archivos XML de la página del SRI, en mi caso una de las facturas recibidas por compras:

Una vez que tienes tu archivo de ejemplo, investiguemos un poco la estructura del XML. Puedes abrir el archivo en un editor de texto de tu pre­­­­­­­­ferencia.

xml de factura descargada del SRI

De este apartado nos interesa verificar las etiquetas o nodos existentes en el archivo. Entonces toda la etiqueta <?xml encierra el contenido que deseo obtener. Si continuo hacia abajo el siguiente nivel es <autorizacion>.

Si estas viendo el archivo en este momento, ya habras notado la similitud con un HTML (no es lo mismo), las etiquetas tienen tanto una apertura como un cierre, <apertura>  </cierre>.

Dicho esto, podemos notar que la etiqueta <autorizacion> tiene una apertura y le siguen las etiquetas de:

<estado>AUTORIZADO</estado>

<numeroAutorizacion>2502202221179004027500120</numeroAutorizacion>

<fechaAutorizacion>2021-03-24T10:34:45-05:00</fechaAutorizacion>

<ambiente>PRODUCCIÓN</ambiente>

<comprobante>

Hacemos un nuevo stop, al llegar a la etiqueta <comprobante> que también aparece como una apertura, el cierre de la misma esta en parte inferior y nos hace entender que esta etiqueta contiene todos los datos resaltados en amarillos. Estos son los datos que requiero para los informes, para estructurarlos y para la aplicación que tenemos en mente.

Para este momento ya tengo el notebook con las librerías a utilizar, y abierto el archivo de ejemplo:

Obtengo el listado de ejemplo usando la librería os, para obtener todos los archivos del directorio actual. Como el objeto que me devuelve este método es una lista, puedo seleccionar uno de ellos usando notación de corchetes[].

El siguiente paso como puedes ver en la imagen es obtener el XML del archivo, esto lo realizas pasándole a la función ET.parse() la factura, seguidamente de la función .getroot() que devuelve la raíz o por decirlo de alguna manera la etiqueta que abarca a todas las demás etiquetas que estamos investigando.

Si usas type para ver de que clase de objeto se trata obtienes el siguiente resultado:

type(root)
output: xml.etree.ElementTree.Element

Para acceder al contenido debo iterar sobre la variable root, con un ciclo for, los métodos que vamos utilizar .tag, .attrib y .text que viene de la librería ET.

.tag: devuelve el nombre de la etiquetas por ejemplo autorización, estado etc.

.attrib: devuelve los valores que contiene una etiqueta lo veremos mejor en el ejemplo

.text: devuelve el contenido en tipo str que contiene una etiqueta

#verificar las etiquetas child de root
for i in root:
    print(i.tag,":", i.attrib)

output:
estado : {}
numeroAutorizacion : {}
fechaAutorizacion : {}
ambiente : {}
comprobante : {}
mensajes : {}

Ten en mente que el objeto root es un diccionario, y los keys de este diccionario son los valores que estás obteniendo con la iteración. En este caso nos interesa el comprobante : {} al tratar de obtener los atributos o contenido nos devuelve un diccionario vació. Esto significa que dentro de estas etiquetas ya no existen sub-niveles o mas árboles de datos del XML. Pero esto no es verdad porque ya vimos que dentro de comprobante está toda la información de la factura. Para tener más claro el panorama envés de .attrib usamos .text y el resultado es el siguiente:

Si te fijaste, en el inicio de la investigación del archivo notaste que toda la información resaltada en amarillo esta encerrada en un <![CDATA[ no voy entrar en detalles de esto, pero llévate en la mente que todo lo que está encerrado en CDATA ahora es un texto plano.

Es decir que nuestra iteración es correcta, ya no existe un atributo en comprobante : {} lo que contiene ya es solo un texto.

Entonces para seguir usando la librería de ET, debemos abrir este string como un XML, y pues la misma librería de ET nos ofrece un método, este es ET.fromstring

#usando el metodo de fromstring abres el texto como XML
xml_comprobante=ET.fromstring(comprobante)
xml_comprobante

output:
<Element 'factura' at 0x000001C0BF278E50>

Ahora nuevamente tenemos un árbol de XML, que tiene la etiqueta ‘factura’. Podemos iterar sobre esta etiquetas y recolectar los datos de tag y los texto que contiene, en este caso debemos iterar un nivel mas bajo para ir sacando los contenidos que nos interesa.

Declaras un diccionario vacío para ir llenándolo con los datos, y a partir de este diccionario ya se puede construir un dataframe, para entregarlo a tus herramientas de análisis.

Por último generamos dos funciones para organizar el trabajo que hemos levantado. La primera función se va encargar de obtener el texto de comprobante, y devolver los datos al diccionario, y la otra nos va ayudar a la ingesta de las facturas que tenemos listadas.

Nos resta crear un bucle que acumule los valores que me van a devolver las funciones y generar un DataFrame con los datos de los diccionarios que obtengo de mis dos facturas ejemplo.

#usando las funciones para obtener datos
#facturas seleccionadas 
data_df=[]
for i in range(len(facturas)):
    textos=parser(facturas[i])
    data_df.append(obtener_dato(textos))

Con el resultado de data_df tenemos los datos para generar un dataframe. Así de sencillo obtuvimos los datos del archivo XML usando Python. La lógica aplicada para obtener datos de la etiqueta factura se puede replicar para investigar adicional a los datos que obtenemos, por ejemplo los detalles de cada ítem comprado, con su costo unitario, o el valor de impuesto que esta dentro de la etiqueta impuestos.

Si deseas estar pendiente de mis publicaciones, tutoriales o próximos lanzamientos de cursos de análisis de datos, te invito cordialmente a suscribirte.