我在网上看到过相当多笨拙的XML->JSON代码,并与Stack的用户进行了一些互动,我相信这群人能比谷歌结果的前几页提供更多的帮助。
因此,我们正在解析一个天气提要,我们需要在许多网站上填充天气小部件。我们现在正在研究基于python的解决方案。
这个公共weather.com RSS提要是我们将要解析的内容的一个很好的例子(我们实际的weather.com提要包含额外的信息,因为与他们有合作关系)。
简而言之,如何使用Python将XML转换为JSON ?
我在网上看到过相当多笨拙的XML->JSON代码,并与Stack的用户进行了一些互动,我相信这群人能比谷歌结果的前几页提供更多的帮助。
因此,我们正在解析一个天气提要,我们需要在许多网站上填充天气小部件。我们现在正在研究基于python的解决方案。
这个公共weather.com RSS提要是我们将要解析的内容的一个很好的例子(我们实际的weather.com提要包含额外的信息,因为与他们有合作关系)。
简而言之,如何使用Python将XML转换为JSON ?
当前回答
我的答案针对的是特定的(有点常见的)情况,在这种情况下,您实际上不需要将整个xml转换为json,但您需要遍历/访问xml的特定部分,并且您需要它是快速和简单的(使用json/dict-like操作)。
方法
为此,需要注意的是,使用lxml将xml解析为树非常快。大多数其他答案中最慢的部分是第二步:遍历树结构(通常在python领域),将其转换为json。
这使我采用了我认为最适合这种情况的方法:使用lxml解析xml,然后(惰性地)包装树节点,为它们提供一个类似字典的接口。
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),
)
这个实现是不完整的,例如,它不清楚地支持元素同时具有文本和属性,或者同时具有文本和子元素的情况(只是因为我在编写它时不需要它……)不过,改进它应该很容易。
速度
在我的特定用例中,我只需要处理xml的特定元素,与使用@Martin Blech的xmltodict然后直接遍历字典相比,这种方法提供了惊人的70倍加速(!)。
奖金
作为奖励,由于我们的结构已经是dict-like的,我们可以免费获得xml2json的另一种替代实现。我们只需要将dict-like结构传递给json.dumps。喜欢的东西:
def xml_to_json(xmlstr, **kwargs):
x = xml_to_dictlike(xmlstr, **kwargs)
return json.dumps(x)
如果你的xml包含属性,你需要使用一些字母数字attr_prefix(例如。"ATTR_"),以确保这些键是有效的json键。
我还没有对这部分进行基准测试。
其他回答
有一种方法可以将基于xml的标记传输为JSON,允许它无损地转换回原始形式。见http://jsonml.org/。
它是一种JSON的XSLT。我希望这对你有帮助
Jsonpickle或者如果你使用feedparser,你可以尝试feed_parser_to_json.py
这是我为此编写的代码。没有对内容进行解析,只是简单的转换。
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片段,使用正则表达式会省事。例如:
# <user><name>Happy Man</name>...</user>
import re
names = re.findall(r'<name>(\w+)<\/name>', xml_string)
# do some thing to names
正如@Dan所说,要通过XML解析来做到这一点,并没有万能的解决方案,因为数据是不同的。我的建议是使用lxml。虽然没有完成json, lxml。物化的效果很好:
>>> 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'
可以使用declxml。它具有高级特性,如多属性和复杂的嵌套支持。您只需要为它编写一个简单的处理器。同样,使用相同的代码,您也可以转换回JSON。它相当简单,文档也很棒。
链接:https://declxml.readthedocs.io/en/latest/index.html