2012-01-17 16 views
12

Estoy tratando de analizar un archivo XML de más de 2 GB con la biblioteca lxml de Python. Desafortunadamente, el archivo XML no tiene una línea que indique la codificación de caracteres, así que tengo que configurarlo manualmente. Sin embargo, al iterar a través del archivo, todavía hay algunos caracteres extraños que aparecen de vez en cuando.¿Cómo debo manejar un XMLSyntaxError en el archivo lxml de Python al analizar un archivo XML de gran tamaño?

No estoy seguro de cómo determinar la codificación de caracteres de la línea, pero además, lxml generará un XMLSyntaxError del alcance del bucle for. ¿Cómo puedo detectar este error correctamente y tratarlo correctamente? He aquí un fragmento de código simplista:

from lxml import etree 
etparse = etree.iterparse(file("my_file.xml", 'r'), events=("start",), encoding="CP1252") 
for event, elem in etparse: 
    if elem.tag == "product": 
     print "Found the product!" 
     elem.clear() 

Esto a la larga produce el error:

XMLSyntaxError: PCDATA invalid Char value 31, line 1565367, column 50

Esa línea del archivo es el siguiente:

% sed -n "1565367 p" my_file.xml 
<romance_copy>Ravioli Florentine. Tender Ravioli Filled With Creamy Ricotta Cheese And 

La 'F' de llenado en realidad se ve así en mi terminal:

xml line causing the error

+0

¿Ya has probado "utf-8" para una codificación? – jsbueno

+1

@jsbueno: El problema es el carácter justo antes de la "F" en "Rellenar", que tiene un valor de 31 (decimal) o 0x1F. Este es un carácter no válido según la especificación XML, por lo que decir que use la codificación UTF-8 no hará la diferencia. La pregunta es cómo hacer que lxml lidie con los personajes malos con más elegancia (es decir, no lanzar una excepción). No encontré una opción para hacer esto en el documento lxml. –

Respuesta

7

Lo que hay que hacer aquí es asegurarse de que el creador del archivo XML se asegura de que: A. ) que la codificación del archivo se declara B.) que el archivo XML está bien formado (sin los caracteres no válidos controlan los caracteres, no hay caracteres no válidos que no caigan en el esquema de codificación, todos los elementos se cierran correctamente, etc.) C.) use un DTD o un esquema XML si desea asegurarse de que existan ciertos atributos/elementos, tenga cierta certeza valores o corresponden a un determinado formato (nota: esto tomará un golpe de rendimiento)

Entonces, ahora a su pregunta. LXml admite una gran cantidad de argumentos cuando lo usa para analizar XML. Check out the documentation. Usted tendrá que buscar en estos dos argumentos:

-> recuperarse -> esforzarse para analizar a través de XML roto
-> huge_tree -> desactivar las restricciones de seguridad y de apoyo árboles muy profundos y contenido de texto muy largo (sólo afecta a libxml2 2.7+)

que le ayudará en cierta medida, pero ciertos caracteres no válidos pueden no sólo ser recuperados de, por lo que una vez más, lo que garantiza que el archivo está escrito correctamente es la mejor opción para limpiar código/pocillo de trabajo .

Ah sí y una cosa más. 2GB es enorme Supongo que tiene una lista de elementos similares en este archivo (ejemplo de lista de libros). Intente dividir el archivo con una expresión Regex en el sistema operativo, luego inicie varios procesos para separar las piezas. De esta forma, podrá usar más de sus núcleos en su caja y el tiempo de procesamiento disminuirá. Por supuesto, tendrá que lidiar con la complejidad de fusionar los resultados nuevamente. No puedo hacer esta solución de compromiso para usted, pero quería darle a usted como "alimento para el pensamiento"

además del puesto: Si no tienen control sobre el archivo de entrada y tienen mala disposición en el mismo, Intentaría reemplazar/eliminar estos caracteres incorrectos al iterar sobre la cadena antes de analizarla como un archivo.Aquí un ejemplo de código que elimina Unicode control characters that you wont need:

#all unicode characters from 0x0000 - 0x0020 (33 total) are bad and will be replaced by "" (empty string) 
for line in fileinput.input(xmlInputFileLocation, inplace=1): 
    for pos in range(0,len(line)): 
     if unichr(line[pos]) < 32: 
      line[pos] = None 
    print u''.join([c for c in line if c]) 
+0

+1, pero 'iterparse' es un analizador basado en eventos, por lo que puede manejar archivos enormes muy bien. –

+1

Desafortunadamente, el archivo XML viene en una carga útil nocturna de un tercero. No tengo ningún control sobre el contenido en él. Dicho esto, no tengo ningún control sobre la declaración de la codificación del archivo, que el archivo no tiene. El archivo XML no está bien formado, tiene algunos caracteres extraños. Y el archivo no se suscribe a ningún DTD o esquema XML, y el proveedor ni siquiera parece entender qué es eso ... Lamentablemente, estoy solo aquí. – blackrobot

+0

En su código, donde usa 'unichr' quiere decir' ord'. – maurits

0

El codecs Python alimentación del módulo una clase EncodedFile que funciona como un contenedor a un archivo - debe pasar un objeto de esta clase para LXML, establecido para reemplazar caracteres desconocidos con Char entidades XML -

trate de hacer esto:

from lxml import etree 
import codecs 

enc_file = codecs.EncodedFile(file("my_file.xml"), "ASCII", "ASCII", "xmlcharrefreplace") 

etparse = etree.iterparse(enc_file, events=("start",), encoding="CP1252") 
... 

el "xmlcharrefreplace" constante pasado es el parámetro de "errores", y especifica qué hacer con personajes desconocidos. Podría ser "estricto" (provoca un error), "ignorar" (dejar como está), "reemplazar" (reemplaza el carácter "?"), "Xmlrefreplace" (crea una "& #xxxx;" referencia xml) o " backslahreplace "(crea una referencia de barra diagonal inversa válida de Python). Para obtener más información, consulte: http://docs.python.org/library/codecs.html

+1

Desafortunadamente, esto parece dar el mismo error, incluso si uso "ignorar" o "reemplazar". 'XMLSyntaxError: PCDATA valor de Char no válido 31, línea 1565367, columna 50 ' – blackrobot

3

me encontré con esto también, conseguir \x16 de datos (el Unicode 'inactivo síncrona' o el carácter 'SYN', que se muestra en el XML como ^V), que conduce a un error al analizar el xml: XMLSyntaxError: PCDATA invalid Char value 22. El 22 se debe a que ord('\x16') es 22.

La respuesta de @michael me puso en el camino correcto. Pero algunos caracteres de control por debajo de 32 están bien, como el retorno o la pestaña, y algunos caracteres más altos siguen siendo malos. Por lo tanto:

# Get list of bad characters that would lead to XMLSyntaxError. 
# Calculated manually like this: 
from lxml import etree 
from StringIO import StringIO 
BAD = [] 
for i in range(0, 10000): 
    try: 
     x = etree.parse(StringIO('<p>%s</p>' % unichr(i))) 
    except etree.XMLSyntaxError: 
     BAD.append(i) 

Esto lleva a una lista de 31 caracteres que se pueden codificado en vez de hacer el cálculo anterior en el código:

BAD = [ 
    0, 1, 2, 3, 4, 5, 6, 7, 8, 
    11, 12, 
    14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 
    # Two are perfectly valid characters but go wrong for different reasons. 
    # 38 is '&' which gives: xmlParseEntityRef: no name. 
    # 60 is '<' which gives: StartTag: invalid element namea different error. 
] 
BAD_BASESTRING_CHARS = [chr(b) for b in BAD] 
BAD_UNICODE_CHARS = [unichr(b) for b in BAD] 

luego usarlo como esto:

def remove_bad_chars(value): 
    # Remove bad control characters. 
    if isinstance(value, unicode): 
     for char in BAD_UNICODE_CHARS: 
      value = value.replace(char, u'') 
    elif isinstance(value, basestring): 
     for char in BAD_BASESTRING_CHARS: 
      value = value.replace(char, '') 
    return value 

Si value es de 2 Gigabytes, puede que necesite hacer esto de una manera más eficiente, pero ignoro eso aquí, aunque la pregunta lo menciona. En mi caso, yo soy el que está creando el archivo xml, pero necesito tratar estos caracteres en los datos originales, así que usaré esta función antes de poner datos en el xml.

Cuestiones relacionadas