Разбор XML для извлечения данных с группировкой - PullRequest
0 голосов
/ 15 апреля 2020

Не могли бы вы помочь мне извлечь данные из XML так, как мне нужно.

XML:

<DATA>

<HITS>ABC
<REC>123456789</REC>
<PRIORITY>0</PRIORITY>
</HITS>

<HITS>DEF
<REC>123456789</REC>
<PRIORITY>1</PRIORITY>
</HITS>

<HITS>GHI
<REC>55555555555</REC>
<PRIORITY>-1</PRIORITY>
</HITS>

<HITS>JKL
<REC>55555555555</REC>
<PRIORITY>-5</PRIORITY>
</HITS>

<HITS>PKR 
<REC>55555555555</REC> 
<PRIORITY>1</PRIORITY> 
</HITS>

<HITS>MNO
<REC>999999999999</REC>
<PRIORITY>1</PRIORITY>
</HITS>

<HITS>DXB
<REC>888888888888</REC>
<PRIORITY>-2</PRIORITY>
</HITS>

<HITS>JFK
<REC>888888888888</REC>
<PRIORITY>-1</PRIORITY>
</HITS>

</DATA>

I необходимо получить максимальный приоритет в XML, сгруппированном по RE C вместе со счетчиком HITS, возможно ли это сделать с помощью сценария awk, sed, grep, python или сценария оболочки, чтобы он работал на сервере AIX ?

Например, logi c моего вывода должно выглядеть примерно так:

HITS AB C, и DEF будет сгруппирован в один уникальный RE C (123456789), и максимальный приоритет равен 1. (1> 0)

Выходная плита для попаданий (AB C, DEF):

PRIORITY REC  HITS  
1        1    2

HITS GHI, JKL, PKR будут сгруппированы в один уникальный RE C (55555555555) с максимальным приоритетом 1. (1> -1> -5).

Выходная плита для попаданий (GHI, JKL и PKR):

PRIORITY REC  HITS  
1        1    3

HITS MNO является единственным членом уникальной группы RE C (999999999999) и имеет приоритет 1.

Выходная плита для удара (MNO):

PRIORITY REC  HITS  
1        1    1

ударов DXB и JF K сгруппирован в уникальный RE C (888888888888) и имеет приоритет -1. (-1> -2)

Выходная плита для попадания (DXB и JFK):

PRIORITY REC  HITS  
-1       1    2

FINAL OUTPUT (Количество уникальных записей (RE C) агрегируется на основе значения приоритета):

PRIORITY REC  HITS  
1        3    6
-1       1    2

Обратите внимание: типичный файл XML имеет большой размер, вероятно, более 600 МБ, поэтому сценарий оболочки, awk, sed, python сценарий, Сценарий grep или shell должен эффективно обрабатывать XML файлов большого размера.

Ответы [ 4 ]

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

Другое решение. Я не знаю, будет ли это решение идти в ногу с 600 МБ тоже.

from simplified_scrapy import SimplifiedDoc,req,utils

dic = {}
tmp = {}
def setData(html):
  doc = SimplifiedDoc(html)
  # First, get all data, group
  for hits in doc.HITSs:
    rec = hits.REC.text
    if not tmp.get(rec):
      tmp[rec] = []
    tmp.get(rec).append({'hits':hits.firstText(),'priority':int(hits.PRIORITY.text)})

with open('data.xml', 'r') as file: # data.xml is your xml file path
  lines = []
  flag = False
  for line in file:
    if flag or line.find('<HITS>')>=0:
      flag = True
      lines.append(line)
    if line.find('</HITS>')>=0:
      setData("".join(lines))
      flag = False
      lines = []
# Sort, get maximum
for rec in tmp:
  values = tmp.get(rec)
  values.sort(key=lambda hits: hits.get('priority'),reverse=True)
  priority = values[0].get('priority')
  # PRIORITY, HITS
  if dic.get(priority) is None:
    dic[priority]=[0,0]
  arr = dic.get(priority)
  arr[0] = arr[0]+1
  arr[1] = arr[1]+len(values)
# Result
for i in dic:
  arr = dic.get(i)
  print (i,arr[0],arr[1])

Результат:

1 3 6
-1 1 2
1 голос
/ 19 апреля 2020

Предполагая, что требуется правильный XML синтаксический анализ, предлагая разбить проблему на несколько шагов - python * Преобразование каждой записи "HITS" XML в (python) словарь * Группировка словарей по RE C , нахождение максимального приоритета, количество * Группировка RECS по приоритету, нахождение количества (попаданий), количества (записей)

Шаг 1 и 2 могут быть объединены вместе, шаг № 3 должен быть выполнен как отдельный проход (это не снимите вопрос OP, если гарантируется, что данные будут отсортированы по RE C).

Для шага # 1 лучше использовать SAX-анализатор (на основе событий), чтобы избежать загрузки всего набора данных в память.

Я считаю, что шаги 2 и 3 могут быть лучше реализованы. Я все еще в процессе перехода на Python. Рассмотрим реализацию ниже как эффективную отправную точку

#! /usr/bin/python

import xml.sax

debug = False

class recItem:
    def __init__ (self):
        self.count = 0
        self.maxPri = None


    # Dictionary by REC of recItem
recData = {}

class dataHandler (xml.sax.ContentHandler):

    def __init__ (self):
        self.lastTag = ""
        self.data = None

    def startElement(self, tag, attribute):
        if ( debug ):
            print "S", self, tag, attribute
        self.lastTag = tag
        if tag == "HITS":
            self.data = { "HITS": "", "REC": "", "PRIORITY": "" }

    def characters(self, content):
        if ( debug ):
            print "C", self, self.lastTag, content
        if ( self.data != None and self.lastTag in self.data ):
            self.data[self.lastTag] += content

   def endElement(self, tag):
        if ( debug ):
            print "E", self, tag
        self.lastTag = None
        if ( tag == "HITS" ) :

            rec = self.data["REC"]
            priority = int(self.data["PRIORITY"])
            hits = self.data["HITS"]

            # Find priority by rec
            if ( rec in recData ):
                # Find max priority
                if ( priority > recData[rec].maxPri ):
                    recData[rec].maxPri = priority
            else:
                # New REC
                item = recItem();
                item.maxPri = priority ;
                recData[rec] = item
            recData[rec].count += 1

            self.data = None

parser = xml.sax.make_parser()
parser.setFeature(xml.sax.handler.feature_namespaces, 0)
parser.setContentHandler(dataHandler())
parser.parse("data.xml")

if ( debug ):
    for k, v in recData.items():
        print k, v.maxPri, v.count


Шаг # 3

class priItem:
    def __init__ (self):
        self.hits = 0
        self.recs = 0

# Group REC by priority
priData = {}
for k, v in recData.items():
    priEntry = priData[v.maxPri] if v.maxPri in priData else None
    if ( priEntry == None ):
        priEntry = priData[v.maxPri] = priItem()
    priEntry.hits += v.count
    priEntry.recs += 1
# Print final output
print "PRIORITY", "REC", "HITS"
for k, v in priData.items():
    print k, v.recs, v.hits

Вывод (соответствует запросу OP), может быть отформатирован в CSV или аналогичен, если необходимо.

PRIORITY REC HITS
1 3 6
-1 1 2
1 голос
/ 19 апреля 2020

Поскольку ОП указал, что он предпочел бы получить ответ в Python, я использовал это здесь - l xml (с xpath) и pandas. Вопрос имеет довольно сложную логическую настройку, к тому же код несколько сложен.

Сейчас уже довольно поздно, поэтому я опубликую ответ и попытаюсь объяснить его позже:

from lxml import etree
import pandas as pd

hitlist = """[your xml above]
doc = etree.fromstring(hitlist)

uniq_recs = []   
recs = doc.xpath('//rec')
for rec in recs:
    if not rec.text in uniq_recs:
        uniq_recs.append(rec.text)

hit_counts = []
max_priority = []
uniq_priorities = []

for u in uniq_recs:
    priorities = []
    hit_counts.append(int(doc.xpath(f'count(//hits[./rec/text()={u}])')))
    for rec in recs:    
        if rec.text==u:
            priorities.append(int(rec.xpath('..//priority/text()')[0]))

    max_priority.append(max(priorities))
    if max(priorities) not in uniq_priorities:
        uniq_priorities.append(max(priorities))

rows = []
for up in uniq_priorities: 
    row = []
    hitts= 0
    for p, h in zip(max_priority, hit_counts):
        if p==up:
            hitts+=h
    row.extend((up,max_priority.count(up),hitts))
    rows.append(row)

columns = ['PRIORITY', 'REC',  'HITS']
pd.DataFrame(rows,columns=columns)

Вывод:

    PRIORITY    REC     HITS
0         1     3       6
1       -1      1       2
0 голосов
/ 19 апреля 2020
sed 's/<DATA>//g; s/<\/HITS>/#/g; s/<[/A-Z]*>/ /g' xml.xml |
        awk '!/#/{ s=s $0 }/#/{ print s; s="" }' |
        awk '{ p[$2]++; if(m[$2]<=$3) m[$2]=$3; else m[$2]=$3; }
            END{ print "PRIORITY REC HITS";
                 for(i in p) { print  m[i],1,p[i]; y[m[i]]=m[i]; x[m[i]]=x[m[i]]+p[i]; z[m[i]]++ };
                 print "FINAL";
                 for (i in y) { print i, z[y[i]], x[y[i]] } }'
  1. sed "сгладит" xml -файл (который я назвал: xml.xml)

  2. awk сделает последний кусок «выравнивания» .... ?

  3. awk подсчитает количество вхождений и определит максимальный

выход после шага 2, файл 'flattend':

 ABC 123456789  0
 DEF 123456789  1
 GHI 55555555555  -1
 JKL 55555555555  -5
 PKR  55555555555   1
 MNO 999999999999  1
 DXB 888888888888  -2
 JFK 888888888888  -1

вывод в конце:

PRIORITY REC HITS
1 1 1
-1 1 2
1 1 3
1 1 2
FINAL
-1 1 2
1 3 6

Я не знаю, будет ли это решение идти в ногу со входом 600Mb ....

...