Mengekstraksi elemen XML bersarang dengan ukuran berbeda ke dalam Pandas

Mari kita asumsikan kita memiliki dokumen XML sembarang seperti di bawah ini

<?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>

Saat ini saya sedang looping atas elemen yang saya perlukan dengan menggunakan jalur absolutnya, karena saya tidak dapat menggunakan metode get atau find apa pun di ElementTree. Dengan demikian, kode saya terlihat seperti di bawah ini:

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)

Tujuannya tentu saja untuk membuat satu kerangka data. Namun, karena setiap node dalam file XML berisi satu atau beberapa elemen, seperti requiredLevel atau searchword Saat ini saya kehilangan data saat mentransmisikannya ke kerangka data dengan:

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

atau menggunakan pd.Series seperti yang diberikan di sini atau solusi lain yang saya tidak sepertinya tidak cocok dari di sini

Taruhan terbaik saya adalah tidak menggunakan Daftar sama sekali, karena tampaknya daftar tersebut tidak mengindeks data dengan benar. Artinya, saya kehilangan data dari node anak ke-2 hingga ke-X. Tapi saat ini saya mandek, dan tidak melihat pilihan lain.

Hasil akhir saya akan terlihat seperti ini:

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 sumber


Jawaban (2)


Pertimbangkan untuk membuat daftar kamus dengan nilai teks yang diciutkan koma. Kemudian masukkan daftar ke konstruktor 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'])

Keluaran

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
Meskipun keduanya hadir dengan solusi yang mungkin untuk jawaban saya, saya harus mengakui bahwa yang terakhir berhasil dalam kasus saya. Dengan jawaban pertama saya terus mengalami beberapa kesalahan dalam fungsi itu sendiri. Jawaban terakhir yang diterima sebagai solusi berfungsi dengan baik. Namun, ada satu kendala kecil yang dapat diperbaiki dengan mudah: jika data memiliki atribut NoneType dan menimbulkan kesalahan, sebuah baris dapat diubah; desc = ", ".join([str(desc.text) for desc in node.findall('.//{xml_path}Element')]) Terima kasih atas dukungan kalian berdua - person Wokkel; 25.04.2019

Konverter xml_to_dict yang ringan dapat ditemukan di sini. Ini dapat ditingkatkan dengan ini untuk menangani namespace.

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)

Jadi biarkan s menjadi string yang menampung xml Anda. Kita dapat mengubahnya menjadi dict melalui

d = xml_to_dict(s, remove_namespace=True)

Sekarang solusinya langsung:

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
Setelah melihat kodenya beberapa saat, sepertinya saya tidak dapat memahaminya. Jadi saya ingin tahu keluaran seperti apa yang diberikan Python kepada Anda dari df . Dalam kasus saya, saya terjebak pada beberapa kesalahan nama dan atribut. Selain itu, dengan mengikuti tautan pertama yang Anda berikan, saya dapat meneruskan xml ke kamus. Namun, menjalankan for loop yang disediakan tidak berhasil. Terima kasih atas komentar Anda, akan diperiksa. Tidak sadar mengimpor modul lain. - person Wokkel; 24.04.2019
comment
Anda tidak harus mengikuti tautannya, saya juga memposting implementasi xml_to_dict. Saya hanya ingin menjelaskan, bahwa itu bukan kode saya. - person JoergVanAken; 24.04.2019