Высокопроизводительный анализ XML в Python с помощью lxml

Изучаем пределы возможностей этого полнофункционального программного пакета для анализа и сериализации XML

lxml – это быстрая и гибкая библиотека для обработки XML на Python. Она снабжена поддержкой языка запросов XML (XPath) и языка преобразования XML-документов (XSLT) и предоставляет API ElementTree. В этой статье подчеркивается как простота lxml, так и ее высокая производительность при обработке очень больших объемов XML-данных.

Лиза Дэли, разработчик ПО и владелец компании, Threepress Consulting Inc.

Photo of Liza DalyЛиза Дэли – разработчик программного обеспечения, специализируется на приложениях для издательской индустрии. Она была ведущим разработчиком многих крупных онлайновых продуктов издательств Oxford University Press, O'Reilly Media и других. В настоящее время Лиза является независимым консультантом и основателем проекта Threepress, цель которого – разработка электронных книг.



24.10.2011

24 марта 2011 - В ответ на отзыв читателя автор изменил листинг 3 (переставив запятую после значения таким образом, что она оказалась снаружи одиночных кавычек). Первая строка кода в листинге 3 изменена с:

context = etree.iterparse(infile, events=('end,'), tag='Title')

на:

context = etree.iterparse(infile, events=('end',), tag='Title')

Часто используемые аббревиатуры:

  • API: Application Programming Interface
  • DOM: Document Object Model
  • HTML: Hypertext Markup Language
  • SAX: Simple API for XML
  • XML: Extensible Markup Language
  • XPath: XML Path Language
  • XSLT: Extensible Stylesheet Language Transformations

Знакомство с lxml

Python никогда не страдал от нехватки XML-библиотек. Начиная с версии 2.0 он включает в себя привычную xml.dom.minidom и связанные с ней модели pulldom и Simple API for XML (SAX). Начиная с версии 2.4 в него входит широко распространенный API ElementTree. К тому же всегда есть независимые библиотеки, которые предоставляют более высокоуровневые или более подходящие для Python интерфейсы.

В то время как для простого DOM- или SAX-анализа небольших файлов достаточно любой XML-библиотеки, разработчики все чаще сталкиваются с крупными наборами данных и необходимостью разбирать XML в реальном времени в контексте Web-сервисов. В то же время опытные XML-разработчики могут предпочесть более XML-ориентированные языки, такие как XPath или XSLT, за их лаконичность и выразительность. Идеальным выходом из этой ситуации было бы иметь доступ к декларативному синтаксису XPath, сохраняя при этом общую функциональность, доступную в Python.

Версии ПО и примеры, использованные в статье

  • lxml 2.1.2
  • libxml2 2.7.1
  • libxslt 1.1.24
  • cElementTree 1.0.5
  • Данные обновленных авторских прав США, предоставленные фирмой Google
  • содержимое RDF-дампа Открытого Каталога

Более подробную информацию о ПО и данных см. в разделе Ресурсы.

Тесты проводились на машине ThinkPad T43 с процессором Pentium M 1.86 ГГц и оперативной памятью 2Гб под управлением Ubuntu с использованием IPython-команды timeit. Значения времени представлены исключительно для сравнения подходов и не должны рассматриваться как показатели описанного ПО.

lxml - первая XML-библиотека для Python, которая имеет высокую производительность и содержит встроенную поддержку XPath 1.0, XSLT 1.0, пользовательские элементарные классы и даже типичный для Python интерфейс привязки данных. Библиотека lxml построена на базе двух библиотек языка С: libxml2 и libxslt. Они обеспечивают большую часть возможностей для основных задач анализа, сериализации и преобразования.

Какие именно компоненты lxml использовать в коде - зависит от ваших потребностей. Вам удобен XPath? Вы предпочитаете работать с Python-подобными объектами? Сколько памяти для поддержания крупных деревьев доступно на данный момент в системе?

Данная статья не затрагивает все аспекты lxml, однако она демонстрирует методы эффективной обработки XML-файлов, оптимизированной для достижения высокой скорости и низкого использования памяти. Оба используемых примера находятся в свободном доступе: данные обновленных авторских прав США, переведенные фирмой Google в формат XML, и содержимое RDF-дампа Открытого Каталога.

В этой статье lxml сравнивается лишь с cElementTree, а не с десятками других доступных библиотек Python. Я выбрал cElementTree, так как она встроен в Python 2.5 и, подобно lxml, построена на базе библиотек C.

Сложности, связанные с большими объемами данных

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

Листинг 1. Простая операция анализа
from lxml import etree
doc = etree.parse('content-sample.xml')

cElementTree

Если все, что требуется от вашей программы – это разбор и простой анализ, можно воспользоваться модулем cElementTree, который является частью пакета Python 2.5. Это реализация ElementTree на языке C, которая использует expat для анализа и среди всех остальных библиотек является наиболее подходящей, когда требуется разобрать дерево полного документа. Однако ее API еще более ограничен, чем ElementTree, и она медленнее lxml в большинстве задач, особенно в сериализации.

Метод parse из lxml считывает файл целиком и строит в памяти дерево. По сравнению с CElementTree дерево lxml гораздо более дорогостоящее, потому что оно содержит в памяти больше информации о контексте узла, включая ссылки на родителя. Анализ документа размером 2 ГБ на машине с 2 ГБ оперативной памяти немедленно приведет к использованию файла подкачки, что катастрофически скажется на производительности. Если приложение написано с расчетом на то, что эти данные будут находиться в памяти, то будет уместно провести основательную перестройку кода.

Итерационный анализ

В случаях, когда нежелательно или непрактично строить дерево в памяти, используйте технологию итерационного анализа, в которой не требуется считывать исходный файл целиком. lxml предлагает два подхода:

  • Поддержка класса целевого анализатора
  • Использование метода iterparse

Использование метода целевого анализатора

Метод целевого анализатора знаком разработчикам, которым удобен управляемый по событиям SAX код. Целевой анализатор - это класс, который обеспечивает выполнение следующих методов:

  1. start срабатывает при открытии элемента. Данные потомка и элемента пока недоступны.
  2. end срабатывает при закрытии элемента. Теперь доступны все узлы-потомки элемента, в том числе и текстовые узлы.
  3. data срабатывает при обращении к текстовым потомкам и имеет доступ к текстовому содержимому.
  4. close срабатывает при завершении анализа.

В листинге 2 показано создание класса целевого анализатора (TitleTarget) , который реализует необходимые методы. Этот анализатор собирает текстовые потомки элемента Title во внешний список (self.text) и при вызове метода close() возвращает его.

Листинг 2. Целевой анализатор, возвращающий список текстовых потомков элемента Title
class TitleTarget(object):
    def __init__(self):
        self.text = []
    def start(self, tag, attrib):
        self.is_title = True if tag == 'Title' else False
    def end(self, tag):
        pass
    def data(self, data):
        if self.is_title:
            self.text.append(data.encode('utf-8'))
    def close(self):
        return self.text

parser = etree.XMLParser(target = TitleTarget())

# Эти и многие другие примеры считываются из авторских прав Google
infile = 'copyright.xml'

results = etree.parse(infile, parser)    

# В конце 'results' будет содержать результаты работы  
# метода close() целевого анализатора

out = open('titles.txt', 'w')
out.write('\n'.join(results))
out.close()

Этот код при тестировании на авторских правах показал результат 54 секунды. Подход на основе целевого анализатора может быть достаточно быстрым и не генерирует дерево анализа, занимающее много памяти, но все события срабатывают для всех элементов, встречающихся в данных. Для очень больших документов, если нас интересует лишь небольшое число элементов, как в этом примере, такой подход может быть нежелателен. Возможно ли ограничить обработку до выбранного тега и улучшить производительность?

Использование метода iterparse

Метод iterparse из библиотеки lxml является расширением ElementTree API. Метод iterparse возвращает Python-итератор для контекста выбранного элемента. Он принимает два используемых аргумента: кортеж отслеживаемых событий и имя тега.В данном случае интерес представляет лишь текстовое содержимое элемента <Title> (которое становится доступно при достижении события end). Результат работы кода из листинга 3 будет идентичен результату метода целевого анализатора из листинга 2, но этот код должен бы работать значительно быстрее, так как lxml может оптимизировать обработку событий изнутри. Объем кода также значительно меньше.

Листинг 3. Простое итерирование над именованным тегом и событием
context = etree.iterparse(infile, events=('end',), tag='Title')

for event, elem in context:
       out.write('%s\n' % elem.text.encode('utf-8'))

Если вы запустите этот код на выполнение и проследите за результатами, то увидите, что сначала он добавляет заголовки очень быстро, но затем очень сильно замедляется. Быстрая проверка top показывает, что началось использование файла подкачки:

PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND                
170 root      15  -5     0    0    0 D  3.9  0.0   0:01.32 kswapd0

Что происходит? Несмотря на то, что iterparse сначала не считывает весь файл целиком, он не освобождает ссылки на узлы после каждой итерации. Если мы собираемся многократно обращаться к целому документу, это даже полезно. Однако в данном случае предпочтительнее было бы освобождать эту память в конце каждого цикла. Это касается, в частности, ссылок на ранее обработанных потомков и текстовые узлы, а также на предшествующие узлы-братья для данного узла, чьи ссылки из корневого узла также неявно сохраняются, как в листинге 4:

Листинг 4. Исправленное итерирование с очисткой ненужных ссылок на узлы
for event, elem in context:
    out.write('%s\n' % elem.text.encode('utf-8'))        

    # Нет обращений к потомкам, поэтому вызов clear() безопасен
    elem.clear()

    # Удалите пустые ссылки из корневого узла в <Title> 
    while elem.getprevious() is not None:
        del elem.getparent()[0]

Для удобства код из листинга 4 был переработан в функцию, которая принимает на вход выполняемую над узлом операцию func , как в листинге 5. Этот метод будет использоваться далее в примерах.

Листинг 5. Функция, просматривающая в цикле контекст, вызывая каждый раз func, и затем освобождая ненужные ссылки
def fast_iter(context, func):
    for event, elem in context:
        func(elem)
        elem.clear()
        while elem.getprevious() is not None:
            del elem.getparent()[0]
    del context

Производительность.

Оптимизированный подход с методом iterparse, показанный в листинге 4, выдает результат, идентичный результату целевого анализатора из листинга 2, но в два раза быстрее. Он даже быстрее cElementTree в случаях, когда задание ограничено конкретным событием или именем тега, как здесь. (Однако, в большинстве случаев, cElementTree превзойдет lxml по производительности, когда анализ является основным видом деятельности.)

В таблице 1 приведено время исполнения различных методов при анализе авторских прав на машине, описанной на врезке.

Таблица 1. Сравнение итерационных методов анализа: выделение text() из <Title>
XML-библиотекаМетодСреднее время, с
cElementTreeIterparse32
lxmlЦелевой анализатор54
lxmlОптимизированный iterparse25

Что с масштабированием?

Использование того же метода iterparse в листинге 4 на содержимом Открытого Каталога занимает 122 секунды, или в 5 с небольшим раз дольше, чем анализ авторских прав. Так как содержимое Открытого Каталога почти в 5 раз больше (около 1.9 ГБ), для этого метода можно ожидать примерно линейного роста затрат времени даже на очень больших файлах.


Сериализация

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

Библиотека lxml особенно эффективна, если речь идет о сериализации XML в строку или файл, так как данная библиотека напрямую базируется на C-коде libxml2. Если в задании требуется какая-либо сериализация, то lxml является очевидным выбором. Однако для достижения наивысшей производительности стоит воспользоваться рядом приемов.

Используйте deepcopy для сериализации поддеревьев.

Lxml сохраняет связи между узлами-потомками и их родителями. Поэтому узел в lxml может иметь одного и только одного родителя. (В cElementTree нет понятия узла-родителя)

Код в листинге 6 берет каждый элемент <Record> из файла с авторскими правами и сохраняет упрощенную запись, содержащую только заголовок и информацию об авторском праве.

Листинг 6. Сериализация потомков элемента
from lxml import etree
import deepcopy 

def serialize(elem):
    # Вывод нового дерева в виде:
    # <SimplerRecord>
    #   <Title>This title</Title>
    #   <Copyright><Date>date</Date><Id>id</Id></Copyright>
    # </SimplerRecord>
    
    # Создание нового корневого узла
    r = etree.Element('SimplerRecord')

    # Создание нового потомка
    t = etree.SubElement(r, 'Title')

    # Присвоение текстовому атрибуту нового потомка 
	# исходного текстового содержания <Title>
    t.text = elem.iterchildren(tag='Title').next().text

    # Полное копирование дерева-потомка
    for c in elem.iterchildren(tag='Copyright'):
        r.append( deepcopy(c) )
    return r

out = open('titles.xml', 'w')
context = etree.iterparse('copyright.xml', events=('end',), tag='Record')

# Пошаговый проход по всем узлам <Record>, используя быстрый итеративный метод 
fast_iter(context, 
          # Сериализация упрощенной версии для каждого <Record>
          # и запись в конечный файл
          lambda elem: 
              out.write(
                 etree.tostring(serialize(elem), encoding='utf-8')))

Не стоит использовать deepcopy для копирования текста одного узла. Гораздо быстрее создать новый узел, заполнить его текстовый атрибут вручную и затем сериализовать его. Тесты показали, что вызов deepcopy для <Title> и <Copyright> был на 15% медленнее чем код в листинге 6. В то же время использование deepcopy для сериализации крупных деревьев-потомков дает огромный выигрыш в производительности.

При сравнении с cElementTree (см. код из листинга 7) lxml-сериализатор оказался почти в 2 раза быстрее (50 секунд против 95 секунд):

Листинг 7. Сериализация с помощью сElementTree
def serialize_cet(elem):
    r = cet.Element('Record')

    # Создание нового элемента с тем же текстовым потомком
    t = cet.SubElement(r, 'Title')
    t.text = elem.find('Title').text

    # ElementTree не хранит ссылки на родителей – 
    # элемент может существовать в нескольких деревьях одновременно.
    # Использование deepcopy здесь необязательно.
    for c in elem.findall('Copyright'):
       r.append(h)
    return r

context = cet.iterparse('copyright.xml', events=('end','start'))
context = iter(context)
event, root = context.next()

for event, elem in context:
    if elem.tag == 'Record' and event =='end':
        result = serialize_cet(elem)
        out.write(cet.tostring(result, encoding='utf-8'))
        root.clear()

Для получения более подробной информации об этом итерационном шаблоне обратитесь к разделу «Incremental Parsing» в документации к ElementTree. (См. раздел Ресурсы)


Быстрый поиск элементов

Типичной задачей работы с XML после анализа является поиск определенных данных внутри разобранного дерева. lxml предлагает несколько подходов, от упрощенного поискового синтаксиса до полного XPath 1.0. Как пользователю вам стоит знать о характеристиках производительности и методах оптимизации различных подходов.

Избегайте использования find и findall

Методы find и findall , унаследованные из ElementTree API, находят один или несколько узлов-потомков, используя упрощенный, похожий на XPath язык выражений, называемый ElementPath. Пользователи, переходящие с ElementTree на lxml могут свободно продолжать использовать синтаксис find/ElementPath.

Библиотека lxml поддерживает еще два варианта поиска подузлов: методы iterchildren/iterdescendants и настоящий XPath. В случаях, когда выражение должно сопоставлять имя узла, методы iterchildren или iterdescendants с их необязательным параметром тега работают гораздо быстрее (в некоторых случаях почти в 2 раза), чем эквивалентные им выражения ElementPath.

Для более сложных шаблонов используйте класс XPath для предварительной компиляции поисковых запросов. Простые шаблоны, которые имитируют поведение iterchildren c аргументами тега (например etree.XPath("child::Title")), выполняются фактически за то же время, что и их эквиваленты с iterchildren, тем не менее предварительная компиляция важна. Компиляция шаблона на каждом шагу цикла или использование метода xpath() (описанного в документации к lxml, см. раздел Ресурсы ) может занимать почти в 2 раза больше времени, чем многократное использование один раз скомпилированного шаблона.

XPath работает очень быстро. Если требуется сериализовать только подмножество узлов, лучше заранее установить границы с помощью точного XPath-выражения, чем изучать все узлы позже. Например, ограничив сериализацию только теми заголовками, которые содержат слово night, как в листинге 8, можно уменьшить время до 60% от того, что потребовалось бы при сериализации всего множества.

Листинг 8. Условная сериализация с помощью классов XPath
def write_if_node(out, node):
    if node is not None:
        out.write(etree.tostring(node, encoding='utf-8'))

def serialize_with_xpath(elem, xp1, xp2):
    '''Берем исходный элемент <Record> и применяем
	'''2 прекомпилированных XPath-класса. Возвращаем узел 
	'''только в случае совпадения первого выражения.  
    '''
    r = etree.Element('Record')

    t = etree.SubElement(r, 'Title')
    x = xp1(elem)
    if x:
        t.text = x[0].text
        for c in xp2(elem):
            r.append(deepcopy(c))
        return r

xp1 = etree.XPath("child::Title[contains(text(), 'night')]")
xp2 = etree.XPath("child::Copyright")
out = open('out.xml', 'w')
context = etree.iterparse('copyright.xml', events=('end',), tag='Record')
fast_iter(context, 
   lambda elem: write_if_node(out, serialize_with_xpath(elem, xp1, xp2)))

Поиск узлов в других частях документа

Стоит отметить, что даже при использовании iterparse можно применять XPath-предикаты, основанные на «заглядывании вперед» текущего узла. Для обнаружения всех узлов <Record>, после которых сразу же следует запись, содержащая в заголовке слово night, следует сделать следующее:

etree.XPath("Title[contains(../Record/following::Record[1]/Title/text(), 'night')]")

Однако при применении эффективно использующей память стратегии итерирования, описанной в листинге 4, эта команда не вернет ничего, так как предшествующие узлы удаляются в процессе анализа документа:

etree.XPath("Title[contains(../Record/preceding::Record[1]/Title/text(), 'night')]")

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


Другие способы повышения производительности

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

Psyco

Модуль Psyco - это часто упускаемый из виду способ повышения скорости Python-приложений с минимальными затратами труда. Типичный выигрыш для чистой Python-программы – от 2х до 4х раз, но lxml выполняет большую часть работы на C, поэтому разница необычно мала. При запуске кода из листинга 4 с модулем Psyco время работы уменьшилось всего на 3 секунды (43.9 с против 47.3 с). Psyco требует при работе много дополнительной памяти, что может свести на нет все преимущества, если потребуется использование файла подкачки.

Если в Вашем lxml-приложении есть часто исполняемый основной код на Python (например, активное манипулирование строками в текстовых узлах), может быть полезно подключить Psyco исключительно для соответствующих методов. За более подробной информацией о Psyco обратитесь к разделу Ресурсы.

Разделение на потоки

Если же ваше приложение использует в основном внутренние средства lxml, основанные на языке С, выгодно использовать его как многопоточное приложение в мультипроцессорной среде. Существуют ограничения на порождение потоков, особенно с XSLT. За более подробной информацией обратитесь к разделу FAQ в документации lxml.

Разделяй и властвуй

Если можно дробить крупные документы на индивидуально анализируемые поддеревья, становится возможно разбивать документ на уровне поддеревьев (используя быструю сериализацию lxml) и распределять работу с этими файлами между несколькими компьютерами. Использование виртуальных серверов с выделением ресурсов по требованию все больше набирает популярность при решении оффлайновых задач, требующих больших процессорных ресурсов. Имеется специально ориентированное на Python-программистов учебное пособие по установке и управлению виртуальным EC2 кластером. См. подробнее в разделе Ресурсы.


Общие стратегии для задач обработки больших объемов XML-данных

Конкретные примеры кода, приведенные здесь, могут не подходить для вашего проекта, но если вам придется столкнуться с XML-данными объемом в несколько гигабайт и более, примите во внимание несколько принципов, выработанных в ходе тестирования и изучения документации к lxml:

  • Используйте стратегию итерационного анализа для пошаговой обработки больших документов.
  • Если требуется поиск по целому документу в произвольном порядке, перейдите на индексированную XML-базу данных.
  • Будьте предельно консервативны при выборе данных. Если интерес представляют отдельные узлы, используйте методы, которые производят поиск по имени. Если вам требуется предикативный синтаксис, используйте один из XPath-классов и методов.
  • Учитывайте специфику задачи и принимайте во внимание удобство подходов для разработчика. Если скорость не главное, для Python-разработчиков могут быть более естественны объектные модели, такие как objectify из lxml или Amara. Если требуется только анализ, быстрее работает cElementTree.
  • Не пожалейте времени хотя бы на простейшее тестирование производительности. При обработке миллионов записей важен даже небольшой выигрыш в производительности и при этом не всегда очевидно, какие методы наиболее эффективны.

Заключение

Множество программных продуктов устроены по принципу "выбери два", то есть, вы можете выбрать только два качества из трех: скорости, гибкости, и читабельности. В то же время при правильном использовании библиотека lxml может обеспечить все три качества. Те XML-разработчики, кому приходилось бороться с производительностью DOM или с событийной моделью SAX, теперь имеют возможность поработать с типичными для Python библиотеками более высокого уровня. Программисты из мира Python, незнакомые с XML, получили простую возможность для изучения выразительных средств XPath и XSLT. Оба стиля программирования могут счастливо сосуществовать в приложении, построенном на базе lxml.

Возможности lxml значительно больше рассмотренных в данной статье. Не забудьте изучить модуль lxml.objectify, особенно при работе с небольшими наборами данных или приложениями, для которых XML не является основной целью. Для HTML-контента, который может быть плохо сформирован, lxml предоставляет 2 полезных пакета: модуль lxml.html и анализатор BeautifulSoup. Также можно расширить lxml собственноручно, написав Python-модули, которые могут быть вызваны из XSLT, или создав собственные Python- или C-расширения. Ищите информацию об этих и других возможностях в документации к lxml (ссылка в разделе Ресурсы).

Ресурсы

Научиться

Получить продукты и технологии

  • lxml (EN). Документация к lxml вполне понятна, но, возможно, излишне подробна. Ищите ценную информацию в разделе FAQ и в тестах производительности.
  • Данные обновленных авторских прав США, предоставленные фирмой Google (EN). Скачайте для экспериментов данные обновленных авторских прав США, сконвертированные в XML фирмой Google (архив 371MБ, 426 907 индивидуальных записей).
  • Содержимое RDF-дампа Открытого Каталога (EN). RDF-дамп базы данных Открытого Каталога (архив 1.9ГБ, 5 354 663 индивидуальных записей).
  • eXist (EN). Изучите эту систему управления базами данных с открытым кодом, использующую XQuery.
  • Psyco (EN). Узнайте подробнее об этом Python-модуле, который может существенно ускорить выполнение Python-кода.
  • Amara (EN). Попробуйте эту XML-библиотеку для Python с большим набором возможностей и характерным для Python API. Amara не обладает столь же высокой производительностью, что и lxml или cElementTree, но может быть очень полезной для большинства XML-задач.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=XML, Open source
ArticleID=767409
ArticleTitle=Высокопроизводительный анализ XML в Python с помощью lxml
publish-date=10242011