【问题标题】:Using python's zip and list comprehensions to convert xml to csv使用 python 的 zip 和列表推导将 xml 转换为 csv
【发布时间】:2013-07-08 22:18:33
【问题描述】:

我一直在 SO 中查看有关 zip 和魔法 * 的其他问题,这对我理解它的工作原理有很大帮助。例如:

尽管我仍然需要考虑一下实际发生的事情,但我现在有了更好的理解。所以我想要实现的是将 xml 文档转换为 csv。上面的最后一个链接非常接近我想要做的,但是我的源 xml 没有最一致的结构,这就是我碰壁的地方。这是我的源 xml 的一个例子(为了这个例子而简化了):

<?xml version="1.0" encoding="utf-8"?>
<root>
    <child>
        <Name>John</Name>
        <Surname>Doe</Surname>
        <Phone>123456</Phone>
        <Phone>654321</Phone>
        <Fax>111111</Fax>
    </child>
    <child>
        <Name>Tom</Name>
        <Surname>Cat</Surname>
        <Phone>98765</Phone>
        <Phone>56789</Phone>
        <Phone>00000</Phone>
    </child>
</root>

如您所见,&lt;child&gt; 下可以有 2 个或更多相同的元素。此外,如果某个元素没有值,它甚至都不存在(就像第二个 &lt;child&gt; 没有 &lt;Fax&gt;)。

这是我目前拥有的代码:

data = etree.parse(open('test.xml')).findall(".//child")
tags = ('Name', 'Surname', 'Phone', 'Fax')

for child in data:
    for a in zip(*[child.findall(x) for x in tags]):
        print([x.text for x in a])

>> Result:

['John', 'Doe', '123456', '111111']

虽然这为我提供了一种可用于编写 csv 的格式,但它有两个问题:

  1. 它跳过第二个孩子,因为它没有&lt;Fax&gt;element(我想)。如果我只通过设置 tags = ('Name', 'Surname') 来搜索两个孩子中都存在的元素,那么我有 2 个列表返回(太棒了!)

  2. 第一个孩子实际上有 2 个电话号码,但只返回一个

从我可以测试的结果来看,当 zip* 开始发挥作用时,东西开始消失......我怎么可能设置一个默认值以便我可以保留空值?

更新:为了更清楚我打算做什么,这是预期的输出格式(带分号分隔符的 CSV,其中每个字段中的多个值用逗号分隔):

John;Joe;123456,654321;111111;
Tom;Cat;98765,56789;00000;;

谢谢!

【问题讨论】:

    标签: python xml csv zip list-comprehension


    【解决方案1】:

    我一起破解了这个。如果您需要更具体的格式,请阅读 csv 模块的文档并进行相应更改。

    from csv import DictWriter
    from StringIO import StringIO
    import xml.etree
    from xml.etree import ElementTree
    
    xml_str = \
    '''
    <?xml version="1.0" encoding="utf-8"?>
    <root>
        <child>
            <Name>John</Name>
            <Surname>Doe</Surname>
            <Phone>123456</Phone>
            <Phone>654321</Phone>
            <Fax>111111</Fax>
        </child>
        <child>
            <Name>Tom</Name>
            <Surname>Cat</Surname>
            <Phone>98765</Phone>
            <Phone>56789</Phone>
            <Phone>00000</Phone>
        </child>
    </root>
    '''
    
    root = ElementTree.parse(StringIO(xml_str.strip()))
    entry_list = []
    for child_tag in root.iterfind("child"):
        child_tags = child_tag.getchildren()
    
        tag_count = {}
        [tag_count.__setitem__(tag.tag, tag_count.get(tag.tag, 0) + 1) for tag in child_tags]
    
        m_count = dict([(key, 0) for (key, val) in filter(lambda (x, y): y > 1, tag_count.items())])
    
        enum = lambda x: ("%s%s" % (x.tag, (" %d" % m_count.setdefault(x.tag, m_count.pop(x.tag) + 1)) if(tag_count[x.tag] > 1) else ""), x.text)
        tmp_dict = dict([enum(tag) for tag in child_tags])
    
        entry_list.append(tmp_dict)
    
    field_order = ["Name", "Surname", "Phone 1", "Phone 2", "Phone 3", "Fax"]
    field_check = lambda q: field_order.index(q) if(field_order.count(q)) else sys.maxint
    
    all_fields = list(reduce(lambda x, y: x | set(y.keys()), entry_list, set([])))
    all_fields.sort(cmp=lambda x, y: field_check(x) - field_check(y))
    
    with open("test.csv", "w") as file_h:
        writer = DictWriter(file_h, all_fields, restval="", extrasaction="ignore", dialect="excel", lineterminator="\n")
        writer.writerow(dict(zip(all_fields, all_fields)))
        writer.writerows(entry_list)
    

    【讨论】:

    • 哇...效果很好,但我必须花很多时间来理解你刚刚在那里所做的一切!谢谢!
    【解决方案2】:

    你说,关于你的第一个问题,“[i]如果我只搜索两个孩子中都存在的元素......我有 2 个列表”,这意味着第二个孩子缺乏输出与两个child 节点之间的交互有关。事实并非如此。您似乎忽略了zip 的行为方面,zip 在用完最短的参数后停止处理其参数。

    考虑以下代码简化的输出:

    for child in data:
        print [child.findall(x) for x in tags]
    

    输出将是(省略内存地址):

    [[<Element 'Name'>], [<Element 'Surname'>], [<Element 'Phone'>, <Element 'Phone'>], [<Element 'Fax'>]]
    [[<Element 'Name'>], [<Element 'Surname'>], [<Element 'Phone'>, <Element 'Phone'>, <Element 'Phone'>], []]
    

    注意第二个列表有一个 empty 子列表(因为第二个孩子没有Fax 节点)。这意味着当您将这些子列表压缩在一起时,该过程会立即停止并返回一个空列表;在第一次通过时,它已经用尽了其中一个子列表。 这就是为什么你的第二个孩子在输出中被省略了;它与子元素之间共享的元素无关。

    zip 行为的相同原理解释了您的第二个问题。请注意,上面的第一个输出列表包含四个元素:三个标签的长度为 1 的列表和长度为 2 的两个电话元素的列表。当您将它们压缩在一起时,该过程会在用尽任何子列表后再次停止。在这种情况下,最短的子列表长度为 1,因此结果仅从电话子列表中提取一个元素。

    我不确定你希望你的输出看起来像什么,但如果你只是想为每个子节点构建一个包含该节点中每个元素的文本的列表,你可以执行类似的操作:

    for child in data:
        print [x.text for x in child]
    

    这将产生:

    ['John', 'Doe', '123456', '654321', '111111']
    ['Tom', 'Cat', '98765', '56789', '00000']
    

    【讨论】:

    • 您好,Alp,感谢您的回复。但是,它缺少一些要点。我将编辑我的回复以添加预期的输出以使其更加清晰。我的目的是将结果转换为 csv 格式,这样你的输出在这种情况下就不会好了。这就是我最终想要的:John;Joe;123456,654321;111111;Tom;Cat;98765,56789;00000;; 所以如果我有 2 个&lt;phone&gt; 元素,它们最终需要一起出现在一个 csv“字段”中。此外,顺序很重要,作为 csv,每个字段都必须明显匹配相应的标题。
    猜你喜欢
    • 1970-01-01
    • 2019-07-13
    • 2017-09-16
    • 2016-12-21
    • 1970-01-01
    • 1970-01-01
    • 2019-07-27
    • 2013-03-29
    相关资源
    最近更新 更多