große xml-Dateien auslesen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Mawilo
User
Beiträge: 452
Registriert: Sonntag 22. Februar 2004, 10:58
Wohnort: Sachsen
Kontaktdaten:

Hallo,

ich habe angefangen, ein Programm zu schreiben, welches xml-Dateien verarbeitet. Das Programm soll mir die Arbeit etwas erleichtern.
Dazu muss ich bis zu 40 xml-Dateien einlesen. Die Dateien haben eine Größe von 1 bis ca. 10 MB. Aus den ganzen Dateien muss ich dann verschiedene Daten neu zusammenstellen und in zwei neue Dateien schreiben.

Da das ganze ziemlich auf die Performance geht, bin ich über Hinweise sehr dankbar.
Ich habe bisher mit minidom angefangen, die Dateien auszulesen. Die Attribute möchte ich zur weiteren Verarbeitung in eine MySQL-Datenbank laden.

Gibt es für xml bessere Wege? Evtl. unter Einbindung der dtd-Datei?

Eine kleine xml-Testdatei habe ich hier abgelegt (Ziel speichern als...). Ich hoffe, damit kann man sich einen Überblick über den Dateiaufbau meiner xml-Dateien machen.

Danke schon mal für Eure Hinweise
Mawilo
Zuletzt geändert von Mawilo am Dienstag 4. September 2007, 18:54, insgesamt 1-mal geändert.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mawilo hat geschrieben:Aus den ganzen Dateien muss ich dann verschiedene Daten neu zusammenstellen
Hallo Mawilo!

So würde es mit ElementTree funktionieren. Das ist ab Python 2.5 mit dabei. Für Python 2.4 kann man es extra installieren.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

# ab Python 2.5
from xml.etree import cElementTree as ET # die etwas schnellere C-Version


root = ET.parse("Testdatei.xml").getroot()
print dir(root)
print root.getchildren()
print
print root.getchildren()[1].getchildren()
print 

# kopfsatz
kopfsatz = root.find("kopfsatz")
if not kopfsatz is None:
    # zeitraum-beginn
    zeitraum_beginn = kopfsatz.find("zeitraum-beginn")
    if not zeitraum_beginn is None:
        print "zeitraum-beginn:", zeitraum_beginn.text
    # zeitraum-ende
    zeitraum_ende = kopfsatz.find("zeitraum-ende")
    if not zeitraum_ende is None:
        print "zeitraum-ende:", zeitraum_ende.text
    

# orders
for order in root.findall("order"):
    # kopfdaten
    kopfdaten = order.find("kopfdaten")
    if not kopfdaten is None:
        routenr = kopfdaten.find("routenr")
        if not routenr is None:
            print "  routenr:", routenr.text
        netz = kopfdaten.find("netz")
        if not netz is None:
            print "  netz:", netz.text
        #...
    # route
    # ...
So lange du die Dateien nicht alle gleichzeitig öffnest, dürfte das für einen aktuellen Computer kein Problem darstellen.

Wie die Performance von cElementTree bei 10 MB großen Dateien aussieht, das weiß ich nicht. Bitte lass uns wissen, ob es schneller als MiniDom ist. :K

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
Mawilo
User
Beiträge: 452
Registriert: Sonntag 22. Februar 2004, 10:58
Wohnort: Sachsen
Kontaktdaten:

Hallo Gerold,

na da werde ich mir mal die Version 2.5 auf den Laptop ziehen. Mal sehen, was schneller ist.

Vielen Dank
Mawilo
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Ansonsten kannst du ja immer noch sax verwenden. Damit habe zumindest ich gute Erfahrungen bezüglich Performance gemacht.

Gruss,
Jonas
Benutzeravatar
Mawilo
User
Beiträge: 452
Registriert: Sonntag 22. Februar 2004, 10:58
Wohnort: Sachsen
Kontaktdaten:

Ich habe jetzt eine 12.176 kb große Datei mit minidom und mit cElementTree ausgelesen.
Minidom hat 47,6 Sekunden und cElementTree nur 0,9 benötigt.

Also bei mir ein deutlicher Sieg für cElementTree.

Der einzigste Wermutstropfen ist, dass die Installation von Python 2.5 mit der msi-Datei nicht wirklich funktioniert hat. Ich muss immer mit Admin-rechten arbeiten, damit alles funktioniert. Mit eingeschränkten Rechten findet er nie die python.exe. :cry:

Mawilo
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mawilo hat geschrieben:Minidom hat 47,6 Sekunden und cElementTree nur 0,9 benötigt.
[...]
Mit eingeschränkten Rechten findet er nie die python.exe.
Hallo Mawilo!

Ach die paar Sekunden Unterschied... ;-)

Und das mit den Rechten kriegst du auch noch hin. Hast du Python schon in den Pfad eingetragen? Damit meine ich das hier: http://gelb.bcom.at/trac/misc/wiki/Tuto ... d-anpassen

Du kannst Python auch einfach noch einmal in den gleichen Ordner installieren (reparieren). Gibt es dabei Probleme? Wurde Python als Administrator installiert? Wenn nicht, dann Python als Admin installieren. Wurde beim Installieren darauf geachtet, dass auch wirklich für "alle" installiert wird und nicht nur für den aktuellen Benutzer? Welches Betriebssystem?

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
Mawilo
User
Beiträge: 452
Registriert: Sonntag 22. Februar 2004, 10:58
Wohnort: Sachsen
Kontaktdaten:

Meine Python-Version habe ich manuell wieder geflickt - jetzt geht alles.

Ich habe nun auch einmal eine große xml-Datei ausgelesen und versucht, die einzelnen Datensätze in die MySQL-Datenbank zu laden. War ziemlich blauäugig. Das sind aus einer Datei 1.367.582 Datensätze und es hat 293 Minuten gedauert, das in die Datenbank zu laden.

Kann man so etwas auch schneller in eine Datenbank laden? Oder gibt es andere Möglichkeiten, mit großen Datenmengen zu jounglieren?

Mawilo
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Wo wird denn die meiste Zeit verbraucht?
Verwendest du bereits executemany?

Gruss,
Jonas
BlackJack

Da ziemlich viele "Akteure" beteiligt sind, wird man wohl messen müssen wo die meiste Zeit der ca. 13 Millisekunden pro Datensatz verbraucht werden.

Wie wird das XML geparst? Komplett oder inkrementell? Wenn komplett, läuft die Datenbank auf dem gleichen Rechner und reicht der RAM für beide Prozesse ohne das ausgelagert werden muss? Wie kommen die Daten in die DB? `executemany()`? Ist das bei MySQL(db) auch effizient implementiert? Steht eine DB-Engine mit Transaktionen dahinter oder nicht? Falls ja, wird regelmässig "comitted"? Falls ja, wie häufig? Fragen über Fragen.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mawilo hat geschrieben:Das sind aus einer Datei 1.367.582 Datensätze und es hat 293 Minuten gedauert, das in die Datenbank zu laden.
Kann man so etwas auch schneller in eine Datenbank laden?
Hallo Mawilo!

Das ist ziemlich lange -- muss ich zu geben. Aber auch dann, wenn du jeden Datensatz einzeln in die DB schreibst, dann sind 29,3 Minuten eher realistisch als 293. Das ist einfach zu lange. Ist die Datenbank auf dem selben Computer? Wie sieht es mit der Speicherbelastung wärend dieses Vorganges aus? Wird ständig auf die Auslagerungsdatei ausgelagert? Oder wird sogar nach jedem Datensatz ein ``conn.commit()`` ausgeführt? Das würde die Sache natürlich extrem verlangsamen.

Mein Geschwindigkeitstipp:
- Schreibe große SQL-Dateien mit je 500.000 Datensätzen ins Dateisystem. Also Dateien mit vielen INSERT-Anweisungen.
- Übertrage die SQL-Dateien zum Server auf dem der MySQL-Server läuft. (Z.B. mit FTP)
- Führe diese SQL-Dateien mit dem Kommandozeilenprogramm "mysql" auf dem Computer aus auf dem der MySQL-Server läuft.

Z.B. so: ``mysql database < sqldaten.sql``

Viel schneller als so wirst du die Daten kaum mehr in den MySQL-Server bekommen.

Wenn du nicht weißt, wie so eine SQL-Datei aussehen könnte, dann mach einfach mal eine Datensicherung mit dem Programm "mysqldump". Das erstellt solche SQL-Dateien.

EDIT:

Ich persönlich verwende gerne "scp" um die Daten zum Server zu übertragen und dann "ssh" um Programme wie z.B. "mysql" direkt auf dem Server auszuführen. Das funktioniert wunderbar. Sogar mit Cygwin auf Windows-Servern.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Echte Helden können über ssh sogar Pipen (:

Code: Alles auswählen

cat dump.sql|ssh example.com 'mysql database'
oder so :)
Benutzeravatar
Mawilo
User
Beiträge: 452
Registriert: Sonntag 22. Februar 2004, 10:58
Wohnort: Sachsen
Kontaktdaten:

Hallo,

ich habe das Programm auf meinem Laptop (w2k) ausgeführt. Die Datenbank ist eine MySQL-Datenbank, die ich mit XAMPP installiert habe. Es läuft also alles lokal auf meinem Rechner. Die xml-Datei wurde komplett eingelesen und dann mit cElementTree geparst. Wenn ich die xml-Datei nur lese, dauert das ca. 30 Sekunden.
Ein Datensatz besteht aus 32 Elementen. Ich lese einen Datensatz aus, erstelle mit den 32 Elementen ein Dictionary und lade das Dictionary mit execute() in die Datenbank. Mit executemany() habe ich noch nicht gearbeitet (und es jetzt auch nicht hinbekommen :oops: )
Mein Versuch mit executemany sieht (gekürzt) so aus:

Code: Alles auswählen

datensatz = (self.daten, ) # ist ein eigentlich ein Dictionary 
self.cursor.executemany(""" INSERT INTO my_daten (zeitraum_beginn, zeitraum_ende, ..... ) VALUES ('?')""", datensatz)
Da bekomme ich immer diese Meldung:

Code: Alles auswählen

raise errorclass, errorvalue
OperationalError: (1136, "Column count doesn't match value count at row 1")
Oder kann man das Dictionary anders übergeben?

Mawilo
BlackJack

Wie sieht das denn mit dem Speicherverbrauch und dem Auslagern auf Platte aus?

Die SQL-Anweisung muss genauso aussehen wie bei `execute()`, nur anstatt der Werte muss man statt *einem* Datensatz ein "iterable" von Datensätzen angeben. Das ein Fragezeichen nicht ausreicht um mehrere Spaltenwerte zu übergeben sollte klar sein. Ausserdem sind die ' um den Platzhalter nicht nötig bzw. falsch. Und ein Dictionary funktioniert so natürlich nicht, weil ja gar nicht klar ist, in welcher Reihenfolge die Werte in die SQL-Anweisung eingesetzt werden sollen.

Um den Speicherbedarf möglichst gering zu halten, könnte man das XML inkrementell Verarbeiten. Ab besten in einer Generatorfunktion verpackt, welche jeweils einen Datensatz für `executemany()` liefert.
Benutzeravatar
Mawilo
User
Beiträge: 452
Registriert: Sonntag 22. Februar 2004, 10:58
Wohnort: Sachsen
Kontaktdaten:

BlackJack hat geschrieben:Und ein Dictionary funktioniert so natürlich nicht, weil ja gar nicht klar ist, in welcher Reihenfolge die Werte in die SQL-Anweisung eingesetzt werden sollen.
In meinem Dictionary sind die Namen der Keys identisch mit den Feldnamen der Datenbank.
BlackJack hat geschrieben:Und Ab besten in einer Generatorfunktion verpackt, welche jeweils einen Datensatz für `executemany()` liefert.
Ich schreibe schon für jeden Datensatz ein Dictionary und übergebe immer nur einen Datensatz an die Klasse zum Einlesen.

Mit executemany werde ich mich heute Abend noch mal näher beschäftigen. Ich habe erst mal eine Funktion eingebaut, um Datensätze zu filtern, die ich später sowieso wegwerfe. Das reduziert die Datenmass schon mal ein wenig.
BlackJack

Die Datenbank kennt keine Python-Dictionaries, also nützt die Namensgleichheit da nicht so viel. Es sei denn, das Datenbankmodul versteht auch eine der "named" Platzhalter-Arten. Dann muss man die Namen aber nochmal alle in der SQL-Anweisung entsprechend hinschreiben.
Benutzeravatar
Mawilo
User
Beiträge: 452
Registriert: Sonntag 22. Februar 2004, 10:58
Wohnort: Sachsen
Kontaktdaten:

Ich brauche noch einmal Hilfe. Entweder habe ich ein Brett vorm Kopf oder es ist heute einfach zu warm.

Folgender Code sollte doch eigentlich funktionieren:

Code: Alles auswählen

import MySQLdb

host = 'localhost'
db = 'test'
user = 'root'

connect = MySQLdb.connect(host=host, db=db, user=user)
c = connect.cursor()

c.executemany("""INSERT INTO testtabelle 
    (wert1, wert2, wert3) VALUES(?, ?, ?)""", [('a','b','c'), ('b','c','d'),('c','d','e')])
c.close()
Aber leider bekomme ich nur diese Meldung:

Code: Alles auswählen

raise errorclass, errorvalue
TypeError: not all arguments converted during string formatting
Warum geht das nicht?

Mawilo

*** eventuell sollte das ein Admin mal ins Datenbankforum legen ***
BlackJack

Versteht MySQLdb überhaupt die Fragezeichen? Ich glaube das Modul erwartet '%s' als Platzhalter.
Benutzeravatar
Mawilo
User
Beiträge: 452
Registriert: Sonntag 22. Februar 2004, 10:58
Wohnort: Sachsen
Kontaktdaten:

war eben doch zu warm :D

In MySQL müssen die Platzhalter natürlich als %s geschrieben werden.

Danke
Antworten