В предыдущей статье был рассмотрен подход к созданию 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 Мб в архиве.