Внезапно захотелось пересчитать все 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 (2x2.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'%st%dn' % (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.
Автор: TIEugene