繁体   English   中英

使用 Python 将 XML 转换为 JSON?

[英]Converting XML to JSON using Python?

我在网络上看到了相当多的难看的 XML->JSON 代码,并且与 Stack 的用户进行了一段时间的互动,我相信这群人可以提供比 Google 结果的前几页更多的帮助。

因此,我们正在解析天气提要,我们需要在众多网站上填充天气小部件。 我们现在正在研究基于 Python 的解决方案。

这个公共weather.com RSS 提要是我们将要解析的一个很好的例子(我们的实际weather.com 提要包含额外的信息,因为与他们合作)。

简而言之,我们应该如何使用 Python 将 XML 转换为 JSON?

xmltodict (完全披露:我写的)可以帮助您按照此“标准”将 XML 转换为 dict+list+string 结构。 它是基于Expat的,因此速度非常快,并且不需要将整个 XML 树加载到内存中。

拥有该数据结构后,您可以将其序列化为 JSON:

import xmltodict, json

o = xmltodict.parse('<e> <a>text</a> <a>text</a> </e>')
json.dumps(o) # '{"e": {"a": ["text", "text"]}}'

XML 和 JSON 之间没有“一对一”映射,因此将一个转换为另一个必须需要了解您想要对结果什么。

话虽如此,Python 的标准库有几个用于解析 XML (包括 DOM、SAX 和 ElementTree)的模块。 从 Python 2.6 开始, json模块中包含了对 Python 数据结构与 JSON 之间相互转换的支持。

所以基础设施就在那里。

您可以使用xmljson库使用不同的XML JSON 约定进行转换。

例如,这个 XML:

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

通过BadgerFish 约定转换为:

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

并通过GData 约定(不支持属性):

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

...并通过Parker 约定(不支持属性):

{
  'p': 'text'
}

可以使用相同的约定从 XML 转换为 JSON,也可以从 JSON 转换为 XML:

>>> 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>]

披露:我写了这个库。 希望它可以帮助未来的搜索者。

如果有一段时间你只得到响应代码而不是所有数据,那么像 json parse这样的错误就会出现,所以你需要将它转换为文本

import xmltodict

data = requests.get(url)
xpars = xmltodict.parse(data.text)
json = json.dumps(xpars)
print json 

对于可能仍然需要这个的任何人。 这是一个更新的、简单的代码来进行这种转换。

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

这是我为此构建的代码。 没有内容解析,只是简单的转换。

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

有一种方法可以将基于 XML 的标记传输为 JSON,从而可以无损地将其转换回其原始形式。 请参阅http://jsonml.org/

它是 JSON 的一种 XSLT。 我希望你觉得它有帮助

我建议不要直接转换。 将 XML 转换为对象,然后从对象转换为 JSON。

在我看来,这给出了 XML 和 JSON 如何对应的更清晰的定义。

正确使用需要时间,您甚至可以编写工具来帮助您生成其中的一些,但它大致如下所示:

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

您可能想看看http://designtheory.org/library/extrep/designdb-1.0.pdf 该项目从一个大型 XML 文件库的 XML 到 JSON 转换开始。 在转换中做了很多研究,产生了最简单直观的 XML -> JSON 映射(在文档的前面有描述)。 总之,将所有内容转换为 JSON 对象,并将重复块作为对象列表。

表示键/值对的对象(Python 中的字典,Java 中的 hashmap,JavaScript 中的对象)

没有映射回 XML 来获得相同的文档,原因是,键/值对是属性还是<key>value</key>是未知的,因此该信息丢失了。

如果你问我,属性是一个开始; 然后他们再次为 HTML 工作得很好。

好吧,可能最简单的方法是将 XML 解析为字典,然后使用 simplejson 将其序列化。

当我在 python 中使用 XML 做任何事情时,我几乎总是使用 lxml 包。 我怀疑大多数人都使用 lxml。 您可以使用 xmltodict 但您将不得不支付再次解析 XML 的代价。

要使用 lxml 将 XML 转换为 json,您:

  1. 使用 lxml 解析 XML 文档
  2. 将 lxml 转换为 dict
  3. 将列表转换为 json

我在我的项目中使用以下类。 使用 toJson 方法。

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)

内置main的输出是:

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

这是这个xml的转换:

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

我发现对于简单的 XML 片段,使用正则表达式可以省去麻烦。 例如:

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

要通过 XML 解析来做到这一点,正如@Dan 所说,由于数据不同,因此没有一劳永逸的解决方案。 我的建议是使用 lxml。 虽然没有完成 json, lxml.objectify给出了安静的好结果:

>>> 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'

虽然用于 XML 解析的内置库非常好,但我偏爱lxml

但是对于解析 RSS 提要,我推荐Universal Feed Parser ,它也可以解析 Atom。 它的主要优点是它甚至可以消化大多数畸形饲料。

Python 2.6 已经包含一个 JSON 解析器,但速度提高的新版本可以作为simplejson 使用

使用这些工具构建您的应用程序应该不会那么困难。

我的回答解决了特定(并且有些常见)的情况,您实际上并不需要将整个 xml 转换为 json,但是您需要的是遍历/访问 xml 的特定部分,并且您需要它是快速的,并且简单(使用类似 json/dict 的操作)。

方法

为此,请务必注意,使用lxml将 xml 解析为 etree 的速度非常快。 大多数其他答案中较慢的部分是第二遍:遍历 etree 结构(通常在 python-land 中),将其转换为 json。

这让我找到了最适合这种情况的方法:使用lxml解析 xml,然后(懒惰地)包装 etree 节点,为它们提供类似 dict 的界面。

代码

这是代码:

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

这个实现并不完整,例如,它没有完全支持元素同时具有文本和属性,或者同时具有文本和子元素的情况(只是因为我在编写它时不需要它......)它应该很容易改进它,虽然。

速度

在我的特定用例中,我只需要处理 xml 的特定元素,与使用 @Martin Blech 的xmltodict然后直接遍历 dict 相比,这种方法提供了惊人惊人的 70 (!) 倍的加速

奖金

作为奖励,由于我们的结构已经是类似 dict 的,我们可以免费获得xml2json另一种替代实现。 我们只需要将类似 dict 的结构传递给json.dumps 类似的东西:

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

如果您的 xml 包含属性,则需要使用一些字母数字attr_prefix (例如“ATTR_”),以确保键是有效的 json 键。

我还没有对这部分进行基准测试。

查看 lxml2json (披露:我写的)

https://github.com/rparelius/lxml2json

它非常快速、轻量级(只需要 lxml),一个优点是您可以控制某些元素是转换为列表还是字典

jsonpickle或者如果您使用的是 feedparser,您可以尝试feed_pa​​rser_to_json.py

您可以使用 declxml。 它具有多属性和复杂嵌套支持等高级功能。 你只需要为它编写一个简单的处理器。 同样使用相同的代码,您也可以转换回 JSON。 它相当简单,文档也很棒。

链接: https : //declxml.readthedocs.io/en/latest/index.html

不久前我在 github 上发布了一个..

https://github.com/davlee1972/xml_to_json

这个转换器是用 Python 编写的,它将一个或多个 XML 文件转换成 JSON / JSONL 文件

它需要一个 XSD 模式文件来找出嵌套的 json 结构(字典与列表)和 json 等效数据类型。

python xml_to_json.py -x PurchaseOrder.xsd PurchaseOrder.xml

INFO - 2018-03-20 11:10:24 - Parsing XML Files..
INFO - 2018-03-20 11:10:24 - Processing 1 files
INFO - 2018-03-20 11:10:24 - Parsing files in the following order:
INFO - 2018-03-20 11:10:24 - ['PurchaseOrder.xml']
DEBUG - 2018-03-20 11:10:24 - Generating schema from PurchaseOrder.xsd
DEBUG - 2018-03-20 11:10:24 - Parsing PurchaseOrder.xml
DEBUG - 2018-03-20 11:10:24 - Writing to file PurchaseOrder.json
DEBUG - 2018-03-20 11:10:24 - Completed PurchaseOrder.xml

我还有一个后续的 xml to parquet 转换器,它以类似的方式工作

https://github.com/blackrock/xml_to_parquet

这里的东西是积极维护的,到目前为止是我最喜欢的: python 中的 xml2json

如果您不想使用任何外部库和 3rd 方工具,请尝试以下代码。

代码

import re
import json

def getdict(content):
    res=re.findall("<(?P<var>\S*)(?P<attr>[^/>]*)(?:(?:>(?P<val>.*?)</(?P=var)>)|(?:/>))",content)
    if len(res)>=1:
        attreg="(?P<avr>\S+?)(?:(?:=(?P<quote>['\"])(?P<avl>.*?)(?P=quote))|(?:=(?P<avl1>.*?)(?:\s|$))|(?P<avl2>[\s]+)|$)"
        if len(res)>1:
            return [{i[0]:[{"@attributes":[{j[0]:(j[2] or j[3] or j[4])} for j in re.findall(attreg,i[1].strip())]},{"$values":getdict(i[2])}]} for i in res]
        else:
            return {res[0]:[{"@attributes":[{j[0]:(j[2] or j[3] or j[4])} for j in re.findall(attreg,res[1].strip())]},{"$values":getdict(res[2])}]}
    else:
        return content

with open("test.xml","r") as f:
    print(json.dumps(getdict(f.read().replace('\n',''))))

样本输入

<details class="4b" count=1 boy>
    <name type="firstname">John</name>
    <age>13</age>
    <hobby>Coin collection</hobby>
    <hobby>Stamp collection</hobby>
    <address>
        <country>USA</country>
        <state>CA</state>
    </address>
</details>
<details empty="True"/>
<details/>
<details class="4a" count=2 girl>
    <name type="firstname">Samantha</name>
    <age>13</age>
    <hobby>Fishing</hobby>
    <hobby>Chess</hobby>
    <address current="no">
        <country>Australia</country>
        <state>NSW</state>
    </address>
</details>

输出

[
  {
    "details": [
      {
        "@attributes": [
          {
            "class": "4b"
          },
          {
            "count": "1"
          },
          {
            "boy": ""
          }
        ]
      },
      {
        "$values": [
          {
            "name": [
              {
                "@attributes": [
                  {
                    "type": "firstname"
                  }
                ]
              },
              {
                "$values": "John"
              }
            ]
          },
          {
            "age": [
              {
                "@attributes": []
              },
              {
                "$values": "13"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Coin collection"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Stamp collection"
              }
            ]
          },
          {
            "address": [
              {
                "@attributes": []
              },
              {
                "$values": [
                  {
                    "country": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "USA"
                      }
                    ]
                  },
                  {
                    "state": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "CA"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "details": [
      {
        "@attributes": [
          {
            "empty": "True"
          }
        ]
      },
      {
        "$values": ""
      }
    ]
  },
  {
    "details": [
      {
        "@attributes": []
      },
      {
        "$values": ""
      }
    ]
  },
  {
    "details": [
      {
        "@attributes": [
          {
            "class": "4a"
          },
          {
            "count": "2"
          },
          {
            "girl": ""
          }
        ]
      },
      {
        "$values": [
          {
            "name": [
              {
                "@attributes": [
                  {
                    "type": "firstname"
                  }
                ]
              },
              {
                "$values": "Samantha"
              }
            ]
          },
          {
            "age": [
              {
                "@attributes": []
              },
              {
                "$values": "13"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Fishing"
              }
            ]
          },
          {
            "hobby": [
              {
                "@attributes": []
              },
              {
                "$values": "Chess"
              }
            ]
          },
          {
            "address": [
              {
                "@attributes": [
                  {
                    "current": "no"
                  }
                ]
              },
              {
                "$values": [
                  {
                    "country": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "Australia"
                      }
                    ]
                  },
                  {
                    "state": [
                      {
                        "@attributes": []
                      },
                      {
                        "$values": "NSW"
                      }
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  }
]

JSON格式表示数据

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

json中,我们以密钥和值格式重复数据

 { "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"} ] } 

XML格式表示数据

 <!-- 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--> 

Python编写数据:首先要创建JSON,你需要在python中准备数据。 我们可以使用Python中的List和Dictionary来准备数据。

Python List <==> JSON数组

Python Dictionary <==> JSON对象(键值格式)选中此处以获取更多详细信息

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM