aus XML-Datei ausgelesene Strings in Dataframe schreiben?

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
Sarnelia
User
Beiträge: 7
Registriert: Freitag 8. Januar 2021, 17:55

Guten Abend,

ich bin neu hier und melde mich, da ich momentan einfach nicht weiter komme...

Ich beschäftige mich gerade mit bibliothekarischen Daten, die meist als XML zur Verfügung stehen und möchte diese nach bestimmten Wörtern durchsuchen und die gefundenen Schnipsel in ein Dataframe schreiben. Ich habe dazu die XML-Datei importiert, habe ein Funktion für die Suche nach einem bestimmten XML-Element, mit der ich mir den Inhalt ausgeben lassen kann und bin jetzt an der Stelle, an der ich die gefundenen Ergebnisse gerne passend ausgeben lassen würde, aber irgendwo in meiner Schleife ist noch der Wurm drin bzw. fehlt mir einfach die Erfahrung und das Know-how...

Mit dem Code unten schaffe ich es, dass mir im passenden Unterfeld für Titel in den Bibliotheksdaten nach dem Wort "Analyse" gesucht wird und mit "print(coll)" spuckt er mir auch die Titel entsprechend aus. Soweit so gut. Soweit ich das ganze verstehe, eröffne ich oben eine Liste, in die ich weiter unten in der Schleife, wenn meine zweite Bedingung erfüllt ist (dass das Keyword auch im Titel enthalten ist), den jeweiligen Titel zufüge. Das funktioniert aber nicht wirklich, mir scheint, dass er zwar am Ende des Schleifendurchlaufs brav den Titel ausgibt, beim nächsten Durchlauf "coll" aber überschreibt und entsprechend den nächsten Titel ausgibt. An sich zwar nicht schlecht, aber eigentlich möchte ich die Titel erst sammeln und nach dem Durchlauf der Schleife ausgeben bzw. dann eben mit einer Liste oder idealerweise eben einem Dataframe weiterarbeiten können. Mir ist klar, dass in diesem Fall "print" dafür an der falschen Stelle steht. Wenn ich es allerdings vorziehe, gibt er mir auch die ganzen Leerfelder aus, in denen die Bedingung nicht erfüllt war.

Code: Alles auswählen


....

coll = []
keyword = 'Analyse'        
    
    for child in record.findall('{http://www.loc.gov/MARC21/slim}datafield'):
           title = getSubfieldText(child, "245", "a")
        if title:
            has_title = True
            content = str(title)
            if keyword in content:
                coll += [title]
            else:
                continue
            print(coll)
  
Was mache ich falsch? Bzw. wie bekomme ich meine Ausgaben passend in eine Datei umgeleitet? Über Hilfe würde ich mich sehr freuen, aktuell schwirrt mir der Kopf und ich komme einfach nicht weiter :?

Viele Grüße
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Was macht denn getSubfieldText? Funktionen werden wie Variablennamen komplett klein geschrieben.
Die Einrückungen sind fehlerhaft. So kann man nicht genau sagen, wie der Programmablauf ist.
Daten hängt man an Listen mit append.
Das continue an der Stelle macht den Programmfluss sehr unübersichtlich. Versuche ohne auszukommen.
Was sagt denn die print-Ausgabe?
Sarnelia
User
Beiträge: 7
Registriert: Freitag 8. Januar 2021, 17:55

Hallo und erst einmal vielen Dank für die schnelle Antwort!

Danke für den Hinweis bzgl. der Einrückung!
Die Funktion getSubfieldText sieht folgendermaßen aus:

Code: Alles auswählen

def getSubfieldText(elem, marcTag, subfield):
    if (elem.attrib['tag'] == marcTag) :
            subelems = list(elem)
            for subelem in subelems:
                if subelem.attrib['code'] == subfield:
                    return subelem.text.encode('UTF8')
    else:
        return False
Die Funktion soll allgemein in der Lage sein, aus verschiedenen Subfields den Text zu holen, im zuerst/oben geposteten Code bekommt sie als Ziele das Feld "245" und Subfield "a" übergeben, hier findet sich der Titel. Der zugehörige XML-Code sieht - stark gekürzt - folgendermaßen aus:

Code: Alles auswählen

<?xml version="1.0" encoding="UTF-8"?><collection xmlns="http://www.loc.gov/MARC21/slim">
  <record type="Bibliographic">
    <leader>00000pam a2200000 c 4500</leader>
    <controlfield tag="001">1181888360</controlfield>
    <controlfield tag="003">DE-101</controlfield>
    <controlfield tag="005">20190815223316.0</controlfield>
    <controlfield tag="007">tu</controlfield>
    <controlfield tag="008">190327s2019    gw ||||| m||| 00||||ger  </controlfield>
    <datafield tag="015" ind1=" " ind2=" ">
      <subfield code="a">19,A34</subfield>
      <subfield code="a">19,H09</subfield>
      <subfield code="z">19,N14</subfield>
      <subfield code="2">dnb</subfield>
    </datafield>
     <datafield tag="100" ind1="1" ind2=" ">
      <subfield code="0">(DE-588)138356653</subfield>
      <subfield code="0">https://d-nb.info/gnd/138356653</subfield>
      <subfield code="0">(DE-101)138356653</subfield>
      <subfield code="a">Soukah, Zouheir</subfield>
      <subfield code="d">1980-</subfield>
      <subfield code="e">Verfasser</subfield>
      <subfield code="4">aut</subfield>
      <subfield code="2">gnd</subfield>
    </datafield>
    <datafield tag="245" ind1="1" ind2="0">
      <subfield code="a">&#152;Der&#156; Orient im kulturellen Gedächtnis der Deutschen</subfield>
      <subfield code="b">vergleichende Analyse ausgewählter Reiseberichte des 19. und beginnenden 21. Jahrhunderts</subfield>
      <subfield code="c">Zouheir Soukah</subfield>


Ich habe den Code jetzt noch mal bzgl. append und continue angepasst, aktuell sieht er so aus:

Code: Alles auswählen

    for child in record.findall('{http://www.loc.gov/MARC21/slim}datafield'):
    # Get title in either field 130$a or 245$a
        title1 = getSubfieldText(child, "245", "a")
        if title1:
            has_title = True
            content = str(title1)
            if keyword in content:
                coll.append(title1)
            else:
                break
    
            print(coll)

Als Ausgabe erhalte ich dann eine Liste mit Titeln à la:

[b'Fotoromane - Analyse eines Massenmediums']
[b'Analyse der Stilentwicklung in politischen Diskursen wa\xcc\x88hrend der Franzo\xcc\x88sischen Revolution (1789 - 1794)']
[b'Legitimation einer staatlichen Industriepolitik und Analyse der europa\xcc\x88ischen Industriepolitik in ausgewa\xcc\x88hlten Teilbereichen']


Außerhalb der Funktion selbst komme ich aber immer noch nicht an die Daten. Wenn ich print eins vorziehe gibt er mir sofort wieder tonnenweise Leerzeilen für alle Titel aus, die nicht das Wort "Analyse" enthalten (gemischt mit den wenigen Treffern), eins weiter gibt er nur noch leere Zeilen aus.

Vielen Dank noch/schon einmal für die Hilfe! :)
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Eine Funktion sollte nur einen Datentyp zurückgeben, eventuell noch None. Aber mal einen String und mal einen Wahrheitswert, das sollte nicht sein. Zudem gibt es einen Pfad, bei dem implizit None zurückgegeben wird, eine Funktion sollte aber entweder nichts oder immer explizit etwas zurückgeben.
Achso, String, intern arbeitet man mit Strings, nicht mit encodierten Bytestrings. Wer ist dieser Marc?
So ist das Umwandeln des Bytestrings in seine Repräsentation per str auch Unsinn. Die Repräsentation ist nur zu Debuggingzwecken da.
Warum hat title jetzt noch eine nichtssagende 1 bekommen? Variablennamen sollten aussagekärftig sein, Nummern sind das selten. Das `break` macht jetzt sematisch etwas anderes. Und sieht auch unsinnig aus, warum soll die Schleife beim ersten finden des Subelements nur abgebrochen werden, wenn das Keyword nicht passt?

Code: Alles auswählen

def get_subfield_text(element, tag, subfield):
    if element.attrib['tag'] == tag:
            for subelement in element:
                if subelement.attrib['code'] == subfield:
                    return subelement.text
    return None

coll = []
for child in record.findall('{http://www.loc.gov/MARC21/slim}datafield'):
    # Get title in either field 130$a or 245$a
    title = get_subfield_text(child, "245", "a")
    if title:
        if keyword in title:
            coll.append(title)
        print(coll)
Der Fehler im Programm liegt also darin, wie Du die Zeilen, die Du zeigst in Deinen restlichen Code einbaust, den Du nicht gezeigt hast.
Sarnelia
User
Beiträge: 7
Registriert: Freitag 8. Januar 2021, 17:55

Vielen Dank! Ja, ich habe da eindeutig hier und da noch etwas Aufholbedarf, daher vielen Dank für die Erklärungen, das hilft mir sehr weiter und ich werde mich noch mal intensiver damit auseinandersetzen.

Ich habe meinen Code jetzt entsprechend abgeändert und er läuft auch durch, allerdings erhalte ich nun als Ausgabe folgendes:

"...
[]
[]
[]
['Fotoromane - Analyse eines Massenmediums']
[]
[]
[]
[]
[]
[]
usw."

Wenn ich ihn wieder einrücke, also direkt unter "coll.append" schreibe, bekomme ich eine Ausgabe wie vorhin, nur dass er nun nach Titel 2 abbricht, weil er mit der Codierung ein Problem hat ("UnicodeEncodeError"). Deshalb hatte ich auch vorher das encoding angehängt, ist aber natürlich alles nur halbes Werk und war ein Workaround. Das XML-File selbst ist aber, soweit ich sehen kann, UTF8-codiert. Weiß also auch leider da nicht, warum er Probleme macht...

Title hatte übrigens die 1, weil es noch ein weiteres Feld gibt, in dem sich der Titel verstecken kann ("130"), das Feld ist aber nur in bestimmten Fällen vorhanden und ich wollte den Code erst mal überhaupt für den Hauptfall zum Laufen bringen ;). Theoretisch soll er dann aber irgendwann noch um den zweiten Fall erweitert werden.

MARC21 ist übrigens das Datenformat, steht für Machine-Readable-Cataloguing, aus den 60ern, immer noch internationaler Standard im Bibliothekswesen, großer Spaß ;).
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sarnelia: Wo lässt Du das denn laufen? Das ist anscheinend etwas das nicht mit dem kompletten Unicode-Umfang klar kommt. Das solltest Du ändern statt irgendwelche komischen Sachen im Code zu machen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Sarnelia
User
Beiträge: 7
Registriert: Freitag 8. Januar 2021, 17:55

Guten Abend und vielen Dank noch einmal für die Antworten!

@_blackjack_ : Ich nutze momentan Jupyter Notebooks und Komodo Edit - danke für den Hinweis, Komodo meckert hier, in Jupyter Notebooks gab es kein Problem wegen der Codierung!

Ich habe jetzt noch einmal ein wenig herumprobiert und denke, der Code selbst ist mittlerweile insgesamt schon deutlich "schicker" und weniger umständlich, mein ursprüngliches Problem in der Schleife ist leider allerdings nach wie vor vorhanden :| Da gefragt wurde (und ich den Code zwischenzeitlich noch mal etwas aufräumen konnte), poste ich hier jetzt mal den ganzen Salat (den Aufbau des zugehörigen XML-Files habe ich als Referenz ja weiter oben schon einmal gepostet):

Code: Alles auswählen

import xml.etree.ElementTree as ET
import pandas as pd


tree = ET.parse('downloads/data/bib_daten.xml')
root = tree.getroot()

records = list(root)
total_records = len(records)
print("Gesamtzahl an Einträgen:",total_records)

#record_elements = list(records) #-> bislang nutzlos

def get_subfield_text(element, tag, subfield):
    if (element.attrib['tag'] == tag):
            allsubs = list(element)
            for subelement in allsubs:
                if subelement.attrib['code'] == subfield:
                    return subelement.text
                
    return None
     

keyword = "Analyse"  # Setze Keyword, nach dem gesucht werden soll

for record in records:  
    coll = []
    
    for child in record.findall('{http://www.loc.gov/MARC21/slim}datafield'):             
        title = get_subfield_text(child, "245", "a")
        if title:
            if keyword in title:
                coll.append(title)
                print(coll)
Mit dieser Schreibweise erhalte ich die von mir gewünschte Liste mit Titeln, sobald ich das untere "print" aber auch nur eins nach vorne ziehe, habe ich wieder die ganzen Leerzeilen zwischen den Treffern in der Ausgabe. Irgendwie komme ich hier einfach nicht weiter.

Ich hatte mich auch gefragt, ob sich die Schleife nicht eleganter ohne das doppelte "for" lösen lässt, in dem ich ggf. direkt eins weiter im XML runtergehe, allerdings scheint das nicht zum Ziel zu führen. Ich freue mich über jeden weiterführenden Tipp, irgendwo habe ich da gerade gedanklich noch eine Blockade und komme einfach nicht weiter :oops:
Sarnelia
User
Beiträge: 7
Registriert: Freitag 8. Januar 2021, 17:55

Ok, nachdem mir gerade der Gedanke kam, einmal zu testen was eigentlich passiert, wenn ich jeden gefundenen Titel würde ausgeben lassen wollen sehe ich, dass das Problem wahrscheinlich doch irgendwo in der Funktion "get_subfield_text" liegt. Ich hatte zuvor die von @Sirius3 geänderte Methode ausprobiert, damit aber keinen Erfolg gehabt und daraus geschlossen, dass der Zwischenschritt des Herausziehen der Grandchildren ist (was ich versucht habe, über "allsubs = list(element)" zu lösen. Jetzt habe ich gerade noch einmal die Methode von Sirius3 eingefügt und ausprobiert und siehe da, jetzt erhalte ich ebenfalls das gesuchte Ergebnis. Aber wie zuvor erhalte ich eine Menge Leerzeilen zwischen den Ergebnissen, sobald print sich auf die gesamte Abfrage bezieht.

Ich vermute, der Fehler liegt irgendwo darin, dass statt nur der gewünschten Datafields mit dem Tag "245" aus irgendeinem Grund ALLE Datafields im XML ausgelesen werden. Ich hatte get_subfields_text ja so gedacht, dass folgender Eintrag

Code: Alles auswählen

def get_subfield_text(element, tag, subfield):
    if (element.attrib['tag'] == tag):
dazu führt, dass mit unterer For-Schleife als tag "245" übergeben wird und entsprechend die if-Funktion bei get_subfields_text dann nur für die datafields den text rausholt, die auch mit 245 getaggt sind, das scheint aber nicht zu funktionieren... Hat jemand eine Idee, was da falsch ist? :geek:
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sarnelia: Was ist denn das Problem? *Wo* willst Du denn *was* mit dem Inhalt von `coll` machen? Und was soll `coll` dann alles enthalten? Das `print()` steht da ja an einer nicht wirklich sinnvollen Stelle, ausser vielleicht zur Fehlersuche. Die Weiterverabeitung von `coll` müsste ja *nach* der ``for child …``-Schleife passieren, denn *da* ist die Liste vollständig. Sollte sie da nicht vollständig sein, und erst nach der äusseren ``for``-Schleife verarbeitet werden sollen, dann solltest Du dringen darüber nachdenken an welcher Stelle im Programmablauf Du `coll` eine leere Liste zuweist.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Sarnelia
User
Beiträge: 7
Registriert: Freitag 8. Januar 2021, 17:55

Ich möchte insgesamt die Fähigkeit haben, eine Datei mit bibliographischen Angaben (wie im Bibliotheksbereich üblich) nach verschiedenen Dingen zu durchsuchen, die Ergebnisse zwischenzuspeichern und später ggf. weiterverarbeiten zu können. Im konkreten Fall habe ich also eine Datei mit ca. 3500 verschiedenen bibliographischen Angaben, und ich möchte (erst einmal) alle Titel sehen können, die ein bestimmtes Suchwort enthalten (im konkreten Fall "Analyse"). "coll" soll also für mich erst einmal einfach alle Titel enthalten, die das gesuchte Wort enthalten. Idealerweise würde ich diese Titel dann auch gern in einer Datei und auch als weitere Variable nutzen können, an sich gern als Dataframe. Der soll dann auch noch erweitert werden um Autoren, ISBN etc., damit man hinterher was damit anfangen kann, aber momentan kriege ich ja nicht einmal die Titel passend in "coll" unter. Von daher ist das schon richtig, dass das ganze bis hierher da auch erst einmal hauptsächlich zum Testen steht.

Wenn ich print(coll) am Ende des ganzen übrigens einfach ganz nach links setze, gibt er mir lediglich [] aus.

Ich bin, wie man sicher merkt, bei weitem kein hauptberuflicher Programmierer, kenne Python aber ein wenig und versuche gerade, mich anhand dieser Aufgabe weiter damit vertraut zu machen und zu lernen. Das ist aber autodidaktisch leider nicht immer so ganz einfach, daher wende ich mich hierher ;).
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sarnelia: Das bedeutet das im letzten `record` keine Titel waren. Du fängst ja bei jedem `record` erneut mit einer leeren Liste an. Deswegen schrieb ich ja das Du die Zuweisung ``coll = []`` noch mal überdenken möchtest. Also wo die stehen soll. Wenn nämlich nicht jeder `record` mit einer einer neuen, leeren Liste für `coll` beginnen soll, dann steht das offensichtlich an der falschen Stelle.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Sarnelia
User
Beiträge: 7
Registriert: Freitag 8. Januar 2021, 17:55

Ganz ehrlich: Ich weiß es nicht. Ich hatte coll=[] zunächst noch vor der Schliefe definiert, weil ich die Variable ja auch außerhalb der Schleife später weiternutzen will, aber das scheint nicht notwendig zu sein. Ich habe es in die letzte if-Abfrage geschrieben (vor coll.append), da bekomme ich zwar die Titel ausgegeben, aber so wie zuvor auch (also wenn print auch direkt dort drin steht, passt es, wenn print eins vorgezogen ist, gibt er mir die Titel alle ca. 30-50mal am Stück aus. Dasselbe passiert, wenn ich coll definiere, nachdem er einen Titel gefunden hat und auch, wenn ich es in die beiden for-schleifen nehme. Ich habe einfach keine Idee mehr :(

Mein Eindruck ist eh, dass es mit print in der Schleife halt auch nur funktioniert, weil er dann jedesmal, wenn er einen passenden Titel gefunden hat, diesen auch schreibt. Dass er die Titel aber erst einmal in die Variable speichert und dann ausgibt, das scheint irgendwie nicht zu funktionieren - entweder es sind dann Leerzeilen drin, oder er schreibt dieselben Titel mehrfach rein. Ich hätte ja auch noch verstanden, wenn er den ersten dann erst einmal, dann den ersten und den zweiten, dann Titel 1, 2 und 3 usw ausgäbe, aber auch das tut er nicht, er gibt dann entweder erst ca. 30x Titel 1, dann 30x Titel 2 etc. oder aber alle Titel hintereinander, das aber 30 mal und dann noch mal dieselben Titel ungemischt direkt hintereinander. Ich werde nicht schlau draus. :cry:
Benutzeravatar
__blackjack__
User
Beiträge: 14053
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Sarnelia: Ich weiss jetzt nicht so ganz was man dazu sagen soll. Du hast offenbar Verständnisprobleme wie Code abläuft und versuchst programmieren durch raten in dem Du die Zuweisung der leeren Liste und das `print()` hoffnungsvoll herumschiebst, statt beides dort hin zu schreiben wo es logischerweise stehen müsste. Programmieren durch raten funktioniert nicht und der Rechner macht exakt was Du ihm sagst. Nicht was Du eigentlich wolltest oder meintest sondern nur das was Du sagst.

Wie es richtig aussehen muss kann ich auch nicht so genau sagen, weil ich auch nur raten kann was Du eigentlich machen möchtest. Ich vermute Du willst alle Titel in einer Liste sammeln und nachdem die alle gesammelt sind, etwas damit machen. Dagegen spricht dann allerdings dass das `print()` überhaupt irgendwo innerhalb der Schleifen steht, wenn die denn für die Verarbeitung stehen soll. Und natürlich das Du beim sammeln immer wieder mit einer leeren Liste alles bis dahin gesammelte verwirfst. Denn dann müsste ja eine Teilverarbeitung an einer Stelle stehen bevor das jeweilige Teilergebnis mit einer leeren Liste ersetzt wird.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten