แยกองค์ประกอบ XML ที่ซ้อนกันซึ่งมีขนาดต่างกันออกเป็น Pandas

สมมติว่าเรามีเอกสาร XML ตามอำเภอใจดังต่อไปนี้

<?xml version="1.0" encoding="UTF-8"?>
<programs xmlns="http://something.org/schema/s/program">
   <program xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xsi:schemaLocation="http://something.org/schema/s/program  http://something.org/schema/s/program.xsd">
      <orgUnitId>Organization 1</orgUnitId>
      <requiredLevel>academic bachelor</requiredLevel>
      <requiredLevel>academic master</requiredLevel>
      <programDescriptionText xml:lang="nl">Here is some text; blablabla</programDescriptionText>
      <searchword xml:lang="nl">Scrum master</searchword>
   </program>
   <program xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xsi:schemaLocation="http://something.org/schema/s/program  http://something.org/schema/s/program.xsd">
      <requiredLevel>bachelor</requiredLevel>
      <requiredLevel>academic master</requiredLevel>
      <requiredLevel>academic bachelor</requiredLevel>
      <orgUnitId>Organization 2</orgUnitId>
      <programDescriptionText xml:lang="nl">Text from another organization about some stuff.</programDescriptionText>
      <searchword xml:lang="nl">Excutives</searchword>
   </program>
   <program xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <orgUnitId>Organization 3</orgUnitId>
      <programDescriptionText xml:lang="nl">Also another huge text description from another organization.</programDescriptionText>
      <searchword xml:lang="nl">Negotiating</searchword>
      <searchword xml:lang="nl">Effective leadership</searchword>
      <searchword xml:lang="nl">negotiating techniques</searchword>
      <searchword xml:lang="nl">leadership</searchword>
      <searchword xml:lang="nl">strategic planning</searchword>
   </program>
</programs>

ขณะนี้ฉัน looping อยู่เหนือองค์ประกอบที่ฉันต้องการโดยใช้เส้นทางสัมบูรณ์ เนื่องจากฉันไม่สามารถใช้วิธีการ get หรือ find ใด ๆ ใน ElementTree ได้ ดังนั้นโค้ดของฉันจึงมีลักษณะดังนี้:

import pandas as pd
import xml.etree.ElementTree as ET   
import numpy as np
import itertools

tree = ET.parse('data.xml')
root = tree.getroot()
root.tag

dfcols=['organization','description','level','keyword']
organization=[]
description=[]
level=[]
keyword=[]

for node in root:
    for child in 
       node.findall('.//{http://something.org/schema/s/program}orgUnitId'):
        organization.append(child.text) 
    for child in node.findall('.//{http://something.org/schema/s/program}programDescriptionText'):
        description.append(child.text) 
    for child in node.findall('.//{http://something.org/schema/s/program}requiredLevel'):
        level.append(child.text)
    for child in node.findall('.//{http://something.org/schema/s/program}searchword'):
        keyword.append(child.text)

แน่นอนว่าเป้าหมายคือการสร้างดาต้าเฟรมเดียว อย่างไรก็ตาม เนื่องจากแต่ละโหนดในไฟล์ XML มีองค์ประกอบหนึ่งหรือหลายองค์ประกอบ เช่น requiredLevel หรือ searchword ขณะนี้ฉันกำลังสูญเสียข้อมูลเมื่อฉันส่งไปยัง dataframe โดยวิธีใดวิธีหนึ่งต่อไปนี้

df=pd.DataFrame(list(itertools.zip_longest(organization,
    description,level,searchword,
    fillvalue=np.nan)),columns=dfcols)

หรือใช้ pd.Series ตามที่กำหนด ที่นี่ หรือวิธีแก้ไขปัญหาอื่นที่ฉันไม่ทำ ดูเหมือนจะไม่พอดีจากที่นี่

ทางออกที่ดีที่สุดของฉันคือไม่ใช้ Lists เลย เนื่องจากดูเหมือนว่าจะจัดทำดัชนีข้อมูลไม่ถูกต้อง นั่นคือฉันสูญเสียข้อมูลจากโหนดย่อยที่ 2 ถึง X แต่ตอนนี้ฉันติดอยู่ และไม่เห็นตัวเลือกอื่นเลย

ผลลัพธ์สุดท้ายของฉันควรมีลักษณะดังนี้:

organization    description  level                keyword
Organization 1  ....         academic bachelor,   Scrum master
                             academic master 
Organization 2  ....         bachelor,            Executives
                             academic master, 
                             academic bachelor    
Organization 3  ....                              Negotiating,
                                                  Effective leadership,
                                                  negotiating techniques,
                                                  ....

person Wokkel    schedule 24.04.2019    source แหล่งที่มา


คำตอบ (2)


พิจารณาสร้างรายการพจนานุกรมที่มีค่าข้อความที่ยุบด้วยเครื่องหมายจุลภาค จากนั้นส่งรายการไปยังตัวสร้าง pandas.DataFrame:

dicts = []
for node in root:
    orgs = ", ".join([org.text for org in node.findall('.//{http://something.org/schema/s/program}orgUnitId')])
    desc = ", ".join([desc.text for desc in node.findall('.//{http://something.org/schema/s/program}programDescriptionText')])
    lvls = ", ".join([lvl.text for lvl in node.findall('.//{http://something.org/schema/s/program}requiredLevel')])
    wrds = ", ".join([wrd.text for wrd in node.findall('.//{http://something.org/schema/s/program}searchword')])

    dicts.append({'organization': orgs, 'description': desc, 'level': lvls, 'keyword': wrds})

final_df = pd.DataFrame(dicts, columns=['organization','description','level','keyword'])

เอาท์พุต

print(final_df)
#      organization                                        description                                         level                                            keyword
# 0  Organization 1                       Here is some text; blablabla            academic bachelor, academic master                                       Scrum master
# 1  Organization 2   Text from another organization about some stuff.  bachelor, academic master, academic bachelor                                          Excutives
# 2  Organization 3  Also another huge text description from anothe...                                                Negotiating, Effective leadership, negotiating...
person Parfait    schedule 24.04.2019
comment
แม้ว่าทั้งสองจะมาพร้อมกับวิธีแก้ปัญหาที่เป็นไปได้สำหรับคำตอบของฉัน แต่ฉันต้องยอมรับว่าวิธีสุดท้ายใช้ได้ผลในกรณีของฉัน ด้วยคำตอบแรก ฉันยังคงพบข้อผิดพลาดหลายประการภายในฟังก์ชันของตัวเอง คำตอบสุดท้ายที่ได้รับการยอมรับว่าเป็นวิธีแก้ปัญหานั้นทำงานได้อย่างเรียบร้อย อย่างไรก็ตาม มีจุดจับเล็กๆ อย่างหนึ่งที่แก้ไขได้ง่าย: หากข้อมูลมีแอตทริบิวต์ NoneType และแสดงข้อผิดพลาด บรรทัดก็สามารถเปลี่ยนแปลงได้ desc = ", ".join([str(desc.text) for desc in node.findall('.//{xml_path}Element')]) ขอบคุณสำหรับการสนับสนุนทั้งสองคน - person Wokkel; 25.04.2019

คุณสามารถดูตัวแปลง xml_to_dict แบบน้ำหนักเบาได้ที่นี่ สามารถปรับปรุงได้โดย สิ่งนี้ เพื่อจัดการเนมสเปซ

def xml_to_dict(xml='', remove_namespace=True):
    """Converts an XML string into a dict

    Args:
        xml: The XML as string
        remove_namespace: True (default) if namespaces are to be removed

    Returns:
        The XML string as dict

    Examples:
        >>> xml_to_dict('<text><para>hello world</para></text>')
        {'text': {'para': 'hello world'}}

    """
    def _xml_remove_namespace(buf):
        # Reference: https://stackoverflow.com/a/25920989/1498199
        it = ElementTree.iterparse(buf)
        for _, el in it:
            if '}' in el.tag:
                el.tag = el.tag.split('}', 1)[1]
        return it.root

    def _xml_to_dict(t):
        # Reference: https://stackoverflow.com/a/10077069/1498199
        from collections import defaultdict

        d = {t.tag: {} if t.attrib else None}
        children = list(t)
        if children:
            dd = defaultdict(list)
            for dc in map(_xml_to_dict, children):
                for k, v in dc.items():
                    dd[k].append(v)
            d = {t.tag: {k: v[0] if len(v) == 1 else v for k, v in dd.items()}}

        if t.attrib:
            d[t.tag].update(('@' + k, v) for k, v in t.attrib.items())

        if t.text:
            text = t.text.strip()
            if children or t.attrib:
                if text:
                    d[t.tag]['#text'] = text
            else:
                d[t.tag] = text

        return d

    buffer = io.StringIO(xml.strip())
    if remove_namespace:
        root = _xml_remove_namespace(buffer)
    else:
        root = ElementTree.parse(buffer).getroot()

    return _xml_to_dict(root)

ดังนั้นให้ s เป็นสตริงที่เก็บ xml ของคุณ เราสามารถแปลงมันเป็น dict ผ่านทาง

d = xml_to_dict(s, remove_namespace=True)

ตอนนี้วิธีแก้ปัญหาตรงไปตรงมา:

rows = []
for program in d['programs']['program']:
    cols = []
    cols.append(program['orgUnitId'])
    cols.append(program['programDescriptionText']['#text'])
    try:
        cols.append(','.join(program['requiredLevel']))
    except KeyError:
        cols.append('')

    try:
         searchwords = program['searchword']['#text']
    except TypeError:
         searchwords = []
         for searchword in program['searchword']:
            searchwords.append(searchword['#text'])
         searchwords = ','.join(searchwords)
    cols.append(searchwords)

    rows.append(cols)

df = pd.DataFrame(rows, columns=['organization', 'description', 'level', 'keyword'])
person JoergVanAken    schedule 24.04.2019
comment
หลังจากดูโค้ดมาระยะหนึ่งแล้วฉันก็ไม่สามารถเข้าใจมันได้ ดังนั้นฉันสงสัยว่า Python เอาต์พุตประเภทใดที่ส่งถึงคุณจาก df ในกรณีของฉัน ฉันติดอยู่กับข้อผิดพลาดชื่อและแอตทริบิวต์หลายรายการ นอกจากนี้ หลังจากลิงก์แรกที่คุณให้มา ฉันสามารถส่ง xml ลงในพจนานุกรมได้ อย่างไรก็ตาม การเรียกใช้ for loop ที่ให้มานั้นใช้งานไม่ได้ ขอบคุณสำหรับความคิดเห็น จะลองดูครับ ไม่ทราบว่ามีการนำเข้าโมดูลอื่น - person Wokkel; 24.04.2019
comment
คุณไม่จำเป็นต้องไปตามลิงก์ ฉันโพสต์การใช้งาน xml_to_dict แล้วเช่นกัน ฉันแค่อยากทำให้ชัดเจนว่ามันไม่ใช่รหัสของฉัน - person JoergVanAken; 24.04.2019