Увеличьте скорость разбора XML с элементами и пространством имен до Pandas - PullRequest
1 голос
/ 21 апреля 2020

Итак, у меня есть файл 52M xml, который состоит из 115139 элементов.

from lxml import etree
tree  = etree.parse(file)
root  = tree.getroot()
In [76]: len(root)
Out[76]: 115139

У меня есть эта функция, которая перебирает элементы внутри root и вставляет каждый проанализированный элемент внутри Pandas DataFrame.

def fnc_parse_xml(file, columns):
    start = datetime.datetime.now()
    df    = pd.DataFrame(columns=columns)
    tree  = etree.parse(file)
    root  = tree.getroot()
    xmlns = './/{' + root.nsmap[None] + '}'

    for loc,e in enumerate(root):
        tot = []
        for column in columns:
            tot.append(e.find(xmlns + column).text)
        df.at[loc,columns] = tot

    end = datetime.datetime.now()
    diff = end-start
    return df,diff

Этот процесс работает, но занимает много времени. У меня i7 с 16 ГБ оперативной памяти.

In [75]: diff.total_seconds()/60                                                                                                      
Out[75]: 36.41769186666667
In [77]: len(df)                                                                                                                      
Out[77]: 115139

Я уверен, что есть лучший способ разбора файла 52M xml в Pandas DataFrame.

Это выдержка из файла xml ...

<findToFileResponse xmlns="xmlapi_1.0">
    <equipment.MediaIndependentStats>
        <rxOctets>0</rxOctets>
        <txOctets>0</txOctets>
        <inSpeed>10000000</inSpeed>
        <outSpeed>10000000</outSpeed>
        <time>1587080746395</time>
        <seconds>931265</seconds>
        <port>Port 3/1/6</port>
        <ip>192.168.157.204</ip>
        <name>RouterA</name>
    </equipment.MediaIndependentStats>
    <equipment.MediaIndependentStats>
        <rxOctets>0</rxOctets>
        <txOctets>0</txOctets>
        <inSpeed>100000</inSpeed>
        <outSpeed>100000</outSpeed>
        <time>1587080739924</time>
        <seconds>928831</seconds>
        <port>Port 1/1/1</port>
        <ip>192.168.154.63</ip>
        <name>RouterB</name>
    </equipment.MediaIndependentStats>
</findToFileResponse>

Есть идеи, как улучшить скорость?

Для извлечения вышеприведенного xml функция fnc_parse_xml(file, columns) возвращает этот DF ....

In [83]: df                                                                                                                           
Out[83]: 
  rxOctets txOctets   inSpeed  outSpeed           time seconds        port               ip     name
0        0        0  10000000  10000000  1587080746395  931265  Port 3/1/6  192.168.157.204  RouterA
1        0        0    100000    100000  1587080739924  928831  Port 1/1/1   192.168.154.63  RouterB

Ответы [ 3 ]

1 голос
/ 21 апреля 2020

Другой вариант вместо построения дерева путем синтаксического анализа всего файла XML - использовать iterparse ...

import datetime
import pandas as pd
from lxml import etree


def fnc_parse_xml(file, columns):
    start = datetime.datetime.now()
    # Capture all rows in array.
    rows = []
    # Process all "equipment.MediaIndependentStats" elements.
    for event, elem in etree.iterparse(file, tag="{xmlapi_1.0}equipment.MediaIndependentStats"):
        # Each row is a new dict.
        row = {}
        # Process all chidren of "equipment.MediaIndependentStats".
        for child in elem.iterchildren():
            # Create an entry in the row dict using the local name (without namespace) of the element for
            # the key and the text content as the value.
            row[etree.QName(child.tag).localname] = child.text
        # Append the row dict to the rows array.
        rows.append(row)
    # Create the DateFrame. This would probably be better in a try/catch to handle errors.
    df = pd.DataFrame(rows, columns=columns)
    # Calculate time difference.
    end = datetime.datetime.now()
    diff = end - start
    return df, diff


print(fnc_parse_xml("input.xml",
                    ["rxOctets", "txOctets", "inSpeed", "outSpeed", "time", "seconds", "port", "ip", "name"]))

На моей машине это обрабатывает файл размером 92,5 МБ в около 4 секунд.

1 голос
/ 21 апреля 2020

Вы объявляете пустой фрейм данных, поэтому вы можете получить ускорение, если вы укажете индекс заранее. В противном случае происходит постоянное расширение кадра данных.

df = pd.DataFrame(index=range(0, len(root)))

Вы также можете создать фрейм данных в конце l oop.

vals = [[e.find(xmlns + column).text for column in columns] for e in root]
df = pd.DataFrame(data=vals, columns=['rxOctets', ...])
0 голосов
/ 21 апреля 2020

мы будем использовать библиотеку xmltodict - позволяет обрабатывать xml документы как dict / json. данные, которые вас интересуют, встроены в оборудование. Ключ 'MediaIndependentStats':

import xmltodict
data = """<findToFileResponse xmlns="xmlapi_1.0">
    <equipment.MediaIndependentStats>
        <rxOctets>0</rxOctets>
        <txOctets>0</txOctets>
        <inSpeed>10000000</inSpeed>
        <outSpeed>10000000</outSpeed>
        <time>1587080746395</time>
        <seconds>931265</seconds>
        <port>Port 3/1/6</port>
        <ip>192.168.157.204</ip>
        <name>RouterA</name>
    </equipment.MediaIndependentStats>
    <equipment.MediaIndependentStats>
        <rxOctets>0</rxOctets>
        <txOctets>0</txOctets>
        <inSpeed>100000</inSpeed>
        <outSpeed>100000</outSpeed>
        <time>1587080739924</time>
        <seconds>928831</seconds>
        <port>Port 1/1/1</port>
        <ip>192.168.154.63</ip>
        <name>RouterB</name>
    </equipment.MediaIndependentStats>
</findToFileResponse>"""


pd.concat(pd.DataFrame.from_dict(ent,orient='index').T
          for ent in xmltodict.parse(data)['findToFileResponse']['equipment.MediaIndependentStats'])

rxOctets    txOctets    inSpeed outSpeed    time    seconds port    ip  name
0   0   0   10000000    10000000    1587080746395   931265  Port 3/1/6  192.168.157.204 RouterA
0   0   0   100000  100000  1587080739924   928831  Port 1/1/1  192.168.154.63  RouterB
...