разделить большие xml на маленькие кусочки - PullRequest
0 голосов
/ 20 января 2020

Мы хотим обработать большой xml файл размером около 6 ГБ.

Здесь мы читаем большой xml в dataframe, а затем экспортируем его в CSV-файл, который мы используем l xml, с iterparse для прочитайте xml построчно и загрузите его в dataframe.но этот процесс занимает почти 6 минут.

Поэтому мы решили разделить это xml на маленькие куски, например, по 1 ГБ каждый, а затем обработать их одновременно.

Можете ли вы предложить самый быстрый способ разбиения этих больших xml файлов?

Я уже попробовал ссылку https://gist.github.com/benallard/8042835, которая почти 8-10 только для разделения файл

Структура моего xml такая, как показано ниже. В реальном приложении у нас есть почти 200 тегов и около 100000 записей с тегом

<?xml version="1.0" encoding="UTF-8"?>
<ACADEMICS>  
  <STUDENTS ASOF_DATE="11/21/2019" CREATE_DATE="11/22/2019" RECORDS="108881">      
    <STUDENT>      
      <NAME>JOHN</NAME>      
      <REGNUM>1000</REGNUM>      
      <COUNTRY>USA</COUNTRY>      
      <ID>JH1</ID>
      <SHORT_STD_DESC>JOHN IS A GOOD STUDENT</SHORT_STD_DESC>
    </STUDENT>
   <STUDENT>      
    <NAME>ADAM</NAME>      
    <REGNUM>1001</REGNUM>      
    <COUNTRY>FRANCE</COUNTRY>      
    <ID>AD2</ID>
    <SHORT_STD_DESC>ADAM IS A GOOD STUDENT</SHORT_STD_DESC>
  </STUDENT>
  <STUDENT>      
    <NAME>PETER</NAME>      
    <REGNUM>1003</REGNUM>      
    <COUNTRY>BELGIUM</COUNTRY>      
    <ID>PE5</ID>
    <SHORT_STD_DESC>PETER IS A GOOD STUDENT</SHORT_STD_DESC>
</STUDENT>
<STUDENT>      
    <NAME>ERIC</NAME>      
    <REGNUM>1006</REGNUM>      
    <COUNTRY>AUSTRALIA</COUNTRY>      
    <ID>ER7</ID>
    <SHORT_STD_DESC>ERIC IS A GOOD STUDENT</SHORT_STD_DESC>
</STUDENT>
<STUDENT>      
    <NAME>NICHOLAS</NAME>      
    <REGNUM>1009</REGNUM>      
    <COUNTRY>GREECE</COUNTRY>      
    <ID>NI8</ID>
    <SHORT_STD_DESC>NICHOLAS IS A GOOD STUDENT</SHORT_STD_DESC>
</STUDENT>

1 Ответ

0 голосов
/ 27 января 2020

Когда мы представляем минимальный парсер , при чтении файла XML нужно реагировать только на пару событий:

  • Открытие <STUDENT> встречается: Подготовьте новый список, чтобы мы могли хранить различные значения, которые мы увидим.
  • Открытие <NAME>, <REGNUM> et c. встречается: Узнайте, в какой слот это значение свойства должно быть go («текущий» слот).
  • Обнаружен фрагмент текста: если этот текст принадлежит интересующему нас свойству, напишите это к текущему слоту.
  • Закрытие </NAME>, </REGNUM> et c. встречается: Обратите внимание, что любой следующий текст не интересен и должен игнорироваться.
  • Встречается закрывающий </STUDENT>: мы закончили с текущим студентом и можем вывести все данные, которые мы собрали.

Имея это в виду, мы можем создать обработчик событий SAX, который реализует эту идею:

from xml.sax.handler import ContentHandler

class StudentReader(ContentHandler):
    def __init__(self, callback=None):
        self.column_order = 'ID,NAME,REGNUM,COUNTRY,SHORT_STD_DESC'
        self.current_student = None
        self.current_idx = None
        self.mapping = {key: idx for idx, key in enumerate(self.column_order.split(','))}
        self.num_cols = len(self.mapping)
        self.callback = callback

    def startElement(self, tag, attrs):
        if tag == 'STUDENT':
            # new student with correct number of columns
            self.current_student = [''] * self.num_cols
        elif tag in self.mapping:
            # which column are we writing to?
            self.current_idx = self.mapping[tag]
        else:
            self.current_idx = None

    def endElement(self, tag):
        if tag == 'STUDENT':
            if self.callback is not None:
                # when we have a callback, call it
                self.callback(self.current_student)
            else:
                # without a callback, just print to console (for debugging)
                print(self.current_student)
        elif tag in self.mapping:
            self.current_idx = None

    def characters(self, data):
        if self.current_idx is not None:
            self.current_student[self.current_idx] += data

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

Теперь мы можем создать синтаксический анализатор SAX и дать ему StudentReader экземпляр:

import xml.sax

handler = StudentReader()
xml.sax.parse('data.xml', handler)

... который выводит списки на консоль:

['JH1', 'JOHN', '1000', 'USA', 'JOHN IS A GOOD STUDENT']
['AD2', 'ADAM', '1001', 'FRANCE', 'ADAM IS A GOOD STUDENT']
['PE5', 'PETER', '1003', 'BELGIUM', 'PETER IS A GOOD STUDENT']
['ER7', 'ERIC', '1006', 'AUSTRALIA', 'ERIC IS A GOOD STUDENT']
['NI8', 'NICHOLAS', '1009', 'GREECE', 'NICHOLAS IS A GOOD STUDENT']

Но, возможно, более интересно записать данные, например, в файл CSV.

Для этого и нужен аргумент callback в нашем StudentReader - мы могли бы передать эти списки непосредственно в модуль записи CSV, чтобы он мог записать выходной файл , в то время как входной файл в процессе обработки.

import csv
import xml.sax

with open('output.csv', 'w', encoding='utf8', newline='') as fp:
    writer = csv.writer(fp, delimiter=';')

    handler = StudentReader(writer.writerow)

    xml.sax.parse('data.xml', handler)

Конечно, вы также можете использовать команду базы данных в обратном вызове или что угодно.

...