提问人:Pete Karl II 提问时间:10/10/2008 最后编辑:dreftymacPete Karl II 更新时间:5/2/2023 访问量:380704
使用 Python 将 XML 转换为 JSON?
Converting XML to JSON using Python?
问:
我在网络上看到了相当多的笨拙的 XML->JSON 代码,并且与 Stack 的用户进行了一段时间的互动,我相信这群人可以比 Google 结果的前几页提供更多帮助。
因此,我们正在解析天气提要,我们需要在众多网站上填充天气小部件。我们现在正在研究基于 Python 的解决方案。
这个公共 weather.com RSS 提要是我们将要解析的内容的一个很好的例子(由于与他们建立了合作伙伴关系,我们的实际 weather.com 提要包含其他信息)。
简而言之,我们应该如何使用 Python 将 XML 转换为 JSON?
答:
好吧,可能最简单的方法就是将 XML 解析为字典,然后使用 simplejson 对其进行序列化。
XML 和 JSON 之间没有“一对一”映射,因此将一个映射转换为另一个映射必然需要了解要对结果执行的操作。
话虽如此,Python 的标准库有几个用于解析 XML 的模块(包括 DOM、SAX 和 ElementTree)。从 Python 2.6 开始,json
模块中包含对将 Python 数据结构与 JSON 相互转换的支持。
所以基础设施就在那里。
评论
虽然用于 XML 解析的内置库非常好,但我偏爱 lxml。
但是对于解析 RSS 提要,我推荐 Universal Feed Parser,它也可以解析 Atom。 它的主要优点是它甚至可以消化大多数畸形饲料。
Python 2.6 已经包含一个 JSON 解析器,但速度更快的更新版本以 simplejson 的形式提供。
有了这些工具,构建你的应用程序应该不会那么困难。
你可能想看看 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 的标记传输为 JSON,这允许将其无损地转换回其原始形式。请参见 http://jsonml.org/。
它是 JSON 的一种 XSLT。我希望它对你有所帮助
我发现对于简单的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.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'
评论
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"]}}'
评论
bs4
我建议不要进行直接转换。将 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 ):
...
评论
xml_node
.xpath
这是我为此构建的代码。没有对内容的解析,只是简单的转换。
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()
您可以使用 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>]
披露:我写了这个库。希望它对未来的搜索者有所帮助。
评论
我的答案解决了特定(并且有些常见)的情况,即您实际上不需要将整个 xml 转换为 json,但您需要的是遍历/访问 xml 的特定部分,并且您需要它快速且简单(使用类似 json/dict 的操作)。
方法
为此,需要注意的是,使用 xml 解析 etree 是超快的。在大多数其他答案中,缓慢的部分是第二遍:遍历 etree 结构(通常在 python-land 中),将其转换为 json。lxml
这让我想到了我找到最适合这种情况的方法:使用 解析 xml,然后(懒惰地)包装 etree 节点,为它们提供类似 dict 的接口。lxml
法典
代码如下:
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 倍 (!)。
奖金
作为奖励,由于我们的结构已经是类似字典的,因此我们免费获得了另一种替代实现。我们只需要将类似字典的结构传递给 。像这样:xml2json
json.dumps
def xml_to_json(xmlstr, **kwargs):
x = xml_to_dictlike(xmlstr, **kwargs)
return json.dumps(x)
如果您的 xml 包含属性,则需要使用一些字母数字(例如“ATTR_”),以确保键是有效的 json 键。attr_prefix
我还没有对这部分进行基准测试。
评论
json.dumps(tree)
这里的东西是积极维护的,到目前为止是我的最爱:python中的xml2json
评论
当我在 python 中使用 XML 做任何事情时,我几乎总是使用 lxml 包。我怀疑大多数人都使用 lxml。您可以使用 xmltodict,但您必须支付再次解析 XML 的代价。
要使用 lxml 将 XML 转换为 json,您需要:
- 使用 lxml 解析 XML 文档
- 将 lxml 转换为字典
- 转换list到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>
评论
<text>Some text</text>
'text: self.element.text
toDict
给任何可能仍然需要这个的人。下面是一个更新的简单代码来执行此转换。
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
评论
如果有时您只得到响应代码而不是所有数据,那么就会出现像 json parse 这样的错误,因此您需要将其转换为文本
import xmltodict
data = requests.get(url)
xpars = xmltodict.parse(data.text)
json = json.dumps(xpars)
print json
评论
xmltodict
json = json.dumps(xpars)
查看 lxml2json(披露:我写的)
https://github.com/rparelius/lxml2json
它非常快速、轻量级(只需要 LXML),一个优点是您可以控制某些元素是否转换为列表或字典
您可以使用 declxml。它具有多属性和复杂的嵌套支持等高级功能。你只需要为它编写一个简单的处理器。同样使用相同的代码,您也可以转换回 JSON。它相当简单,文档也很棒。
友情链接: https://declxml.readthedocs.io/en/latest/index.html
如果您不想使用任何外部库和第三方工具,请尝试以下代码。
法典
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"
}
]
}
]
}
]
}
]
}
]
}
]
不久前我在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 到 parquet 转换器,它以类似的方式工作
评论