Ruby XML парсинг

Разбор XML-файлов является довольно распространенной задачей. Существует множество библиотек, которые могу в этом помочь. В своих проектах на Ruby On Rails я использую библиотеку Nokogiri.

Не так давно я столкнулся с задачей разбора файла объемом 350+ Мб, содержащего около 4 млн. элементов. Nokogiri имеет очень быстрый и удобный XML-парсер, который построит DOM-структуру документа и позволит легко обратиться к любым данным. Все замечательно с одной крупной оговоркой — вся структура будет находиться в оперативной памяти. И чем большего объема мы имеем документ, тем больше потребуется памяти. Мой XML-документ занимал более 2Гб памяти. Это не обязательно является проблемой, однако в моем случае, пришлось серьезно задуматься о целесообразности такого подхода.

XML-файлы могут быть и более объемными, поэтому я решил раз и навсегда исправить это узкое место, чтобы вообще не зависеть от размера документа. Варианты решения ниже.

SAX-парсинг

SAX (или Simple API for XML) — альтернативный метод разбора. В основе лежит генерация событий при нахождении парсером заданных элементов. Другими словами, анализатор читает документ и вызывает заданные методы, если находит элементы. Nokogiri поддерживает такую стратегию.

Для реализации следует описать некий класс, который будет обрабатывать события

Представим, что у нас есть простой XML

Запускается анализ примерно так:

Nokogiri будет построчно читать документ, вызывая start_element, characters и end_element, когда обнаружит открывающийся тег, контент и закрывающийся тег соответственно.

Разбирая строку

мы получим

SAX хорошо подходит для извлечения конкретных данных из XML-документа. Если наиболее важны значения атрибутов и данные элементов, нежели структура и контекст, то SAX является отличным решением.

DOM-парсинг

Поскольку XML-формат используется для описания структурированных данных, то это почти всегда означает, что структура все-таки важна. При разборе методом SAX мы теряем информацию о древовидной структуре. Когда же используется DOM-метод, мы получаем полный набор объектов со всей структурой исходного XML-файла. Как я уже упоминал ранее, DOM-парсер Nokogiri очень хорош, но он хранит всю DOM-структуру в оперативной памяти.

DOM-парсер Nokogiri работает примерно так:

Мы получаем объект document, который будет содержать DOM-представление XML-документа. Далее мы можем использовать CSS или XPath, чтобы обратиться к любой точке DOM. Замечательно, но если заботиться о потреблении памяти, может помочь Nokogiri::XML::Reader.

Nokogiri::XML::Reader

Этот метод требует немного больше памяти, чем SAX, но его преимущество в том, что мы можем сохранить структуру необходимых нам узлов. Если бы мы имели миллион узлов (см. пример XML-файла), то можно обрабатывать каждый узел методом DOM сразу как только он был прочитан. Таким образом мы можем использовать преимущества описанных выше методов.

Как это делается

Объекты Nokogiri::XML::Reader представляют собой узлы в виде строк. Метод outer_xml дает нам часть документа в виде строки, которая содержит только один полный узел — то, что нам и нужно! Далее мы превращаем эту часть в DOM-представление и обрабатываем обычным DOM-парсером.

Именно так мне удалось избежать большого потребления памяти сервера при парсинге больших XML-файлов. Если раньше, при использовании DOM-парсинга, разбор файла размером в 350Мб потреблял не менее 1.5Гб оперативной памяти и потребление росло при увеличении объема файла, то теперь парсинг файлов любого размера требует не более 100Мб памяти.