Самый быстрый SAX-парсер для python

от автора

Внезапно захотелось пересчитать все xml-теги в 240 тысячах xml-файлов общим весом 180 GB. Питоном — и побыстрее.

Задача

На самом деле захотелось прикинуть — насколько реально перегнать Библиотеку, Чье Имя Нельзя Произносить Вслух, из fb2 в docbook. В связи со «специфичностью» FB2 надо прикинуть — какие теги можно просто пропустить ввиду редкости. Т.е. просто пересчитать количество вхождения каждого тега во все файлы.
По дороге планировалось сравнить разные sax-парсеры. К сожалению — тестирования не получилось, т.к. и xml.sax и lxml на первом же fb2 поломались. В итоге остался xml.parsers.expat.
Да, и еще — файлы *.fb2 упакованы в zip-архивы.

Исходные данные

Исходными данными является снапшот Библиотеки по состоянию на 2013.02.01, цельнотянутый из тор Интернетов: 242525 файла *.fb2 общим весом 183909288096 байт, упакованые в 56 zip-архивов общим весом 82540008 байт.
Платформа: Asus X5DIJ (Pentium DualCore T4500 (2×2.30), 2GB RAM); Fedora 18, python 2.7.

Код

Написано на скорую руку, с претензией на универсальность:

#!/bin/env python # -*- coding: utf-8 -*- ''' '''  import sys, os, zipfile, hashlib, pprint import xml.parsers.expat, magic  mime = magic.open(magic.MIME_TYPE) mime.load() tags = dict() files = 0  reload(sys) sys.setdefaultencoding('utf-8')  def start_element(name, attrs): 	tags[name] = tags[name] + 1 if name in tags else 1  def	parse_dir(fn): 	dirlist = os.listdir(fn) 	dirlist.sort() 	for i in dirlist: 		parse_file(os.path.join(fn, i))  def	parse_file(fn): 	m = mime.file(fn) 	if (m == 'application/zip'): 		parse_zip(fn) 	elif (m == 'application/xml'): 		parse_fb2(fn) 	else: 		print >> sys.stderr, 'Unknown mime type (%s) of file %s' % (m, fn)  def	parse_zip(fn): 	print >> sys.stderr, 'Zip:', os.path.basename(fn) 	z = zipfile.ZipFile(fn, 'r') 	filelist = z.namelist() 	filelist.sort() 	for n in filelist: 		try: 			parse_fb2(z.open(n)) 			print >> sys.stderr, n 		except: 			print >> sys.stderr, 'X:', n  def	parse_fb2(fn): 	global files 	if isinstance(fn, str): 		fn = open(fn) 	parser = xml.parsers.expat.ParserCreate() 	parser.StartElementHandler = start_element 	parser.Parse(fn.read(), True) 	files += 1  def	print_result(): 	out = open('result.txt', 'w') 	for k, v in tags.iteritems(): 		out.write(u'%s\t%d\n' % (k, v)) 	print 'Files:', files  if (__name__ == '__main__'): 	if len(sys.argv) != 2: 		print >> sys.stderr, 'Usage: %s <xmlfile|zipfile|folder>' % sys.argv[0] 		sys.exit(1) 	src = sys.argv[1] 	if (os.path.isdir(src)): 		parse_dir(src) 	else: 		parse_file(src) 	print_result()  

Результаты

Заряжаем:

time nice ./thisfile.py ~/Torrent/....ec > out.txt 2>err.txt

Получаем:
* Время выполнения — 74’15..45" (параллельно выполнялась небольшая работа и слушалась музыка, ессно);
* Получилось, что скорость обработки — ~40 MB/s (или 58 тактов/байт)
* Отброшено 2584 файлов *.fb2 (expat хоть и non validate parser — но не до такой же степени…) — ~10%;
* в файле results.txt — чего только нет…

А быстрее — можно? На python.

ссылка на оригинал статьи http://habrahabr.ru/post/171447/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *