Como analiso XML em Python?

1003

Eu tenho muitas linhas em um banco de dados que contém XML e estou tentando escrever um script Python para contar instâncias de um atributo de nó específico.

Minha árvore se parece com:

<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>

Como posso acessar os atributos "1"e "2"no XML usando Python?

randombits
fonte
1
Relacionado: Python xml ElementTree de uma fonte de string?
Stevoisiak 2/11

Respostas:

781

Eu sugiro ElementTree. Existem outras implementações compatíveis da mesma API, como lxml, e cElementTreena própria biblioteca padrão do Python; mas, nesse contexto, o que eles adicionam principalmente é ainda mais velocidade - a facilidade da parte da programação depende da API, que ElementTreedefine.

Primeiro, crie uma instância Element a rootpartir do XML, por exemplo, com a função XML ou analisando um arquivo com algo como:

import xml.etree.ElementTree as ET
root = ET.parse('thefile.xml').getroot()

Ou qualquer uma das muitas outras maneiras mostradas em ElementTree. Então faça algo como:

for type_tag in root.findall('bar/type'):
    value = type_tag.get('foobar')
    print(value)

E padrões de código semelhantes, geralmente bastante simples.

Alex Martelli
fonte
41
Você parece ignorar xml.etree.cElementTree, que é fornecido com o Python e, em alguns aspectos, é mais rápido que o lxml ("o iterparse do lxml () é um pouco mais lento que o do cET" - e-mail do autor do lxml).
John Machin
7
O ElementTree funciona e está incluído no Python. No entanto, há suporte XPath limitado e você não pode passar para o pai de um elemento, o que pode atrasar o desenvolvimento (especialmente se você não souber disso). Consulte python xml query get parent para obter detalhes.
Samuel
11
lxmladiciona mais do que velocidade. Ele fornece acesso fácil a informações como nó pai, número da linha na fonte XML, etc., que podem ser muito úteis em vários cenários.
precisa saber é o seguinte
13
Parece que o ElementTree tem alguns problemas de vulnerabilidade, isto é uma citação dos documentos: Warning The xml.etree.ElementTree module is not secure against maliciously constructed data. If you need to parse untrusted or unauthenticated data see XML vulnerabilities.
Cristik
5
@ Cristik Este parece ser o caso da maioria dos analisadores XML, consulte a página de vulnerabilidades XML .
precisa saber é o seguinte
427

minidom é o mais rápido e bastante direto.

XML:

<data>
    <items>
        <item name="item1"></item>
        <item name="item2"></item>
        <item name="item3"></item>
        <item name="item4"></item>
    </items>
</data>

Pitão:

from xml.dom import minidom
xmldoc = minidom.parse('items.xml')
itemlist = xmldoc.getElementsByTagName('item')
print(len(itemlist))
print(itemlist[0].attributes['name'].value)
for s in itemlist:
    print(s.attributes['name'].value)

Resultado:

4
item1
item1
item2
item3
item4
Ryan Christensen
fonte
9
Como você obtém o valor de "item1"? Por exemplo: <item name = "item1"> Valor1 </item>
swmcdonnell 13/02/2013
88
Eu descobri, no caso de alguém ter a mesma pergunta. É s.childNodes [0] .nodeValue
swmcdonnell
1
Eu gosto do seu exemplo, quero implementá-lo, mas onde posso encontrar as funções minidom disponíveis. O site minidom python é uma porcaria na minha opinião.
Drewdin
1
Também estou confuso por que encontra itemdiretamente do nível superior do documento? não seria mais limpo se você fornecesse o caminho ( data->items)? porque, e se você também tivesse data->secondSetOfItemsesses nós nomeados iteme desejasse listar apenas um dos dois conjuntos de item?
amphibient
240

Você pode usar o BeautifulSoup :

from bs4 import BeautifulSoup

x="""<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>"""

y=BeautifulSoup(x)
>>> y.foo.bar.type["foobar"]
u'1'

>>> y.foo.bar.findAll("type")
[<type foobar="1"></type>, <type foobar="2"></type>]

>>> y.foo.bar.findAll("type")[0]["foobar"]
u'1'
>>> y.foo.bar.findAll("type")[1]["foobar"]
u'2'
VOCÊ
fonte
Obrigado pela info @ibz, sim, na verdade, se a fonte não for bem formada, será difícil analisar também os analisadores.
VOCÊ
45
três anos mais tarde com BS4 esta é uma grande solução, muito flexível, especialmente se a fonte não está bem formado
cedbeu
8
@YOU BeautifulStoneSoupestá DEPRECIADO. Basta usarBeautifulSoup(source_xml, features="xml")
andilabs
5
Outros 3 anos depois, tentei carregar o XML usando ElementTree, infelizmente ele não consegue analisar, a menos que eu ajuste a fonte em alguns locais, mas BeautifulSouptrabalhei imediatamente sem nenhuma alteração!
ViKiG
8
@andi Você quer dizer "obsoleto". "Depreciado" significa que seu valor diminuiu, geralmente devido à idade ou desgaste do uso normal.
Jpmc26
98

Existem muitas opções por aí. O cElementTree parece excelente se a velocidade e o uso da memória forem um problema. Tem muito pouca sobrecarga em comparação com a simples leitura no arquivo usando readlines.

As métricas relevantes podem ser encontradas na tabela abaixo, copiadas do site do cElementTree :

library                         time    space
xml.dom.minidom (Python 2.1)    6.3 s   80000K
gnosis.objectify                2.0 s   22000k
xml.dom.minidom (Python 2.4)    1.4 s   53000k
ElementTree 1.2                 1.6 s   14500k  
ElementTree 1.2.4/1.3           1.1 s   14500k  
cDomlette (C extension)         0.540 s 20500k
PyRXPU (C extension)            0.175 s 10850k
libxml2 (C extension)           0.098 s 16000k
readlines (read as utf-8)       0.093 s 8850k
cElementTree (C extension)  --> 0.047 s 4900K <--
readlines (read as ascii)       0.032 s 5050k   

Como apontado pelo @jfs , cElementTreevem com o Python:

  • Python 2: from xml.etree import cElementTree as ElementTree.
  • Python 3: from xml.etree import ElementTree(a versão C acelerada é usada automaticamente).
Cyrus
fonte
9
Existem desvantagens no uso do cElementTree? Parece ser um acéfalo.
mayhewsw
6
Aparentemente, eles não querem usar a biblioteca no OS X, pois passo mais de 15 minutos tentando descobrir de onde baixá-lo e nenhum link funciona. A falta de documentação impede que bons projetos prosperem, desejando que mais pessoas percebessem isso.
Stunner
8
@ Stunner: está no stdlib, ou seja, você não precisa baixar nada. Em Python 2: from xml.etree import cElementTree as ElementTree. Em Python 3: from xml.etree import ElementTree(a versão acelerada C é usada automaticamente)
jfs
1
@mayhewsw É mais difícil descobrir como usar eficientemente ElementTreepara uma tarefa específica. Para documentos que cabem na memória, é muito mais fácil de usar minidome funciona bem para documentos XML menores.
Acumenus
44

Sugiro xmltodict por simplicidade.

Ele analisa seu XML para um OrderedDict;

>>> e = '<foo>
             <bar>
                 <type foobar="1"/>
                 <type foobar="2"/>
             </bar>
        </foo> '

>>> import xmltodict
>>> result = xmltodict.parse(e)
>>> result

OrderedDict([(u'foo', OrderedDict([(u'bar', OrderedDict([(u'type', [OrderedDict([(u'@foobar', u'1')]), OrderedDict([(u'@foobar', u'2')])])]))]))])

>>> result['foo']

OrderedDict([(u'bar', OrderedDict([(u'type', [OrderedDict([(u'@foobar', u'1')]), OrderedDict([(u'@foobar', u'2')])])]))])

>>> result['foo']['bar']

OrderedDict([(u'type', [OrderedDict([(u'@foobar', u'1')]), OrderedDict([(u'@foobar', u'2')])])])
myildirim
fonte
3
Acordado. Se você não precisa do XPath ou de algo complicado, isso é muito mais simples de usar (especialmente no intérprete); útil para APIs REST que publicar XML em vez de JSON
Dan Passaro
4
Lembre-se de que OrderedDict não suporta chaves duplicadas. A maioria dos XML está repleta de vários irmãos do mesmo tipo (digamos, todos os parágrafos em uma seção ou todos os tipos em sua barra). Portanto, isso funcionará apenas para casos especiais muito limitados.
TextGeek
2
@TextGeek Nesse caso, result["foo"]["bar"]["type"]é uma lista de todos os <type>elementos, por isso ainda está funcionando (mesmo que a estrutura seja talvez um pouco inesperada).
luator 30/08/19
38

O lxml.objectify é realmente simples.

Tomando seu texto de exemplo:

from lxml import objectify
from collections import defaultdict

count = defaultdict(int)

root = objectify.fromstring(text)

for item in root.bar.type:
    count[item.attrib.get("foobar")] += 1

print dict(count)

Resultado:

{'1': 1, '2': 1}
Ryan Ginstrom
fonte
countarmazena as contagens de cada item em um dicionário com chaves padrão, para que você não precise verificar a associação. Você também pode tentar olhar collections.Counter.
Ryan Ginstrom
20

Python tem uma interface para o analisador de XML expat.

xml.parsers.expat

É um analisador não validador, portanto, XML ruim não será capturado. Mas se você sabe que seu arquivo está correto, isso é muito bom, e você provavelmente obterá as informações exatas que deseja e poderá descartar o resto rapidamente.

stringofxml = """<foo>
    <bar>
        <type arg="value" />
        <type arg="value" />
        <type arg="value" />
    </bar>
    <bar>
        <type arg="value" />
    </bar>
</foo>"""
count = 0
def start(name, attr):
    global count
    if name == 'type':
        count += 1

p = expat.ParserCreate()
p.StartElementHandler = start
p.Parse(stringofxml)

print count # prints 4
Tor Valamo
fonte
+1 porque estou procurando um analisador não validador que funcione com caracteres de origem estranhos. Espero que isso me dê os resultados que eu quero.
Nathan C. Tresch
1
O exemplo foi feito em 09 e é assim que foi feito.
Tor Valamo
14

Eu posso sugerir declxml .

Divulgação completa: escrevi esta biblioteca porque estava procurando uma maneira de converter entre estruturas de dados XML e Python sem precisar escrever dezenas de linhas de código de análise / serialização imperativo com o ElementTree.

Com declxml, você usa processadores para definir declarativamente a estrutura do seu documento XML e como mapear entre estruturas de dados XML e Python. Os processadores são usados ​​para serialização e análise, bem como para um nível básico de validação.

A análise nas estruturas de dados Python é simples:

import declxml as xml

xml_string = """
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>
"""

processor = xml.dictionary('foo', [
    xml.dictionary('bar', [
        xml.array(xml.integer('type', attribute='foobar'))
    ])
])

xml.parse_from_string(processor, xml_string)

Qual produz a saída:

{'bar': {'foobar': [1, 2]}}

Você também pode usar o mesmo processador para serializar dados em XML

data = {'bar': {
    'foobar': [7, 3, 21, 16, 11]
}}

xml.serialize_to_string(processor, data, indent='    ')

Que produz a seguinte saída

<?xml version="1.0" ?>
<foo>
    <bar>
        <type foobar="7"/>
        <type foobar="3"/>
        <type foobar="21"/>
        <type foobar="16"/>
        <type foobar="11"/>
    </bar>
</foo>

Se você deseja trabalhar com objetos em vez de dicionários, também pode definir processadores para transformar dados para e de objetos.

import declxml as xml

class Bar:

    def __init__(self):
        self.foobars = []

    def __repr__(self):
        return 'Bar(foobars={})'.format(self.foobars)


xml_string = """
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>
"""

processor = xml.dictionary('foo', [
    xml.user_object('bar', Bar, [
        xml.array(xml.integer('type', attribute='foobar'), alias='foobars')
    ])
])

xml.parse_from_string(processor, xml_string)

Que produz a seguinte saída

{'bar': Bar(foobars=[1, 2])}
Gatkin
fonte
13

Apenas para adicionar outra possibilidade, você pode usar desembaraçar , pois é uma biblioteca simples de XML para Python. Aqui você tem um exemplo:

Instalação:

pip install untangle

Uso:

Seu arquivo XML (um pouco alterado):

<foo>
   <bar name="bar_name">
      <type foobar="1"/>
   </bar>
</foo>

Acessando os atributos com untangle:

import untangle

obj = untangle.parse('/path_to_xml_file/file.xml')

print obj.foo.bar['name']
print obj.foo.bar.type['foobar']

A saída será:

bar_name
1

Mais informações sobre desembaraçar podem ser encontradas em " desembaraçar ".

Além disso, se você estiver curioso, poderá encontrar uma lista de ferramentas para trabalhar com XML e Python em " Python e XML ". Você também verá que os mais comuns foram mencionados por respostas anteriores.

jchanger
fonte
O que torna o desembaraçar diferente do minidom?
Aaron Mann
Não posso dizer a diferença entre os dois, pois não trabalhei com o minidom.
jchanger 31/01
10

Aqui está um código muito simples, mas eficaz, usando cElementTree.

try:
    import cElementTree as ET
except ImportError:
  try:
    # Python 2.5 need to import a different module
    import xml.etree.cElementTree as ET
  except ImportError:
    exit_err("Failed to import cElementTree from any known place")      

def find_in_tree(tree, node):
    found = tree.find(node)
    if found == None:
        print "No %s in file" % node
        found = []
    return found  

# Parse a xml file (specify the path)
def_file = "xml_file_name.xml"
try:
    dom = ET.parse(open(def_file, "r"))
    root = dom.getroot()
except:
    exit_err("Unable to open and parse input definition file: " + def_file)

# Parse to find the child nodes list of node 'myNode'
fwdefs = find_in_tree(root,"myNode")

Isso é do " python xml parse ".

Jan Kohila
fonte
7

XML:

<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>

Código Python:

import xml.etree.cElementTree as ET

tree = ET.parse("foo.xml")
root = tree.getroot() 
root_tag = root.tag
print(root_tag) 

for form in root.findall("./bar/type"):
    x=(form.attrib)
    z=list(x)
    for i in z:
        print(x[i])

Resultado:

foo
1
2
Ahito
fonte
6
import xml.etree.ElementTree as ET
data = '''<foo>
           <bar>
               <type foobar="1"/>
               <type foobar="2"/>
          </bar>
       </foo>'''
tree = ET.fromstring(data)
lst = tree.findall('bar/type')
for item in lst:
    print item.get('foobar')

Isso imprimirá o valor do foobaratributo.

Souvik Dey
fonte
6

xml.etree.ElementTree vs. lxml

Estes são alguns profissionais das duas bibliotecas mais usadas que eu gostaria de conhecer antes de escolher entre elas.

xml.etree.ElementTree:

  1. Na biblioteca padrão : não há necessidade de instalar nenhum módulo

lxml

  1. Escreva facilmente uma declaração XML : por exemplo, você precisa adicionarstandalone="no" ?
  2. Impressão bonita : você pode ter um bom XML recuado sem código extra.
  3. Funcionalidade Objectify : permite que você use XML como se estivesse lidando com uma hierarquia de objetos Python normal .node.
  4. sourceline permite obter facilmente a linha do elemento XML que você está usando.
  5. você também pode usar um verificador de esquema XSD embutido.
GM
fonte
5

Acho o Python xml.dom e xml.dom.minidom bastante fácil. Lembre-se de que o DOM não é bom para grandes quantidades de XML, mas se sua entrada for bastante pequena, isso funcionará bem.

EMP
fonte
2

Não há necessidade de usar uma API específica da lib, se você usar python-benedict. Apenas inicialize uma nova instância do seu XML e gerencie-a facilmente, pois é uma dictsubclasse.

A instalação é fácil: pip install python-benedict

from benedict import benedict as bdict

# data-source can be an url, a filepath or data-string (as in this example)
data_source = """
<foo>
   <bar>
      <type foobar="1"/>
      <type foobar="2"/>
   </bar>
</foo>"""

data = bdict.from_xml(data_source)
t_list = data['foo.bar'] # yes, keypath supported
for t in t_list:
   print(t['@foobar'])

Ele suporta e normaliza operações de E / S com muitos formatos: Base64, CSV, JSON, TOML, XML, YAMLe query-string.

É bem testado e de código aberto no GitHub .

Fabio Caccamo
fonte
0
#If the xml is in the form of a string as shown below then
from lxml  import etree, objectify
'''sample xml as a string with a name space {http://xmlns.abc.com}'''
message =b'<?xml version="1.0" encoding="UTF-8"?>\r\n<pa:Process xmlns:pa="http://xmlns.abc.com">\r\n\t<pa:firsttag>SAMPLE</pa:firsttag></pa:Process>\r\n'  # this is a sample xml which is a string


print('************message coversion and parsing starts*************')

message=message.decode('utf-8') 
message=message.replace('<?xml version="1.0" encoding="UTF-8"?>\r\n','') #replace is used to remove unwanted strings from the 'message'
message=message.replace('pa:Process>\r\n','pa:Process>')
print (message)

print ('******Parsing starts*************')
parser = etree.XMLParser(remove_blank_text=True) #the name space is removed here
root = etree.fromstring(message, parser) #parsing of xml happens here
print ('******Parsing completed************')


dict={}
for child in root: # parsed xml is iterated using a for loop and values are stored in a dictionary
    print(child.tag,child.text)
    print('****Derving from xml tree*****')
    if child.tag =="{http://xmlns.abc.com}firsttag":
        dict["FIRST_TAG"]=child.text
        print(dict)


### output
'''************message coversion and parsing starts*************
<pa:Process xmlns:pa="http://xmlns.abc.com">

    <pa:firsttag>SAMPLE</pa:firsttag></pa:Process>
******Parsing starts*************
******Parsing completed************
{http://xmlns.abc.com}firsttag SAMPLE
****Derving from xml tree*****
{'FIRST_TAG': 'SAMPLE'}'''
Siraj
fonte
Inclua também um contexto explicando como sua resposta resolve o problema. Respostas somente de código não são incentivadas.
Pedram Parsian 20/02
-1

Se a fonte for um arquivo xml, diga como esta amostra

<pa:Process xmlns:pa="http://sssss">
        <pa:firsttag>SAMPLE</pa:firsttag>
    </pa:Process>

você pode tentar o seguinte código

from lxml import etree, objectify
metadata = 'C:\\Users\\PROCS.xml' # this is sample xml file the contents are shown above
parser = etree.XMLParser(remove_blank_text=True) # this line removes the  name space from the xml in this sample the name space is --> http://sssss
tree = etree.parse(metadata, parser) # this line parses the xml file which is PROCS.xml
root = tree.getroot() # we get the root of xml which is process and iterate using a for loop
for elem in root.getiterator():
    if not hasattr(elem.tag, 'find'): continue  # (1)
    i = elem.tag.find('}')
    if i >= 0:
        elem.tag = elem.tag[i+1:]

dict={}  # a python dictionary is declared
for elem in tree.iter(): #iterating through the xml tree using a for loop
    if elem.tag =="firsttag": # if the tag name matches the name that is equated then the text in the tag is stored into the dictionary
        dict["FIRST_TAG"]=str(elem.text)
        print(dict)

Saída seria

{'FIRST_TAG': 'SAMPLE'}
Siraj
fonte