【问题标题】:Convert dynamic XML file to CSV file - Python将动态 XML 文件转换为 CSV 文件 - Python
【发布时间】:2020-10-07 06:55:05
【问题描述】:

我想转换这个 XML 文件:

<record id="idOne">
    <ts date="2019-07-03" time="15:28:41.720440">5</ts>
    <ts date="2019-07-03" time="15:28:42.629959">10</ts>
    <ts date="2019-07-03" time="15:28:43.552677">15</ts>
    <ts date="2019-07-03" time="15:28:43.855345">20</ts>
</record>

<record id="idOne">
    <ts date="2019-07-03" time="15:28:45.072922">30</ts>
    <ts date="2019-07-03" time="15:28:45.377087">35</ts>
    <ts date="2019-07-03" time="15:28:46.316321">40</ts>
    <ts date="2019-07-03" time="15:28:47.527960">45</ts>
</record>

到这个 CSV 文件:

ID, date, time, value
idOne, 2019-07-03, 15:28:41.720440, 5
idOne, 2019-07-03, 15:28:42.629959, 10
idOne, 2019-07-03, 15:28:43.552677, 15
idOne, 2019-07-03, 15:28:43.855345, 20
idOne, 2019-07-03, 15:28:45.072922, 30
idOne, 2019-07-03, 15:28:45.377087, 35
idOne, 2019-07-03, 15:28:46.316321, 40
idOne, 2019-07-03, 15:28:47.527960, 45

我可以拥有多个 ID 结构体。

我使用 lxml 库。

我尝试使用 xpath 方法和 for 循环,但我只能获取 ID,而不能获取其余部分。问题是第二个for循环,但是我不知道如何处理“日期”和“时间”的值...

with open(args.input, "r") as f:
    # add root balises to parse the xml file
    records = itertools.chain('<root>', f, '</root>')
    root = etree.fromstringlist(records)

    #root = etree.fromstring(records)
    # count the number of records
    NumberRecords = int(root.xpath('count(//record)'))

    RecordsGrid = [[] for __ in range(NumberRecords)]
    tss = ["id","date", "time", "value"]
    paths = root.xpath('//record')
    #print(paths)
    Counter = 0
    for path in paths:

        for ts in tss[:1]:
            target = f'(./@{ts})'  # using f-strings to populate the full path
            if path.xpath(target):
                # we start populating our current sublist with the relevant info
                RecordsGrid[Counter].append(path.xpath(target)[0])
            else:
                RecordsGrid[Counter].append('NA')

        for ts in tss[1:]:  
            target = f'(./ts[@name="{ts}"]/text())'
            if path.xpath(target):
                RecordsGrid[Counter].append(path.xpath(target)[0])
            else:
                RecordsGrid[Counter].append('NA')
        Counter += 1

    # now that we have our lists, create a df
    df = pd.DataFrame(RecordsGrid, columns=tss)
    df.to_csv(args.output, sep=',', encoding='utf-8', index=False)

结果如下:

id,date,time,value
idOne,NA,NA,NA

感谢您的宝贵时间。

【问题讨论】:

  • 您忘记包含代码,将其包含在帖子中。
  • @Sushanth 谢谢,我更新了帖子

标签: python xml dataframe csv xml-parsing


【解决方案1】:

为了解决您的问题,我编写了以下脚本

from bs4 import BeautifulSoup as bs

data = list()

with open("data.xml") as xml:
    data_xml = bs(xml, "html.parser")
    for record in data_xml.find_all("record"):
        for ts in record.find_all("ts"):
            id_, date, time, value = record.get("id"), ts.get("date"), ts.get("time"), ts.text
            data.append(", ".join([id_, date, time, value]) + "\n")

with open("data.csv", "w") as csv:
    csv.write("ID, date, time, value\n")
    csv.writelines(data)

【讨论】:

    【解决方案2】:

    要使用 lxml,您可以简单地将字符串作为 html() 传递。通过使用 xpath //record/ts(以双反斜杠开头),您可以获取所有 ts 结果。可以通过调用 .getparent() 然后调用属性来访问主 id。

    要将 xml 转换为 csv,我建议使用 python 包 csv。您可以使用普通的文件编写器。但是 csv write 处理了很多问题,而且更干净。

    一般来说,您有一种方法可以处理所有事情。我建议将逻辑拆分为函数。想想Single Responsibility。下面的解决方案我已经将 xml 节点转换为 NamedTupple,然后将 namedTupple 写入 csv。维护/阅读要容易得多。 (即有一处设置标题文本,一处填充数据)。

    from lxml import etree
    import csv #py -m pip install python-csv
    import collections
    from collections import namedtuple
    
    Record = namedtuple('Record', ['id', 'date', 'time', 'value']) # Model to store records.
    
    def CreateCsvFile(results):
        with open('results.csv', 'w', newline='') as csvfile:
            writer = csv.DictWriter(csvfile, fieldnames=list(Record._fields)) # use the namedtuple fields for the headers 
            writer.writeheader()
            writer.writerows([x._asdict() for x in results]) # To use DictWriter, the namedtuple has to be converted to dictionary
    
    def FormatRecord(xmlNode):
        return Record(xmlNode.getparent().attrib['id'], xmlNode.attrib["date"], xmlNode.attrib["time"], xmlNode.text)
    
    def Main(html):
        xmlTree = etree.HTML(html)
        results = [FormatRecord(xmlNode) for xmlNode in xmlTree.xpath('//record/ts')] # the double backslash will retrieve all nodes for record.
        CreateCsvFile(results)
    
    if __name__ == '__main__':
        Main("""<record id="idOne">
                <ts date="2019-07-03" time="15:28:41.720440">5</ts>
                <ts date="2019-07-03" time="15:28:42.629959">10</ts>
                <ts date="2019-07-03" time="15:28:43.552677">15</ts>
                <ts date="2019-07-03" time="15:28:43.855345">20</ts>
            </record>
    
            <record id="idTwo">
                <ts date="2019-07-03" time="15:28:45.072922">30</ts>
                <ts date="2019-07-03" time="15:28:45.377087">35</ts>
                <ts date="2019-07-03" time="15:28:46.316321">40</ts>
                <ts date="2019-07-03" time="15:28:47.527960">45</ts>
            </record>""")
    

    【讨论】:

    • 我有一个问题,你能解释一下吗,for循环之前的函数是什么? : results = [FormatRecord(xmlNode) for xmlNode in xmlTree.xpath('//record/ts')]
    • 这是一个简短的方法或编写一个 for 循环。 xmlTree.xpath('//record/ts') 将返回一个包含 8 个 ts 项的列表。我已将该项目称为 xmlNode(因为它描述了它包含的内容。我可能应该选择 tsXmlNode)。然后我调用函数 FormatRecords(),它传入 xmlNode。 FormatRecords() 函数会将 xmlNode 转换为名为 Record 的 namedTuple。然后将名称元组分配给变量结果。代码在方括号中被扭曲,这迫使代码迭代。因此,变量结果包含一个命名元组数组(称为记录)
    • 您总是可以长时间编写 for 循环(它更容易调试,但这是一种不好的做法,因为它会创建不必要的代码)。 results = [] for xmlNode in xmlTree.xpath('//record/ts'): item = FormatRecord(xmlNode) results.append(item) CreateCsvFile(results)
    猜你喜欢
    • 1970-01-01
    • 2020-11-03
    • 2016-01-07
    • 1970-01-01
    • 2021-05-08
    • 2021-11-21
    • 2011-03-05
    • 2015-10-28
    • 2014-11-22
    相关资源
    最近更新 更多