2012-03-24 14 views
10

Tengo que analizar un archivo XML de 1Gb con una estructura como la siguiente y extraer el texto dentro de las etiquetas "Autor" y "Contenido":usando lxml e iterparse() para analizar un gran archivo XML (+ - 1Gb)

<Database> 
    <BlogPost> 
     <Date>MM/DD/YY</Date> 
     <Author>Last Name, Name</Author> 
     <Content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.</Content> 
    </BlogPost> 

    <BlogPost> 
     <Date>MM/DD/YY</Date> 
     <Author>Last Name, Name</Author> 
     <Content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.</Content> 
    </BlogPost> 

    [...] 

    <BlogPost> 
     <Date>MM/DD/YY</Date> 
     <Author>Last Name, Name</Author> 
     <Content>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula.</Content> 
    </BlogPost> 
</Database> 

Hasta ahora he intentado dos cosas: i) la lectura de todo el archivo y que pasan por allí con .find (xmltag) y ii) analizar el archivo XML con lxml y iterparse(). La primera opción que tengo funciona, pero es muy lenta. La segunda opción no he logrado despegarla.

Aquí es parte de lo que tengo:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"): 
    if element.tag == "BlogPost": 
     print element.text 
    else: 
     print 'Finished' 

El resultado de eso es sólo espacios en blanco, sin texto en ellos.

Debo estar haciendo algo mal, pero no puedo entenderlo. Además, en caso de que no fuera lo suficientemente obvio, soy bastante nuevo en Python y es la primera vez que uso lxml. ¡Por favor ayuda!

+1

Bueno, las etiquetas 'BlogPost' no parecen contener ningún texto en ellas. –

+0

cierto. ¿Cuál sería la forma de obtener todo lo que está entre la etiqueta de apertura y cierre de BlogPost? – mvime

+0

Si simplemente necesita toda la información de las etiquetas 'BlogPost', siga los consejos de andrew. Si lo quiere con formato HTML, aplique 'lxml.etree.tostring()' a ellos. –

Respuesta

18
for event, element in etree.iterparse(path_to_file, tag="BlogPost"): 
    for child in element: 
     print child.tag, child.text 
    element.clear() 

la final clara se le impida el uso de demasiada memoria.

[actualización] para obtener "todo lo que entre ... como una cadena" Creo que se quiere uno de:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"): 
    print etree.tostring(element) 
    element.close() 

o

for event, element in etree.iterparse(path_to_file, tag="BlogPost"): 
    print ''.join([etree.tostring(child) for child in element]) 
    element.close() 

o quizás incluso:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"): 
    print ''.join([child.text for child in element]) 
    element.close() 
+0

Esto funciona más o menos como quería. Tendré que personalizarlo un poco, pero es genial. ¡Gracias! – mvime

+0

¿Hay alguna manera de obtener todo lo que se encuentra entre el inicio y el final de las etiquetas "BlogPost" como una cadena? – mvime

+1

@mvime, ¿qué tipo de cadena? En formato HTML? Luego vea mi comentario anterior, el método 'lxml.etree.tostring()' hace eso. Puede cortar la etiqueta de apertura y cierre utilizando la notación de división (consulte [esta tabla] (http://docs.python.org/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer -xrange)) –

4

prefiero XPath para este tipo de cosas:

In [1]: from lxml.etree import parse 

In [2]: tree = parse('/tmp/database.xml') 

In [3]: for post in tree.xpath('/Database/BlogPost'): 
    ...:  print 'Author:', post.xpath('Author')[0].text 
    ...:  print 'Content:', post.xpath('Content')[0].text 
    ...: 
Author: Last Name, Name 
Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula. 
Author: Last Name, Name 
Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula. 
Author: Last Name, Name 
Content: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas dictum dictum vehicula. 

No estoy seguro si es diferente en términos de procesamiento de archivos de gran tamaño, sin embargo. Los comentarios sobre esto serían apreciados.

Hacerlo a tu manera,

for event, element in etree.iterparse(path_to_file, tag="BlogPost"): 
    for info in element.iter(): 
     if info.tag in ('Author', 'Content'): 
      print info.tag, ':', info.text 
+0

mm He simplificado un poco el árbol y cuando lo intento no parece funcionar. La etiqueta BlogPost por ejemplo no es simplemente '' sino '' y los valores para Propietario y Estado cambian de una entrada a la otra. – mvime

+1

Los atributos adicionales no afectarán esto; solo la estructura del árbol importa. Para capturar todos los elementos de 'BlogPost', también puede usar' para publicar en tree.xpath ('// BlogPost'): ... ' –

+1

¡Gracias! Aún no puedo votar, pero me ayudaste a entender cómo funciona. La respuesta que entiendo mejor y que he llegado a trabajar es la de Andrew. – mvime

7

Para buscadores futuros: la respuesta superior aquí sugiere borrar el elemento en cada iteración, pero eso todavía te deja con un aumento constante conjunto de elementos vacíos que se construyen lentamente en la memoria:

for event, element in etree.iterparse(path_to_file, tag="BlogPost"): 
    for child in element: 
     print child.tag, child.text 
    element.clear() 

^Esto no es una solución escalable, especialmente como archivo original se hace más grande y más grande. La mejor solución es obtener el elemento raíz , y borrar que cada vez que carga un registro completo. Esto mantendrá el uso de la memoria bastante estable (menos de 20 MB diría).

Aquí hay una solución que no requiere buscar una etiqueta específica. Esta función devolverá un generador que produce todos los nodos 1st child (por ejemplo, <BlogPost> elementos) debajo del nodo raíz (por ejemplo, <Database>). Lo hace registrando el inicio de la primera etiqueta después del nodo raíz, luego esperando la etiqueta final correspondiente, produciendo el elemento completo y luego borrando el nodo raíz.

from lxml import etree 

xmlfile = '/path/to/xml/file.xml' 

def iterate_xml(xmlfile): 
    doc = etree.iterparse(xmlfile, events=('start', 'end')) 
    _, root = next(doc) 
    start_tag = None 
    for event, element in doc: 
     if event == 'start' and start_tag is None: 
      start_tag = element.tag 
     if event == 'end' and element.tag == start_tag: 
      yield element 
      start_tag = None 
      root.clear() 
Cuestiones relacionadas