2012-01-17 10 views
12

Sto cercando di analizzare un file XML che supera i 2 GB con la libreria lxml di Python. Sfortunatamente, il file XML non ha una riga che indica la codifica dei caratteri, quindi devo impostarlo manualmente. Durante l'iterazione del file, tuttavia, ci sono ancora strani personaggi che emergono una volta ogni tanto.Come dovrei gestire un XMLSyntaxError nel lxml di Python mentre analizzo un grande file XML?

Non sono sicuro di come determinare la codifica dei caratteri della linea, ma inoltre, lxml genererà un errore XMLSyntaxError dall'ambito del ciclo for. Come posso rilevare correttamente questo errore e gestirlo correttamente? Ecco un frammento di codice semplicistica:

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() 

Questo alla fine genera l'errore:

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

Quella riga del file è simile al seguente:

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

La 'F' di pieno effettivamente appare come questo nel mio terminale:

xml line causing the error

+0

Hai già provato semplicemente "utf-8" per una codifica? – jsbueno

+1

@jsbueno: il problema è il carattere appena prima della "F" in "Filled", che ha un valore di 31 (decimale) o 0x1F. Questo è un carattere non valido per la specifica XML, quindi dirlo di usare la codifica UTF-8 non farà la differenza. La domanda è come ottenere lxml per affrontare i personaggi cattivi con più grazia (ad esempio, non lanciare un'eccezione). Non ho trovato un'opzione per farlo nel documento lxml. –

risposta

7

La cosa giusta da fare qui è assicurarsi che il creatore del file XML si assicura che: A.) che la codifica del file viene dichiarato B.) che il file XML è ben formato (senza caratteri non validi, caratteri di controllo, caratteri non validi che non rientrano nello schema di codifica, tutti gli elementi sono chiusi correttamente ecc.) C.) utilizzare un DTD o uno schema XML se si desidera garantire che alcuni attributi/elementi esistano, avere certe valori o corrispondono a un determinato formato (nota: questo richiederà un colpo di prestazioni)

Quindi, ora alla tua domanda. LXml supporta un sacco di argomenti quando lo si usa per analizzare XML. Check out the documentation. Volete dare un'occhiata a questi due argomenti:

-> recuperare -> provare ad analizzare tramite XML spezzato
-> huge_tree -> disabilitare le restrizioni di sicurezza e supportare alberi molto profondi e contenuti di testo molto lunghi (interessa solo libxml2 2.7+)

Ti aiuteranno in una certa misura, ma alcuni caratteri non validi non possono essere recuperati, quindi, di nuovo, assicurati che il file sia scritto correttamente è la soluzione migliore per pulire/codice funzionante .

Ah sì e un'altra cosa. 2 GB è enorme. Presumo che tu abbia un elenco di elementi simili in questo file (esempio elenco di libri). Prova a dividere il file con un'espressione Regex sul sistema operativo, quindi avviare più processi per separare i file. In questo modo sarai in grado di utilizzare più core sul tuo box e il tempo di elaborazione diminuirà. Ovviamente dovrai affrontare la complessità della fusione dei risultati. Non posso fare questo trade off per voi, ma ha voluto dare a voi come "cibo per la mente"

Aggiunta di inviare: Se si dispone di alcun controllo sul file di input e avere personaggi cattivi in ​​esso, Proverei a sostituire/rimuovere questi caratteri errati eseguendo un iterazione sulla stringa prima di analizzarla come file.Ecco un esempio di codice che rimuove 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, ma 'iterparse' è un parser basato su eventi, quindi può gestire file enormi bene. –

+1

Sfortunatamente, il file XML arriva in un carico utile notturno da una terza parte. Non ho alcun controllo sul contenuto in esso. Detto questo, non ho alcun controllo sulla dichiarazione della codifica del file, che il file non ha. Il file XML non è ben formato, contiene alcuni caratteri strani. E il file non si abbona a nessun DTD o schema XML e il venditore non sembra nemmeno capire di cosa si tratta ... Sfortunatamente, sono qui da solo. – blackrobot

+0

Nel tuo codice, dove usi 'unichr', intendi' ord'. – maurits

0

Il codecs Python alimentazione del modulo una classe EncodedFile che funziona come un involucro in un file - si dovrebbe passare un oggetto di questa classe per lxml, destinato a sostituire i caratteri sconosciuti con char XML entità -

provare a fare questo:

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") 
... 

il "xmlcharrefreplace" costante passato è il parametro "errori", e specifica cosa fare con personaggi sconosciuti. Potrebbe essere "strict" (genera un errore), "ignore" (lasciare così com'è), "replace" (sostituisce char con "?"), "Xmlrefreplace" (crea un riferimento "x#xxxx" o "xml") o " backslahreplace "(crea un riferimento backslash valido Python). Per ulteriori informazioni, consultare: http://docs.python.org/library/codecs.html

+1

Sfortunatamente, questo sembra dare lo stesso errore, anche se uso "ignora" o "sostituisci". 'XMLSyntaxError: PCDATA valore Char non valido 31, riga 1565367, colonna 50' – blackrobot

3

mi sono imbattuto in questo troppo, ottenendo \x16 nei dati (l'unicode 'inattività sincrona' o il carattere 'SYN', visualizzata nel xml come ^V) che porta ad un errore durante l'analisi del xml: XMLSyntaxError: PCDATA invalid Char value 22. Il 22 è perché perché ord('\x16') è 22.

La risposta di @michael mi ha messo sulla strada giusta. Ma alcuni caratteri di controllo inferiori a 32 vanno bene, come il ritorno o la scheda, e alcuni caratteri più alti sono ancora cattivi. Quindi:

# 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) 

Questo porta ad un elenco di 31 caratteri che può essere codificato invece di fare il calcolo di cui sopra in codice:

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] 

quindi utilizzarlo in questo modo:

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 

Se lo value è di 2 Gigabyte, potrebbe essere necessario farlo in un modo più efficiente, ma qui lo ignoro, anche se la domanda lo menziona. Nel mio caso, sono io quello che crea il file xml, ma ho bisogno di trattare questi caratteri nei dati originali, quindi userò questa funzione prima di inserire i dati nell'xml.

Problemi correlati