Нужно пройтись циклом по большому XML файлу (35GB) и забрать значения из определенного атрибута

Есть файл 35gb, в нём много разных значений, но мне нужны только те, что обозначены как "HOUSEGUID", в файле это выглядит так (введите сюда описание изображения

Нужно пройтись циклом по файлу и в json файл внести значения HOUSEGUID. Т.е должен быть файл с строками 8a325764-2d72-44ad-9c39-4012ee04ada1 <-- такого кода.

Проблема в том, что файл большой и я боюсь что если даже получится написать такой код, то после запуска будет долгий процесс, поэтому было бы неплохо как-то видеть процесс (индикатор)


Ответы (3 шт):

Автор решения: CrazyElf

Ничего сложного:

  • Открываете файл как обычный текстовый файл и читаете блоками какого-нибудь размера (я так понимаю переводов строки в вашем файле нет, поэтому так)
  • В каждом блоке ищете регуляркой свой этот HOUSEGUID
  • Сложность будет, когда искомый текст попадёт на стык двух блоков, поэтому, если не заморачиваться, проще всего будет хранить текущий буфер и предыдущий и искать шаблон в соединённом предыдущем и текущем буфере. Если вы знаете максимальный размер строки с вашим GUID, то от прошлой копии буфера достаточно будет взять только этот максимальный размер минус 1 символ, а не весь буфер.
  • Чтобы не было повторений из-за двойного буфера нужно хранить множество с найденными в очередной порции GUID-ами и те, которые повторяются не писать в выходной файл, а остальные писать. Хотя если у вас есть точный размер искомой строки и вы обрезали буфер по методике предыдущего пункта, то это не нужно, повторов не будет.
  • Прогресс можно выводить с помощью модуля tqdm в цикле чтения файла, но вам нужно будет сначала получить размер файла и указать его, иначе прогресс не получится точно отобразить

Если что-то непонятно, могу набросать конкретный код. Но приведите хотя бы один этот GUID целиком в виде текста в вопросе.

→ Ссылка
Автор решения: gil9red

Я бы попробовал считать XML через xml.sax. А прогресс можно показывать через tqdm.

  • Для отображения прогресс бара, пока идет сбор HOUSEGUID, можно сделать через бесконечный итератор (self.it), тогда на каждый найденных элемент в XML будет обновлен прогресс бар
  • Для скорости и возможности отслеживать прогресс сделал вручную сохранение в JSON
  • Файл взял с сайта ФИАС, там только обновление (~172MB), т.к. не охото качать 10ГБ, а sax должен хорошо работать и с большими файлами. Кст, если убирать дубликаты HOUSEGUID их будет 148671, а так их в файле было 379708
    • Есть вероятность, что при заполнении all_house_guid памяти может не хватить, тогда код с записью файла JSON нужно перенести в startElement, накидал пример-1 и пример-2 (в примерах уже нет множества для отсеивания дубликата, но вы можете сами это реализовать, заведя какой-нибудь кеш уникальных значений, например на 1000 элементов, новые значения будут вытеснять старые).

Попробуйте:

import xml.sax
from tqdm import tqdm


# В файле значения HOUSEGUID дублируются
all_house_guid = set()


class AttrHandler(xml.sax.handler.ContentHandler):
    def startDocument(self):
        self.it = iter(tqdm(iter(lambda: 0, 1)))

    def startElement(self, name, attrs):
        if 'HOUSEGUID' in attrs:
            all_house_guid.add(attrs['HOUSEGUID'])

        next(self.it)


print('Сбор HOUSEGUID')

parser = xml.sax.make_parser()
parser.setContentHandler(AttrHandler())
parser.parse('AS_HOUSE_20210318_88f2df80-430a-400f-9373-da5b2c80e051.XML')

print(f'Найдено {len(all_house_guid)}')

print('Сохранение в JSON...')

with open('all_house_guid.json', 'w') as f:
    f.write('[')

    for i, guid in tqdm(enumerate(all_house_guid), total=len(all_house_guid)):
        if i > 0:
            f.write(',')
        f.write(f'"{guid}"')

    f.write(']')
→ Ссылка
Автор решения: Максим

Как я понимаю, файл слишком велик, а может быть и больше, поэтому предлагаю использовать генератор для чтения и очередь символов для сравнения. Данный код протестирован мной на простом текстовом файле с кодировкой utf-8 но вы можете установить любую кодировку входного файла в коде. Используйте обозначения кодировок отсюда.

К сожалению отображение прогресса обработки тут не реализовано.

import codecs
# https://docs.python.org/3/library/codecs.html#standard-encodings

input_file = 'input_file.txt'
out_file = 'output.txt'


def get_line():
    with codecs.open(input_file, encoding='utf-8') as file:
        while True:
            yield file.read(1)


queue = ''
gen = get_line()
tag = 'HOUSEGUID="'
with open(out_file, 'w') as out_file:
    while True:
        char = next(gen)
        if not char:
            break
        queue += char
        if tag.find(queue) == 0:
            if queue == tag:
                # нашли то что надо
                queue = ''
                c = ''
                while True:
                    c += next(gen)
                    if c[-1] == '"':
                        c = c[0:-1]
                        out_file.write(c + "\r")
                        break
        else:
            queue = ''
→ Ссылка