XML mit Python in JSON konvertieren?

170

Ich habe eine ganze Menge ungünstigen XML-> JSON-Codes im Web gesehen, und nachdem ich ein wenig mit den Benutzern von Stack interagiert habe, bin ich überzeugt, dass diese Menge mehr helfen kann als die ersten Seiten der Google-Ergebnisse.

Wir analysieren also einen Wetter-Feed und müssen Wetter-Widgets auf einer Vielzahl von Websites füllen. Wir suchen jetzt nach Python-basierten Lösungen.

Dieser öffentliche RSS-Feed von weather.com ist ein gutes Beispiel für das, was wir analysieren würden ( unser aktueller Feed von weather.com enthält zusätzliche Informationen aufgrund einer Partnerschaft mit ihnen ).

Wie sollten wir XML mit Python in JSON konvertieren?

Pete Karl II
quelle

Antworten:

61

Es gibt keine Eins-zu-Eins-Zuordnung zwischen XML und JSON. Die Konvertierung in eine andere erfordert daher unbedingt ein gewisses Verständnis dafür, was Sie mit den Ergebnissen tun möchten .

Die Standardbibliothek von Python verfügt jedoch über mehrere Module zum Parsen von XML (einschließlich DOM, SAX und ElementTree). Ab Python 2.6 ist die Unterstützung für die Konvertierung von Python-Datenstrukturen in und aus JSON im jsonModul enthalten .

Die Infrastruktur ist also da.

Dan Lenski
quelle
2
xmljson IMHO ist am schnellsten zu verwenden, da verschiedene Konventionen sofort unterstützt werden. pypi.org/project/xmljson
nitinr708
Es wurde bereits in neueren Antworten erwähnt. Es deckt immer noch nur eine kleine Teilmenge gültiger XML-Konstrukte ab, aber wahrscheinlich die Mehrheit dessen, was Menschen in der Praxis verwenden.
Dan Lenski
281

xmltodict (vollständige Offenlegung: Ich habe es geschrieben) kann Ihnen helfen, Ihr XML nach diesem "Standard" in eine dikt + Liste + Zeichenfolgenstruktur zu konvertieren . Es basiert auf Expat , ist also sehr schnell und muss nicht den gesamten XML-Baum in den Speicher laden.

Sobald Sie diese Datenstruktur haben, können Sie sie in JSON serialisieren:

import xmltodict, json

o = xmltodict.parse('<e> <a>text</a> <a>text</a> </e>')
json.dumps(o) # '{"e": {"a": ["text", "text"]}}'
Martin Blech
quelle
@ Martin Blech Wenn ich eine JSON-Datei aus meiner Django-Modelldatei erstelle. Wie kann ich meine XML-Datei zuordnen, um die XML-Datei für die erforderlichen Felder in JSON zu konvertieren?
Sayth
1
@sayth Ich denke, Sie sollten dies als separate SO-Frage posten.
Martin Blech
@ Martin Blech. Ich habe eine Frage hinzugefügt, aber es ist ziemlich schwierig, in SO zu passen. Ich bin ein Anfänger und habe so viele Informationen wie möglich bereitgestellt. Ich gehe
sayth
Nach so langer Zeit bin ich ein bisschen überrascht, dass xmltodict in einigen Linux-Distributionen keine "Standard" -Bibliothek ist. Obwohl es so aussieht, als würde es den Job direkt nach dem, was wir lesen können, erledigen, werde ich leider eine andere Lösung wie xslt-Konvertierung verwenden
sancelot
Vielen Dank für das Schreiben dieser fantastischen Bibliothek. Obwohl bs4es die Aufgabe von XML erfüllen kann, zu diktieren, ist es extrem einfach, die Bibliothek zu verwenden
Tessaracter
24

Sie können die xmljson- Bibliothek verwenden, um mithilfe verschiedener XML-JSON-Konventionen zu konvertieren .

Zum Beispiel dieses XML:

<p id="1">text</p>

übersetzt über die BadgerFish-Konvention in folgendes:

{
  'p': {
    '@id': 1,
    '$': 'text'
  }
}

und über die GData-Konvention in diese (Attribute werden nicht unterstützt):

{
  'p': {
    '$t': 'text'
  }
}

... und über die Parker-Konvention in diese (Attribute werden nicht unterstützt):

{
  'p': 'text'
}

Es ist möglich, von XML nach JSON und von JSON nach XML mit denselben Konventionen zu konvertieren:

>>> import json, xmljson
>>> from lxml.etree import fromstring, tostring
>>> xml = fromstring('<p id="1">text</p>')
>>> json.dumps(xmljson.badgerfish.data(xml))
'{"p": {"@id": 1, "$": "text"}}'
>>> xmljson.parker.etree({'ul': {'li': [1, 2]}})
# Creates [<ul><li>1</li><li>2</li></ul>]

Offenlegung: Ich habe diese Bibliothek geschrieben. Hoffe, es hilft zukünftigen Suchenden.

S Anand
quelle
4
Das ist eine ziemlich coole Bibliothek, aber lesen Sie bitte Wie man persönliche Open-Source-Bibliotheken anbietet. bevor Sie weitere Antworten veröffentlichen, die dies zeigen.
Martijn Pieters
1
Danke @MartijnPieters - Ich habe das gerade durchgemacht und werde sicherstellen, dass ich mich daran halte.
S Anand
1
Vielen Dank an Anand für die Lösung - sie scheint gut zu funktionieren, hat keine externen Abhängigkeiten und bietet viel Flexibilität beim Umgang mit den Attributen unter Verwendung der verschiedenen Konventionen. Genau das, was ich brauchte und die flexibelste und einfachste Lösung war, die ich gefunden habe.
mbbeme
Danke Anand - leider kann ich XML nicht mit utf8-Codierung analysieren. Beim Durchlaufen von Quellen scheint der Codierungssatz von XMLParser (..) ignoriert zu werden
Patrik Beck
@PatrikBeck Könnten Sie bitte ein kleines Beispiel für XML mit einer fehlerhaften utf8-Codierung teilen?
S Anand
11

Wenn Sie irgendwann nur den Antwortcode anstelle aller Daten erhalten, tritt ein Fehler wie json parse auf, sodass Sie ihn als Text konvertieren müssen

import xmltodict

data = requests.get(url)
xpars = xmltodict.parse(data.text)
json = json.dumps(xpars)
print json 
Akshay Kumbhar
quelle
7

Hier ist der Code, den ich dafür erstellt habe. Es gibt keine Analyse des Inhalts, nur eine einfache Konvertierung.

from xml.dom import minidom
import simplejson as json
def parse_element(element):
    dict_data = dict()
    if element.nodeType == element.TEXT_NODE:
        dict_data['data'] = element.data
    if element.nodeType not in [element.TEXT_NODE, element.DOCUMENT_NODE, 
                                element.DOCUMENT_TYPE_NODE]:
        for item in element.attributes.items():
            dict_data[item[0]] = item[1]
    if element.nodeType not in [element.TEXT_NODE, element.DOCUMENT_TYPE_NODE]:
        for child in element.childNodes:
            child_name, child_dict = parse_element(child)
            if child_name in dict_data:
                try:
                    dict_data[child_name].append(child_dict)
                except AttributeError:
                    dict_data[child_name] = [dict_data[child_name], child_dict]
            else:
                dict_data[child_name] = child_dict 
    return element.nodeName, dict_data

if __name__ == '__main__':
    dom = minidom.parse('data.xml')
    f = open('data.json', 'w')
    f.write(json.dumps(parse_element(dom), sort_keys=True, indent=4))
    f.close()
Paulo Vj
quelle
7

Es gibt eine Methode zum Transportieren von XML-basiertem Markup als JSON, mit der es verlustfrei in seine ursprüngliche Form zurückkonvertiert werden kann. Siehe http://jsonml.org/ .

Es ist eine Art XSLT von JSON. Ich hoffe, Sie finden es hilfreich

themihai
quelle
7

An alle, die das noch brauchen. Hier ist ein neuerer, einfacher Code für diese Konvertierung.

from xml.etree import ElementTree as ET

xml    = ET.parse('FILE_NAME.xml')
parsed = parseXmlToJson(xml)


def parseXmlToJson(xml):
  response = {}

  for child in list(xml):
    if len(list(child)) > 0:
      response[child.tag] = parseXmlToJson(child)
    else:
      response[child.tag] = child.text or ''

    # one-liner equivalent
    # response[child.tag] = parseXmlToJson(child) if len(list(child)) > 0 else child.text or ''

  return response
jnhustin
quelle
1
Es funktioniert zumindest in Python 3.7, fügt jedoch leider einige unerwartete Daten zu den Schlüsselnamen hinzu, wenn bestimmte Werte in Ihrer XML enthalten sind. Beispielsweise wird in jedem Knoten wie folgt ein xmlns-Tag auf einem Knoten auf Stammebene angezeigt : {'{ maven .apache.org / POM / 4.0.0 } artefaktId ':' test-service ', der von xml wie folgt stammt: <project xmlns = " maven.apache.org/POM/4.0.0 " xsi: schemaLocation = " maven .apache.org / POM / 4.0.0 maven.apache.org/xsd/maven-4.0.0.xsd "xmlns: xsi =" w3.org/2001/XMLSchema-instance "> <modelVersion> 4.0.0 </ modelVersion>
hrbdg
5

Vielleicht möchten Sie einen Blick auf http://designtheory.org/library/extrep/designdb-1.0.pdf werfen . Dieses Projekt beginnt mit einer XML-zu-JSON-Konvertierung einer großen Bibliothek von XML-Dateien. Bei der Konvertierung wurde viel recherchiert, und es wurde das einfachste intuitive XML -> JSON-Mapping erstellt (es wird zu Beginn des Dokuments beschrieben). Zusammenfassend können Sie alles in ein JSON-Objekt konvertieren und sich wiederholende Blöcke als Liste von Objekten einfügen.

Objekte, die Schlüssel / Wert-Paare bedeuten (Wörterbuch in Python, Hashmap in Java, Objekt in JavaScript)

Es gibt keine Zuordnung zu XML, um ein identisches Dokument zu erhalten. Der Grund dafür ist, dass nicht bekannt ist, ob ein Schlüssel / Wert-Paar ein Attribut oder ein Attribut war <key>value</key>, weshalb diese Informationen verloren gehen.

Wenn Sie mich fragen, sind Attribute ein Hack, um zu beginnen; Andererseits funktionierten sie gut für HTML.

Pykler
quelle
4

Nun, der wahrscheinlich einfachste Weg ist, das XML in Wörterbücher zu analysieren und es dann mit simplejson zu serialisieren.

Dguaraglia
quelle
4

Ich würde vorschlagen, keine direkte Konvertierung anzustreben. Konvertieren Sie XML in ein Objekt und dann vom Objekt in JSON.

Meiner Meinung nach gibt dies eine klarere Definition der Übereinstimmung von XML und JSON.

Es braucht Zeit, um richtig zu werden, und Sie können sogar Tools schreiben, die Ihnen beim Generieren eines Teils davon helfen, aber es würde ungefähr so ​​aussehen:

class Channel:
  def __init__(self)
    self.items = []
    self.title = ""

  def from_xml( self, xml_node ):
    self.title = xml_node.xpath("title/text()")[0]
    for x in xml_node.xpath("item"):
      item = Item()
      item.from_xml( x )
      self.items.append( item )

  def to_json( self ):
    retval = {}
    retval['title'] = title
    retval['items'] = []
    for x in items:
      retval.append( x.to_json() )
    return retval

class Item:
  def __init__(self):
    ...

  def from_xml( self, xml_node ):
    ...

  def to_json( self ):
    ...
Michael Anderson
quelle
2

Ich fand für einfache XML-Schnipsel, reguläre Ausdrücke zu verwenden würde Probleme sparen. Beispielsweise:

# <user><name>Happy Man</name>...</user>
import re
names = re.findall(r'<name>(\w+)<\/name>', xml_string)
# do some thing to names

Um dies durch XML-Analyse zu tun, gibt es, wie @Dan sagte, keine Komplettlösung, da die Daten unterschiedlich sind. Mein Vorschlag ist, lxml zu verwenden. Obwohl lxml.objectify noch nicht fertig ist, liefert lxml.objectify recht gute Ergebnisse:

>>> from lxml import objectify
>>> root = objectify.fromstring("""
... <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...   <a attr1="foo" attr2="bar">1</a>
...   <a>1.2</a>
...   <b>1</b>
...   <b>true</b>
...   <c>what?</c>
...   <d xsi:nil="true"/>
... </root>
... """)

>>> print(str(root))
root = None [ObjectifiedElement]
    a = 1 [IntElement]
      * attr1 = 'foo'
      * attr2 = 'bar'
    a = 1.2 [FloatElement]
    b = 1 [IntElement]
    b = True [BoolElement]
    c = 'what?' [StringElement]
    d = None [NoneElement]
      * xsi:nil = 'true'
Andrew_1510
quelle
1
aber es entfernt doppelte Knoten
Pooya
2

Während die eingebauten Bibliotheken für das XML-Parsing ziemlich gut sind, bin ich ein Teil von lxml .

Aber zum Parsen von RSS-Feeds würde ich Universal Feed Parser empfehlen , der auch Atom analysieren kann. Sein Hauptvorteil ist, dass es selbst die meisten missgebildeten Futtermittel verdauen kann.

Python 2.6 enthält bereits einen JSON-Parser, eine neuere Version mit verbesserter Geschwindigkeit ist jedoch als simplejson verfügbar .

Mit diesen Tools sollte das Erstellen Ihrer App nicht so schwierig sein.

Luka Marinko
quelle
2

Meine Antwort befasst sich mit dem spezifischen (und etwas häufigen) Fall, in dem Sie nicht wirklich die gesamte XML in JSON konvertieren müssen , sondern nur bestimmte Teile der XML durchlaufen / darauf zugreifen müssen und schnell sein müssen einfach (mit json / dict-ähnlichen Operationen).

Ansatz

Aus diesem Grund ist es wichtig zu beachten, dass das Parsen einer XML-Datei mit etree verwendet wird lxml ist schnell ist. Der langsame Teil in den meisten anderen Antworten ist der zweite Durchgang: Durchqueren der Etree-Struktur (normalerweise im Python-Land) und Konvertieren in JSON.

Was mich zu dem Ansatz führt, den ich für diesen Fall am besten gefunden habe: Parsen der XML mit lxml und anschließendes Umschließen der etree-Knoten (träge), um ihnen eine diktartige Schnittstelle zu bieten.

Code

Hier ist der Code:

from collections import Mapping
import lxml.etree

class ETreeDictWrapper(Mapping):

    def __init__(self, elem, attr_prefix = '@', list_tags = ()):
        self.elem = elem
        self.attr_prefix = attr_prefix
        self.list_tags = list_tags

    def _wrap(self, e):
        if isinstance(e, basestring):
            return e
        if len(e) == 0 and len(e.attrib) == 0:
            return e.text
        return type(self)(
            e,
            attr_prefix = self.attr_prefix,
            list_tags = self.list_tags,
        )

    def __getitem__(self, key):
        if key.startswith(self.attr_prefix):
            return self.elem.attrib[key[len(self.attr_prefix):]]
        else:
            subelems = [ e for e in self.elem.iterchildren() if e.tag == key ]
            if len(subelems) > 1 or key in self.list_tags:
                return [ self._wrap(x) for x in subelems ]
            elif len(subelems) == 1:
                return self._wrap(subelems[0])
            else:
                raise KeyError(key)

    def __iter__(self):
        return iter(set( k.tag for k in self.elem) |
                    set( self.attr_prefix + k for k in self.elem.attrib ))

    def __len__(self):
        return len(self.elem) + len(self.elem.attrib)

    # defining __contains__ is not necessary, but improves speed
    def __contains__(self, key):
        if key.startswith(self.attr_prefix):
            return key[len(self.attr_prefix):] in self.elem.attrib
        else:
            return any( e.tag == key for e in self.elem.iterchildren() )


def xml_to_dictlike(xmlstr, attr_prefix = '@', list_tags = ()):
    t = lxml.etree.fromstring(xmlstr)
    return ETreeDictWrapper(
        t,
        attr_prefix = '@',
        list_tags = set(list_tags),
    )

Diese Implementierung ist nicht vollständig, z. B. werden Fälle, in denen ein Element sowohl Text als auch Attribute oder sowohl Text als auch untergeordnete Elemente enthält, nicht sauber unterstützt (nur weil ich es beim Schreiben nicht benötigt habe ...). Es sollte einfach sein um es jedoch zu verbessern.

Geschwindigkeit

In meinem speziellen Anwendungsfall, in dem ich nur bestimmte Elemente der XML verarbeiten musste, ergab dieser Ansatz eine überraschende und bemerkenswerte Beschleunigung um den Faktor 70 (!) Im Vergleich zur Verwendung von @Martin Blechs xmltodict und anschließendem direkten Durchlaufen des Diktats .

Bonus

Als Bonus erhalten wir, da unsere Struktur bereits diktiert ist, eine weitere alternative Implementierung xml2jsonkostenlos. Wir müssen nur unsere diktartige Struktur weitergeben json.dumps. Etwas wie:

def xml_to_json(xmlstr, **kwargs):
    x = xml_to_dictlike(xmlstr, **kwargs)
    return json.dumps(x)

Wenn Ihre XML-Datei Attribute enthält, müssen Sie alphanumerische Zeichen verwenden attr_prefix Datei (z. B. "ATTR_") verwenden, um sicherzustellen, dass die Schlüssel gültige JSON-Schlüssel sind.

Ich habe diesen Teil nicht bewertet.

shx2
quelle
Wenn ich es versuche, json.dumps(tree)heißt es, dass das Objekt vom Typ 'ETreeDictWrapper' nicht JSON-serialisierbar ist
Vlad T.
2

Wenn ich etwas mit XML in Python mache, verwende ich fast immer das lxml-Paket. Ich vermute, dass die meisten Leute lxml verwenden. Sie könnten xmltodict verwenden, müssen jedoch die Strafe für das erneute Parsen des XML zahlen.

So konvertieren Sie XML mit lxml in json:

  1. Analysieren Sie ein XML-Dokument mit lxml
  2. Konvertieren Sie lxml in ein Diktat
  3. Liste in json konvertieren

Ich benutze die folgende Klasse in meinen Projekten. Verwenden Sie die toJson-Methode.

from lxml import etree 
import json


class Element:
    '''
    Wrapper on the etree.Element class.  Extends functionality to output element
    as a dictionary.
    '''

    def __init__(self, element):
        '''
        :param: element a normal etree.Element instance
        '''
        self.element = element

    def toDict(self):
        '''
        Returns the element as a dictionary.  This includes all child elements.
        '''
        rval = {
            self.element.tag: {
                'attributes': dict(self.element.items()),
            },
        }
        for child in self.element:
            rval[self.element.tag].update(Element(child).toDict())
        return rval


class XmlDocument:
    '''
    Wraps lxml to provide:
        - cleaner access to some common lxml.etree functions
        - converter from XML to dict
        - converter from XML to json
    '''
    def __init__(self, xml = '<empty/>', filename=None):
        '''
        There are two ways to initialize the XmlDocument contents:
            - String
            - File

        You don't have to initialize the XmlDocument during instantiation
        though.  You can do it later with the 'set' method.  If you choose to
        initialize later XmlDocument will be initialized with "<empty/>".

        :param: xml Set this argument if you want to parse from a string.
        :param: filename Set this argument if you want to parse from a file.
        '''
        self.set(xml, filename) 

    def set(self, xml=None, filename=None):
        '''
        Use this to set or reset the contents of the XmlDocument.

        :param: xml Set this argument if you want to parse from a string.
        :param: filename Set this argument if you want to parse from a file.
        '''
        if filename is not None:
            self.tree = etree.parse(filename)
            self.root = self.tree.getroot()
        else:
            self.root = etree.fromstring(xml)
            self.tree = etree.ElementTree(self.root)


    def dump(self):
        etree.dump(self.root)

    def getXml(self):
        '''
        return document as a string
        '''
        return etree.tostring(self.root)

    def xpath(self, xpath):
        '''
        Return elements that match the given xpath.

        :param: xpath
        '''
        return self.tree.xpath(xpath);

    def nodes(self):
        '''
        Return all elements
        '''
        return self.root.iter('*')

    def toDict(self):
        '''
        Convert to a python dictionary
        '''
        return Element(self.root).toDict()

    def toJson(self, indent=None):
        '''
        Convert to JSON
        '''
        return json.dumps(self.toDict(), indent=indent)


if __name__ == "__main__":
    xml='''<system>
    <product>
        <demod>
            <frequency value='2.215' units='MHz'>
                <blah value='1'/>
            </frequency>
        </demod>
    </product>
</system>
'''
    doc = XmlDocument(xml)
    print doc.toJson(indent=4)

Die Ausgabe von der eingebauten Hauptleitung ist:

{
    "system": {
        "attributes": {}, 
        "product": {
            "attributes": {}, 
            "demod": {
                "attributes": {}, 
                "frequency": {
                    "attributes": {
                        "units": "MHz", 
                        "value": "2.215"
                    }, 
                    "blah": {
                        "attributes": {
                            "value": "1"
                        }
                    }
                }
            }
        }
    }
}

Welches ist eine Transformation dieser XML:

<system>
    <product>
        <demod>
            <frequency value='2.215' units='MHz'>
                <blah value='1'/>
            </frequency>
        </demod>
    </product>
</system>
Spitzmaus
quelle
1

Dieses Zeug hier wird aktiv gepflegt und ist bisher mein Favorit: xml2json in Python

trueadjustr
quelle
1

check out lxml2json (Offenlegung: Ich habe es geschrieben)

https://github.com/rparelius/lxml2json

Es ist sehr schnell, leicht (erfordert nur lxml) und ein Vorteil ist, dass Sie die Kontrolle darüber haben, ob bestimmte Elemente in Listen oder Diktate konvertiert werden

Robert Parelius
quelle
1

Sie können declxml verwenden. Es verfügt über erweiterte Funktionen wie mehrere Attribute und komplexe verschachtelte Unterstützung. Sie müssen nur einen einfachen Prozessor dafür schreiben. Mit demselben Code können Sie auch wieder in JSON konvertieren. Es ist ziemlich einfach und die Dokumentation ist fantastisch.

Link: https://declxml.readthedocs.io/en/latest/index.html

srth12
quelle
-1

Daten in Python vorbereiten : So erstellen Sie JSON vorbereiten , müssen Sie zuerst Daten in Python vorbereiten. Wir können List und Dictionary in Python verwenden, um die Daten vorzubereiten.

Python- Liste <==> JSON- Array

Python Dictionary <==> JSON- Objekt (Schlüsselwertformat) Überprüfen Sie dies für weitere Details

https://devstudioonline.com/article/create-json-and-xml-in-python

Anushree Anisha
quelle
Willkommen bei Stack Overflow! Während Links eine großartige Möglichkeit sind, Wissen zu teilen, werden sie die Frage nicht wirklich beantworten, wenn sie in Zukunft kaputt gehen. Fügen Sie Ihrer Antwort den wesentlichen Inhalt des Links hinzu, der die Frage beantwortet. Falls der Inhalt zu komplex oder zu groß ist, um hierher zu passen, beschreiben Sie die allgemeine Idee der vorgeschlagenen Lösung. Denken Sie daran, immer einen Linkverweis auf die Website der ursprünglichen Lösung zu führen. Siehe: Wie schreibe ich eine gute Antwort?
sɐunıɔ ןɐ qɐp
-4

Darstellung von Daten im JSON- Format

name=John
age=20
gender=male
address=Sector 12 Greater Kailash, New Delhi
Jobs=Noida,Developer | Gurugram,Tester |Faridabad,Designer

In json stellen wir Daten im Schlüssel- und Werteformat dar

{
    "name":"john",
    "age":20,
    "gender":"male",
    "address":["New kP college","Greater Kailash","New Delhi"],
    "jobs":[
               {"Place":"Noida","Title":"Developer "},
               {"Place":"Gurugram","Title":"Tester "},
               {"Place":"Faridabad","Title":"Designer"}
           ]
}

Darstellung von Daten im XML- Format

<!-- In xml we write a code under a key you can take any key -->
<info> <!-- key open -->

<name> john </name> 
<age> 20 </age>
<gender> male </gender>

<address> 
<item> New kP college </item>
<item> Greater Kailash </item>
<item> New Delhi </item>
</address>

<jobs>
 <item>
  <title>Developer </title>
  <place>Noida</place>
 </item>

 <item>
  <title>Designer</title>
  <place>Gurugram</place>
 </item>
 
 <item>
  <title>Developer </title>
  <place>Faridabad</place>
 </item>
</jobs>

</info> <!-- key close-->

Anushree Anisha
quelle