#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 является единственным для каждого Noderesult2. надеюсь, у вас есть идея, как добавлять новые элементы из
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.