2012-03-21 9 views
9

¿Cómo leerías un archivo XML usando sax y lo convertirías en un elemento lxml etree.iterparse?Python sax a lxml para 80 + GB XML

Para proporcionar una visión general del problema, he creado una herramienta de ingestión de XML utilizando lxml para una fuente XML que tendrá un tamaño de 25 - 500MB que necesita ingestión dos veces al día, pero necesita realizar una Ingestión de una vez de un archivo de 60 a 100 GB.

Había elegido utilizar lxml según las especificaciones que detallaban que un nodo no superaría los 4 -8 GB de tamaño, lo que pensé que permitiría que el nodo se lea en la memoria y se borre cuando haya terminado.

Una visión general si el código está por debajo

elements = etree.iterparse(
    self._source, events = ('end',) 
) 
for event, element in elements: 
    finished = True 
    if element.tag == 'Artist-Types': 
     self.artist_types(element) 

def artist_types(self, element): 
    """ 
    Imports artist types 

    :param list element: etree.Element 
    :returns boolean: 
    """ 
    self._log.info("Importing Artist types") 
    count = 0 
    for child in element: 
     failed = False 
     fields = self._getElementFields(child, (
      ('id', 'Id'), 
      ('type_code', 'Type-Code'), 
      ('created_date', 'Created-Date') 
     )) 
     if self._type is IMPORT_INC and has_artist_type(fields['id']): 
      if update_artist_type(fields['id'], fields['type_code']): 
       count = count + 1 
      else: 
       failed = True 
     else: 
      if create_artist_type(fields['type_code'], 
       fields['created_date'], fields['id']): 
       count = count + 1 
      else: 
       failed = True 
     if failed: 
      self._log.error("Failed to import artist type %s %s" % 
       (fields['id'], fields['type_code']) 
      ) 
    self._log.info("Imported %d Artist Types Records" % count) 
    self._artist_type_count = count 
    self._cleanup(element) 
    del element 

Avísame si puedo añadir cualquier tipo de aclaración.

+0

¿Cuál es la pregunta? ¿Recibió un mensaje de error? –

+2

La pregunta está en la primera oración ... ¿por qué el voto a favor? – Nick

+0

Tu pregunta es un poco extraña. ¿Por qué estás usando SAX en absoluto? iterparse es * una alternativa a * SAX. Podrías generar eventos iterparse desde eventos SAX, pero ¿por qué alguien haría eso? –

Respuesta

19

iterparse es un analizador iterativo. Emitirá Element objetos y eventos y construirá incrementalmente todo el árbol Element mientras analiza, por lo que eventualmente tendrá todo el árbol en la memoria.

Sin embargo, es fácil tener un comportamiento de memoria limitado: borre los elementos que ya no necesita al analizarlos.

La carga de trabajo típica "gigante xml" es un único elemento raíz con una gran cantidad de elementos secundarios que representan registros. ¿Asumo que este es el tipo de estructura XML con la que estás trabajando?

Por lo general, es suficiente usar clear() para vaciar el elemento que está procesando. Su uso de memoria crecerá un poco, pero no es mucho. Si tiene un archivo muy grande, incluso los objetos vacíos Element consumirán demasiado y en este caso también debe eliminar los objetos Element previamente vistos. Tenga en cuenta que no puede eliminar de forma segura el elemento actual. El lxml.etree.iterparse documentation describes this technique.

En este caso, procesará un registro cada vez que se encuentre </record>, luego eliminará todos los elementos de registro previos.

A continuación se muestra un ejemplo con un documento XML infinitamente largo. Imprimirá el uso de memoria del proceso a medida que lo analiza. Tenga en cuenta que el uso de la memoria es estable y no continúa creciendo.

from lxml import etree 
import resource 

class InfiniteXML (object): 
    def __init__(self): 
     self._root = True 
    def read(self, len=None): 
     if self._root: 
      self._root=False 
      return "<?xml version='1.0' encoding='US-ASCII'?><records>\n" 
     else: 
      return """<record>\n\t<ancestor attribute="value">text value</ancestor>\n</record>\n""" 

def parse(fp): 
    context = etree.iterparse(fp, events=('end',)) 
    for action, elem in context: 
     if elem.tag=='record': 
      # processing goes here 
      pass 

     #memory usage 
     print resource.getrusage(resource.RUSAGE_SELF).ru_maxrss 

     # cleanup 
     # first empty children from current element 
      # This is not absolutely necessary if you are also deleting siblings, 
      # but it will allow you to free memory earlier. 
     elem.clear() 
     # second, delete previous siblings (records) 
     while elem.getprevious() is not None: 
      del elem.getparent()[0] 
     # make sure you have no references to Element objects outside the loop 

parse(InfiniteXML()) 
+0

No hay un solo nodo "raíz", en lugar de eso los datos se dividen en más de 20 nodos "raíz" que contienen sus propios subconjuntos. La herramienta actual funciona de manera similar a su código en cuanto a la eliminación de nodos innecesarios una vez procesados, lo que permite procesar una gran parte de los datos, pero una vez que intento procesar uno de los nodos más grandes, "estoy asumiendo un tamaño mayor a 8GB "el proceso Segmentará (en el bucle for)' '' para la acción, elem en contexto: '' 'lo cual me lleva a creer que se está leyendo en la memoria. – Nick

+0

¿Podría mostrar algún ejemplo de XML? El código que publicó solo parece mostrar un tipo de elemento principal. Iterparse no lee el archivo completo en la memoria, por lo que se trata de dividir el flujo de trabajo en subárboles más pequeños que * hacen * que quepan en la memoria, y eliminar todo después de cada iteración. –

+1

El código publicado anteriormente es casi lo mismo que puedo dar, lamentablemente, pero dicho esto después de volver a escribir una buena parte de la ingestión, la importación ahora funciona utilizando su enfoque anterior. Consulte el siguiente fragmento del código https://gist.github.com/2161849. – Nick

3

Encontré este útil ejemplo en http://effbot.org/zone/element-iterparse.htm. El énfasis audaz es mío.

incremental de análisis #

Tenga en cuenta que todavía iterparse construye un árbol, al igual que de análisis, pero se puede reorganizar de forma segura o eliminar partes del árbol durante el análisis. Por ejemplo, para analizar archivos de gran tamaño, usted puede deshacerse de elementos tan pronto a medida que los haya procesado:

for event, elem in iterparse(source): 
    if elem.tag == "record": 
     ... process record elements ... 
     elem.clear() 

El patrón anterior tiene un inconveniente; no borra el elemento raíz, por lo que terminará con un elemento único con muchos elementos secundarios vacíos. Si sus archivos son enormes, en lugar de grandes, podría ser un problema. Para solucionar esto, debe tener en sus manos el elemento raíz. La forma más sencilla de hacerlo es permitir a los eventos de inicio y guardar una referencia al primer elemento en una variable:

# get an iterable 
context = iterparse(source, events=("start", "end")) 

# turn it into an iterator 
context = iter(context) 

# get the root element 
event, root = context.next() 

for event, elem in context: 
    if event == "end" and elem.tag == "record": 
     ... process record elements ... 
     root.clear() 

(futuras versiones, será más fácil para acceder al elemento raíz desde el interior el bucle)

+0

Gracias por la respuesta, pero ya lo he explorado y al menos de mi prueba el nodo todavía se lee completamente en la memoria y no se transmite – Nick

0

Este es un par de años y no tiene suficiente reputación a comentar directamente sobre la respuesta aceptada, pero intentado usar esta para analizar un OSM donde estoy encontrando todas las intersecciones en un país. Mi problema original era que me estaba quedando sin RAM, así que pensé que tendría que usar el analizador SAX, pero encontré esta respuesta en su lugar. Curiosamente, no estaba analizando correctamente, y usar la limpieza sugerida de alguna manera fue borrar el nodo elem antes de leerlo (todavía no estoy seguro de cómo estaba pasando esto). Se eliminó elem.clear() del código y ahora funciona perfectamente bien.

Cuestiones relacionadas