Нужно пройтись циклом по большому XML файлу (35GB) и забрать значения из определенного атрибута
Есть файл 35gb, в нём много разных значений, но мне нужны только те, что обозначены как "HOUSEGUID", в файле это выглядит так (
Нужно пройтись циклом по файлу и в json файл внести значения HOUSEGUID. Т.е должен быть файл с строками 8a325764-2d72-44ad-9c39-4012ee04ada1 <-- такого кода.
Проблема в том, что файл большой и я боюсь что если даже получится написать такой код, то после запуска будет долгий процесс, поэтому было бы неплохо как-то видеть процесс (индикатор)
Ответы (3 шт):
Ничего сложного:
- Открываете файл как обычный текстовый файл и читаете блоками какого-нибудь размера (я так понимаю переводов строки в вашем файле нет, поэтому так)
- В каждом блоке ищете регуляркой свой этот
HOUSEGUID - Сложность будет, когда искомый текст попадёт на стык двух блоков, поэтому, если не заморачиваться, проще всего будет хранить текущий буфер и предыдущий и искать шаблон в соединённом предыдущем и текущем буфере. Если вы знаете максимальный размер строки с вашим
GUID, то от прошлой копии буфера достаточно будет взять только этот максимальный размер минус 1 символ, а не весь буфер. - Чтобы не было повторений из-за двойного буфера нужно хранить множество с найденными в очередной порции GUID-ами и те, которые повторяются не писать в выходной файл, а остальные писать. Хотя если у вас есть точный размер искомой строки и вы обрезали буфер по методике предыдущего пункта, то это не нужно, повторов не будет.
- Прогресс можно выводить с помощью модуля
tqdmв цикле чтения файла, но вам нужно будет сначала получить размер файла и указать его, иначе прогресс не получится точно отобразить
Если что-то непонятно, могу набросать конкретный код. Но приведите хотя бы один этот GUID целиком в виде текста в вопросе.
Я бы попробовал считать 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 = ''