В предыдущей статье был рассмотрен подход к созданию csv из xml на базе данных, которые публикует ФИАС. В основу парсинга был положен DOM-парсер, загружающий в память весь файл целиком перед обработкой, что приводило к необходимости дробления файлов большого размера в виду ограниченного объема оперативной памяти. В этот раз предлагается посмотреть насколько хорош SAX-парсер и сравнить его скорость работы c DOM-парсером. В качестве подопытного будет использоваться наибольший из файлов базы ФИАС — houses, размером 27,5 ГБ.
Вынуждены сразу огорчить почтеннейшую публику — сходу скормить SAX-парсеру файл БД ФИАС houses не удастся. Парсер вылетает с ошибкой «not well-formed (invalid token)». И первоначально были подозрения, что файл БД битый. Однако после нарезки БД на несколько мелких частей было установлено, что вылеты вызваны измененной кодировкой для номеров домов и/или строений. То есть в тегах STRUCNUM либо HOUSENUM попадались дома с буквой, записанной в странной кодировке (не UTF-8 и не ANSI, в которой сформирован сам документ):

При этом, если эту кодировку выправить, прогнав файл через функцию remove_non_ascii, запись принимала вид:

Такой файл также не поглощался парсером, из-за лишних знаков.
Пришлось вспоминать регулярные выражения и чистить файл перед загрузкой в парсер.
Вопрос: почему нельзя создать нормальную БД, которая выкладывается для работы приобретает оттенок риторического.
Чтобы выровнять стартовые возможности парсеров, очистим тестовый фрагмент от вышеуказанных нестыковок.
Код для очистки файла БД перед загрузкой в парсер:
Код переводит в xml-файле non_ascii символы в нормальные и затем удаляет лишние "" в наименованиях строений и домов.
Для старта возьмем небольшой xml файл (58,8 Мб), на выходе планируем получить txt или csv, удобный для дальнейшей обработки в pandas или excel.
Выполнив программу получим значения словаря python:

Время выполнения: 5-6 сек.
Обработаем тот же файл, предварительно загрузив его целиком в память. Именно такой метод использует DOM-парсер.
Время выолнения 2-3 сек.
Победа DOM-парсера?
Файлы небольшого размера не отражают действительности в полной мере. Возьмем файл побольше 353 Мб (предварительно почистив, как было указано выше).
Результаты погона:
SAX-парсер: 0:00:32.090836 — 32 сек
DOM-парсер: 0:00:16.630951 — 16 сек
Разница в 2 раза по скорости. Однако это не умаляет главного достоинства SAX-парсера — возможность обрабатывать файлы большого размера без предварительной загрузки в память.
Остается сожалеть, что данное достоинство не применимо к БД ФИАС, так как требуется предварительная работа с кодировками.
Файл для предварительной очистки кодировок:
— 353 Мб в архиве.
Очищенный файл БД для тестов парсеров:
— 353 Мб в архиве.
Вступление
Вынуждены сразу огорчить почтеннейшую публику — сходу скормить SAX-парсеру файл БД ФИАС houses не удастся. Парсер вылетает с ошибкой «not well-formed (invalid token)». И первоначально были подозрения, что файл БД битый. Однако после нарезки БД на несколько мелких частей было установлено, что вылеты вызваны измененной кодировкой для номеров домов и/или строений. То есть в тегах STRUCNUM либо HOUSENUM попадались дома с буквой, записанной в странной кодировке (не UTF-8 и не ANSI, в которой сформирован сам документ):

При этом, если эту кодировку выправить, прогнав файл через функцию remove_non_ascii, запись принимала вид:

Такой файл также не поглощался парсером, из-за лишних знаков.
Пришлось вспоминать регулярные выражения и чистить файл перед загрузкой в парсер.
Вопрос: почему нельзя создать нормальную БД, которая выкладывается для работы приобретает оттенок риторического.
Чтобы выровнять стартовые возможности парсеров, очистим тестовый фрагмент от вышеуказанных нестыковок.
Код для очистки файла БД перед загрузкой в парсер:
Код
from datetime import datetime import re from unidecode import unidecode start = datetime.now() f= open('AS_HOUSE.462.xml', 'r',encoding='ANSI') def remove_non_ascii(text): return unidecode(unidecode(text)) for line in f: b=remove_non_ascii(line) for c in re.finditer(r'\w{5}NUM="\d{1,}\"\w\"',b): print(c[0]) c1=c[0][:-3]+c[0][-2] print(c1) b=b.replace(c[0],c1) # замена в строке #сохраняем результат f1= open('out.xml', 'w',encoding='ANSI') f1.write(b) f1.close() f.close() print(datetime.now()- start)
Код переводит в xml-файле non_ascii символы в нормальные и затем удаляет лишние "" в наименованиях строений и домов.
SAX-парсер
Для старта возьмем небольшой xml файл (58,8 Мб), на выходе планируем получить txt или csv, удобный для дальнейшей обработки в pandas или excel.
Код
import xml.sax import csv from datetime import datetime start = datetime.now() class EventHandler(xml.sax.ContentHandler): def __init__(self,target): self.target = target def startElement(self,name,attrs): self.target.send(attrs._attrs.values()) def characters(self,text): self.target.send('') def endElement(self,name): self.target.send('') def coroutine(func): def start(*args,**kwargs): cr = func(*args,**kwargs) cr.__next__() return cr return start with open('out.csv', 'a') as f: # example use if __name__ == '__main__': @coroutine def printer(): while True: event = (yield) print(event,file=f) xml.sax.parse("out.xml", EventHandler(printer())) print(datetime.now()- start)
Выполнив программу получим значения словаря python:

Время выполнения: 5-6 сек.
DOM-парсер
Обработаем тот же файл, предварительно загрузив его целиком в память. Именно такой метод использует DOM-парсер.
Код
import codecs,os import xml.etree.ElementTree as ET import csv from datetime import datetime parser = ET.XMLParser(encoding="ANSI") tree = ET.parse("out.xml",parser=parser) root = tree.getroot() Resident_data = open('AS_HOUSE.0001.csv', 'a',encoding='ANSI') csvwriter = csv.writer(Resident_data) attr_names = [ 'HOUSEID', 'HOUSEGUID', 'AOGUID', 'HOUSENUM', 'STRUCNUM', 'STRSTATUS', 'ESTSTATUS', 'STATSTATUS', 'IFNSFL', 'IFNSUL', 'TERRIFNSFL', 'TERRIFNSUL', 'OKATO', 'OKTMO', 'POSTALCODE', 'STARTDATE', 'ENDDATE', 'UPDATEDATE', 'COUNTER', 'NORMDOC', 'DIVTYPE', 'REGIONCODE' ] start = datetime.now() object = [] for member in root.findall('House'): object = [member.attrib.get(attr_name, None) for attr_name in attr_names] csvwriter.writerow(object) Resident_data.close() print(datetime.now()- start)
Время выолнения 2-3 сек.
Победа DOM-парсера?
Файлы побольше
Файлы небольшого размера не отражают действительности в полной мере. Возьмем файл побольше 353 Мб (предварительно почистив, как было указано выше).
Результаты погона:
SAX-парсер: 0:00:32.090836 — 32 сек
DOM-парсер: 0:00:16.630951 — 16 сек
Разница в 2 раза по скорости. Однако это не умаляет главного достоинства SAX-парсера — возможность обрабатывать файлы большого размера без предварительной загрузки в память.
Остается сожалеть, что данное достоинство не применимо к БД ФИАС, так как требуется предварительная работа с кодировками.
Файл для предварительной очистки кодировок:
— 353 Мб в архиве.
Очищенный файл БД для тестов парсеров:
— 353 Мб в архиве.
