Преобразование XML в csv с использованием библиотеки python xml

#python #xml #pandas

#python #xml #pandas

Вопрос:

Извините, если этот вопрос уже задан. Ниже приведен XML-файл, для которого я хочу преобразовать его в CSV или Excel. Здесь я хочу извлечь nodeName и его дочерний DestIPAddress в IpRoutelist. и значение в теге Custom / Name

 <?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <EnbConfigGetResponse xmlns="http://Airspan.Netspan.WebServices">
            <EnbConfigGetResult>
                <ErrorCode>OK</ErrorCode>
                <NodeResult>
                    <NodeResultCode>OK</NodeResultCode>
                    <NodeName>IMUMB0899</NodeName>
                    <NodeDetail>
                        <Custom>
                            <Name>Circle</Name>
                            <Value>MU</Value>
                        </Custom>
                        <Custom>
                            <Name>GW VLAN 601</Name>
                            <Value>2405:200:101::</Value>
                        </Custom>
                        <Custom>
                            <Name>GW VLAN 602</Name>
                            <Value>2405:200:104::</Value>
                        </Custom>
                    </NodeDetail>
                    <EnbConfig>
                        <Name>IMUMB0899</Name>
                        <Hardware>1000 SFP</Hardware>
                        <Description>TT</Description>
                        <Site>DND</Site>
                        <Region>DND</Region>
                        <Altitude>0</Altitude>
                        <NbifEventAlarmForwarding>Enabled</NbifEventAlarmForwarding>
                        <ENodeBType>Macro</ENodeBType>
                        <ENodeBID>397063</ENodeBID>
                        <M1SubnetMask>120</M1SubnetMask>
                        <IpRouteList>
                            <IpRoute>
                                <DestIpAddress>172.172.6.20</DestIpAddress>
                                <IpSubnetMask>255.255.255.255</IpSubnetMask>
                                <GatewayIpAddress>172.21.200.1</GatewayIpAddress>
                            </IpRoute>
                            <IpRoute>
                                <DestIpAddress>2405:20:1::</DestIpAddress>
                                <IpSubnetMask>40</IpSubnetMask>
                                <GatewayIpAddress>2405:20:101:4:7:2:61:1</GatewayIpAddress>
                            </IpRoute>
                        </IpRouteList>
                <NodeResult>
     </EnbConfigGetResult>
        </EnbConfigGetResponse>
    </soap:Body>
</soap:Envelope>

  

Я попробовал приведенный ниже код, который извлекает имя и IProute, но когда я пытаюсь объединить только один IP-маршрут, я получаю против nodeName, но доступны два.

 from bs4 import BeautifulSoup
import pandas as pd
import lxml
import xml.etree.cElementTree
import openpyxl
import inspect
import os

sites = "xml"

with open(sites, "r",encoding='unicode_escape') as f:
    xml_data = f.read()

soup = BeautifulSoup(xml_data, "xml")

tag1 = input("Enter tagname1:")
tag2 = input("Enter tagname2:")

data = []
dd = []

for td in soup.find_all(tag1):
    data.append({"NodeName": td.text})
    
for iproute in soup.find_all(tag2):
    dd.append({"IpRoute": iproute.text})

df1 = pd.DataFrame(data)
df2 = pd.DataFrame(dd)
    
df = pd.merge(df1,df2,left_index=True, right_index=True)
    
df.to_excel(sites   '.xlsx', sheet_name='Detail', index = False)
print("*************Done*************")

  

Ожидаемый результат:
введите описание изображения здесь

Комментарии:

1. Что вы вводите для tag1 и tag2 ? Кроме того, почему вы сохраняете свои результаты в двух отдельных структурах данных ( data и dd ), а затем объединяете их позже? Разве вы не хотите сохранить все вместе?

2. Ваш XML неверен; вы можете отредактировать свой вопрос и исправить его?

3. @MattDMo мне нужен вывод, как показано на снимке. И я ввожу tag1=nodeName и tag2 = DestIpAddress

4. @JackFleeting Это часть большого XML-файла, я отредактировал его.

Ответ №1:

Другой метод.

 from simplified_scrapy import SimplifiedDoc, utils, req
# xml = utils.getFileContent('file.xml')
xml = ''' Your xml string'''

doc = SimplifiedDoc(xml)
lstNodeResult = doc.selects('NodeResult')
data = [['NodeName','DestIpAddress','GatewayIpAddress','value1','value2','value3']]
for result in lstNodeResult:
    lstCustom = result.selects('NodeDetail>Custom')
    if lstCustom:
        lstCustom = lstCustom.Value.text
    NodeName = result.NodeName.text
    lstIpRoute = result.IpRoutes
    for IpRoute in lstIpRoute:
        row = [NodeName,IpRoute.DestIpAddress.text,IpRoute.GatewayIpAddress.text]
        if lstCustom: row.extend(lstCustom)
        data.append(row)
# print (data)
utils.save2csv('test.csv',data)

# Or
data = {
    'NodeName':lstNodeResult.NodeName.text,
    'DestIpAddress':lstNodeResult.select('IpRoute>DestIpAddress>text()'),
    'GatewayIpAddress':lstNodeResult.select('IpRoute>GatewayIpAddress>text()')
}
# print (data)
  

Результат:

 .......
I-MU-NVMB-OSC-0900-SMC004,2405:200:310:5a::,2405:200:101:500:7:2:602:3503
I-MU-NVMB-OSC-0900-SMC004,2405:200:310:1::,2405:200:101:500:7:2:602:3503
I-MU-NVMB-ISC-0181-SWC0002,2405:200:310:1::,2405:200:101:500:7:2:602:5d03,MU,2405:200:101:500:7:2:601:5d03,2405:200:101:500:7:2:602:5d03
I-MU-NVMB-ISC-0181-SWC0002,2405:200:311:2::,2405:200:101:500:7:2:602:5d03,MU,2405:200:101:500:7:2:601:5d03,2405:200:101:500:7:2:602:5d03
I-MU-NVMB-ISC-0181-SWC0002,2405:200:310:a152::,2405:200:101:500:7:2:602:5d03,MU,2405:200:101:500:7:2:601:5d03,2405:200:101:500:7:2:602:5d03
.......
  

Вот еще примеры: https://github.com/yiyedata/simplified-scrapy-demo/tree/master/doc_examples

Удалите пустые строки.

 def delEmptyRows(name, encoding="utf-8"):
    lines = utils.getFileLines(name, encoding=encoding)
    lines = [line for line in lines if line.strip()!='']
    utils.saveFile(name, "".join(lines), encoding=encoding)
# use
delEmptyRows('test.csv')
  

Способ получения данных IBridge2RfStatsRow.

 from simplified_scrapy import SimplifiedDoc, utils, req
xml = req.get('https://pastebin.com/raw/SWhDM1zq')

rows = []
doc = SimplifiedDoc(xml)
lstIBridge2RfStatsRow = doc.selects('IBridge2RfStatsRow').children
# Add header
header = []
for col in lstIBridge2RfStatsRow[0]:
    header.append(col.tag)
rows.append(header)

# Generate line
for row in lstIBridge2RfStatsRow:
    rows.append([col.text for col in row])

utils.save2csv('IBridge2RfStatsRow.csv',rows)
  

Комментарии:

1. Привет, @dabingsou Спасибо за ваш ответ, но для каждого имени узла есть несколько destIp., как заставить это работать?

2. @SunilSharma Количество этих destIp не является фиксированным. Что вы хотите с этим сделать?

3. @SunilSharma Эта проблема существует в Windows. Я не нашел хорошего способа. Временно добавлен метод удаления пустых строк.

4. @SunilSharma я обновил ответ, и вы можете увидеть, соответствует ли он вашим требованиям.

5. @SunilSharma name = doc.select(‘StatisticsResult>Name>text()’)

Ответ №2:

Есть определенные вещи, которые вы упустили из виду в своем коде — например, вы просто делаете soup.find_all('NodeName') — который дает all nodenames present в xml и soup.find_all('DestIPAddress') дает all destipaddresses present в xml — как вы их сопоставите, если сделаете это таким образом?

Попробуйте этот код, который я сделал outer loop , чтобы просмотреть каждый noderesult — и inner loop чтобы просмотреть каждый destipaddress , я предполагаю, что nodename он будет единственным для каждого noderesult , следовательно, используя то же имя для каждого destipaddress , найденного в конкретном noderesult :

 tag1 = "NodeName"
tag2 = "DestIpAddress"

tag1_list = []
tag2_list = []

for node_res in soup.find_all('NodeResult'):
    
  for tg2 in node_res.find_all(tag2):
      tag1_list.append(soup.find(tag1).text)
      tag2_list.append(tg2.text)

print(tag1_list)
print(tag2_list)

df = pd.DataFrame({
  'NodeNames': tag1_list,
  'IpRoute': tag2_list
})
    
print(df)
  

DF-вывод :

    NodeNames       IpRoute
0  IMUMB0899  172.172.6.20
1  IMUMB0899   2405:20:1::
  

Вы можете добавить write-excel строку в конце.

Комментарии:

1. Спасибо, это сработало хорошо, я пропустил одну вещь, которая мне также нужна GatewayIpAddress для каждого имени узла. И Nodename является единственным для каждого Noderesult

2. надеюсь, у вас есть идея, как добавлять новые элементы из xml в df

3. Спасибо, у меня появилась идея, что я работал над pandas, но xml для меня немного новый

4. Имя узла в выходных данных будет одинаковым для всех xml, оно принимает первое имя узла для полного xml.

5. в моем ответе есть ошибка — tag1_list.append(soup.find(tag1).text) должно быть изменено на tag1_list.append(node_res.find(tag1).text) — тогда вы получите правильное имя узла.

Ответ №3:

Поскольку вы имеете дело с xml и импортируете lxml, вы также можете использовать его с xpath.

Обратите внимание, что xml в вашем вопросе недействителен (даже после редактирования), и этот ответ предполагает, что пример был правильно отредактирован, чтобы быть действительным.

 from lxml import etree
import pandas as pd

soap = """[your xml above, fixed]"""
doc = etree.XML(soap.encode())
columns = ['NodeName','DesIPAdd','GIPAdd','Value1','Value2','Value3']
ns = {'xx' : "http://Airspan.Netspan.WebServices"}
nodes = doc.xpath('//xx:NodeResult', namespaces=ns)
rows = []
for node in nodes:        
    sub_nodes = node.xpath('//xx:EnbConfig/xx:IpRouteList/xx:IpRoute', namespaces=ns)    
    for sub_node in sub_nodes:
        row = []
        row.append(node.xpath('//xx:NodeName/text()', namespaces=ns)[0])        
        row.append(sub_node.xpath('.//xx:DestIpAddress/text()', namespaces=ns)[0])
        row.append(sub_node.xpath('.//xx:GatewayIpAddress/text()', namespaces=ns)[0])
        row.extend(node.xpath('//xx:NodeDetail/xx:Custom/xx:Value/text()', namespaces=ns))
        rows.append(row)
pd.DataFrame(rows,columns=columns)
  

Вывод:

       NodeName    DesIPAdd      GIPAdd              Value1  Value2      Value3
0   IMUMB0899   172.172.6.20    172.21.200.1           MU   2405:200:101::  2405:200:104::
1   IMUMB0899   2405:20:1::     2405:20:101:4:7:2:61:1  MU  2405:200:101::  2405:200:104::
  

Комментарии:

1. Я попробовал это с моим XML-файлом, вместо 997 строк я получаю 60894 строки, поэтому в цикле что-то происходит, и nodeName одинаково для всех строк. я загрузил часть xml ниже 0bin.net/paste/HOf-mVMg#bLRgpGfbezBNX8wrO-MW /…

2. @SunilSharma Я быстро взглянул на xml; на вашем месте я бы отказался от идеи преобразовать его в pandas (или что-то еще). Я бы придерживался собственной среды xml, изучил xpath и xquery, использовал такие инструменты, как BaseX, и пошел оттуда.

3. Да, но это требование, вот почему необходимо преобразовать его в csv.