lxml с большим файлом: отфильтровать поддеревья на основе атрибута

Проблема высокого уровня, которую я пытаюсь решить, заключается в том, что у меня есть дамп данных SMS размером 1,5 ГБ, и я пытаюсь отфильтровать файл, чтобы сохранить только сообщения для одного контакта и от него.

Я использую lxml в Python для анализа файла, но дайте мне знать, если есть варианты получше.

Структура XML-файла выглядит следующим образом:

SMSES (root node)
  'count': 'xxxx',
  (Children):
      MMS
          'address': 'xxxx',
          'foo':     'bar',
           ... : ...,
           (Children)
               'other fields': 'that _do not_ specify address',
      MMS
          'address': 'xxxx',
          'foo':     'bar',
           ... : ...,
           (Children)
               'other fields': 'that _do not_ specify address'

т. е. я хочу обойти дочерние элементы корневого узла и для каждого MMS, где «адрес» не соответствует определенному значению, удалить это MMS и все его потомки (дочерние элементы, как правило, содержат такие элементы, как изображения и т. д.).

Что я пробовал:

Я нашел такие вопросы/ответы: как удалить элемент в lxml

Но эти потоки, как правило, имеют простые примеры без вложенных элементов.

  • Мне непонятно, как использовать tree.xpath() для поиска элементов, которые не соответствуют значению
  • Мне не ясно, удаляет ли вызов remove(item) потомков элемента (чего я хочу в данном случае).

Я попробовал очень наивный подход, в котором я получаю итератор, а затем прохожу по дереву, удаляя элементы по мере продвижения:

from lxml.etree import XMLParser, parse
p = XMLParser(huge_tree=True)
tree = parse('backup.xml', parser=p)

it = tree.iter()
item = next(it) # consume root node

for item in it:
    if item.attrib['address'] != '0000':
        item.getparent().remove(item)

Проблема с этим сценарием заключается в том, что итератор выполняет DFS, а дочерние элементы MMS не имеют поля адреса. Итак, я ищу:

  • Каков наиболее эффективный + достаточно простой способ выполнить мою задачу?
  • В противном случае, как я могу заставить tree.iter() предоставить мне итератор BFS только для соседей первой степени корня?
  • Действительно ли remove(item) удаляет всех потомков или прикрепляет дочерние элементы к родителю?

Спасибо, что нашли время прочитать. Извините, если это наивный вопрос — синтаксический анализ XML-файлов на самом деле не мой хлеб с маслом, и мне как новичку было трудно читать документацию по LXML.

Спасибо!


person Addison    schedule 28.10.2019    source источник
comment
Было бы легче помочь, если бы вы задавали один отдельный вопрос за раз. Урезанный образец фактического XML, а не просто структура, также поможет нам понять. Судя по всему, ваш наивный подход не вызывает серьезных проблем с памятью, даже если файл большой. Это правда?   -  person mzjn    schedule 28.10.2019
comment
Не могли бы вы добавить к вопросу допустимый фрагмент XML?   -  person balderman    schedule 28.10.2019


Ответы (1)


На прошлой неделе вышла новая версия Saxon/C с привязкой к языку Python, включающая возможность потоковой передачи XSLT 3.0: это очень новое программное обеспечение, но вы можете попробовать его (с оценочной лицензией Saxon-EE, доступной на saxonica.com). Таблица стилей очень проста:

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0">

<xsl:mode streamable="yes"/>

<xsl:template match="/">
<SMSES>
   <xsl:copy-of select="SMS[@address='specific value']"/>
</SMSES>
</xsl:template>

</xsl:transform>

К сожалению, вы абстрагировали свой XML, поэтому я не могу сказать, является ли «адрес» на самом деле элементом или атрибутом, и это имеет большое значение при потоковой передаче. Здесь я предположил, что это атрибут, но если вы предоставите реальный образец XML, я могу помочь вам создать реальный работающий код XSLT.

Вы также можете запустить это прямо из командной строки, используя установленный продукт Saxon/Java, если нет реальных ограничений, что его нужно запускать из Python. Но в любом случае для потоковой передачи требуется корпоративная версия Saxon.

person Michael Kay    schedule 28.10.2019