Manipulando XML com Python
Overview
Olá, entusiastas da programação! Neste tutorial aconchegante, vou guiar vocês através do universo do XML com Python, usando a biblioteca etree. Vamos explorar desde a leitura básica de um arquivo XML até a extração refinada de dados de um web-service. Independentemente de estar manipulando dados para um projeto pessoal ou profissional, tenho certeza de que as dicas aqui contidas serão seu pequeno tesouro no caminho do domínio do XML. Preparados para essa aventura em código? Então, ajustem seus óculos de programador, e vamos nessa!
Neste post mostro algumas formas básicas de ler (parse) e de buscar informações de um XML, que pode vir de um arquivo, uma string ou um web-service (que também é uma string, mas tudo bem). A biblioteca utiliza aqui é a etree.
Antes de começarmos a trabalhar com o XML, vamos fazer o import das bibliotecas que vamos utilizar:
import xml.etree.ElementTree as ET
import requests
Vamos ver 3 formas de converter o XML em um objeto ElementTree. A primeira forma é lendo XML de um arquivo, a segunda é lendo de uma string e a terceira é fazendo uma requisição a um web-service e lendo o resultado.
Para informação, a fonte de dados que utilizo neste exemplo é o catalogo de CDs que o site W3Schools oferece como exemplo de XML.
Lendo um XML de um Arquivo
arquivo = "catalogo_cds_w3schools.xml"
tree = ET.parse(arquivo)
Difícil, não? Pois é. o objeto armazenado na variável tree possui todas as referências para o arquivo. Se o arquivo estiver com algum problema, a biblioteca vai gerar uma exception.
Lendo um XML de uma string
tree = ET.ElementTree(ET.fromstring(xml_string))
Bom, esta forma de leitura possui menos linhas, mas merece uma explicação melhor. A função fromstring() foi desenhada para ler pedaços de um XML. Ele consegue ler um documento inteiro sem problemas, mas ele devolve o “root” (raiz) do documento, ou seja, o elemento raiz. Sendo assim, você não consegue padronizar o comportamento na hora de processar o XML. Mais para frente neste post vou apontar onde ocorreria um erro.
Então o que fazemos é converter a string com XML em um objeto root e depois converter este root em um objeto ElementTree, que vai ter o mesmo comportamento do objeto retornado pela função parse (utilizada na leitura do XML a partir de um arquivo).
Lendo um XML de um web-service
url = "https://www.w3schools.com/xml/cd_catalog.xml"
header = { 'Accept': 'application/xml' }
r = requests.get(url, headers=header)
tree = ET.ElementTree(ET.fromstring(r.content))
O código acima segue a mesma lógica da leitura de um XML a partir de uma string, mas achei importante separar as duas coisas para chamar atenção na forma como a requisição foi feita.
Perceba que defini um header especifico, que indica o tipo de resposta desejada. Sem ele, você pode receber a resposta do web-service de uma forma que não está esperando. Lembre-se: quando você especifica um tipo de resposta, você também cria a chance de que algo de errado aconteça como, por exemplo, o web-service só conseguir responder em json.
Para quem quiser, coloquei um webservice no Heroku para retornar este XML:
Dummy API webservice: https://raccoon-ninja-dummy-api.herokuapp.com/api/v1/cdcatalog?&format=xml
Forma do XML neste exemplo
<?xml version="1.0" encoding="UTF-8"?>
<CATALOG>
<CD>
<TITLE>Empire Burlesque</TITLE>
<ARTIST>Bob Dylan</ARTIST>
<COUNTRY>USA</COUNTRY>
<COMPANY>Columbia</COMPANY>
<PRICE>10.90</PRICE>
<YEAR>1985</YEAR>
</CD>
...
</CATALOG>
O nosso XML de exemplo possui esta forma:
- A raiz (root) do documento, que é a tag CATALOG;
- Lista de elementos, que são definidos pela tag <CD> …. </CD>
- Dentro de cada elemento (<CD>), existem outros elementos que caracterizam um CD.
- Este documento não possui tags com atributos.
- Um atributo é uma propriedade que fica dentro da tag. Exemplo: <CD produtora=“Março Musical”>
Ok. Agora que já sabemos a forma do XML, vamos extrair as informações dele.
Recuperando elemento raiz (root)
Para recuperar o elemento root do documento, basta utilizar a função abaixo:
root = tree.getroot()
Este objeto é do mesmo tipo que é retornado pela função fromstring() e este é o ponto que pode ocorrer um erro. Se você utilizar a função fromstring na hora de ler o XML de uma string, quando você utilizar a função getroot, vai ocorrer o seguinte erro…
AttributeError: “xml.etree.ElementTree.Element” object has no attribute “getroot”
… pois o elemento já é o root.
Parece que estou complicando, né? Mas considere a seguinte situação: Você tem uma aplicação que lê um arquivo XML e depois faz uma série de processamentos. Se a sua aplicação passar a ler o XML de uma string e utilizar a função fromstring, no ponto em que o sistema tenta recuperar o root do documento, vai ocorrer um erro. Então, se você converter o resultado da função fromstring em um ElementTree, o resto da sua aplicação não precisa mudar.
Claro que esta não é a única forma de padronizar o comportamento do sistema, mas achei importante deixar claro que isso pode acontecer.
Fazendo uma iteração geral por todos os elementos
filtro = "*"
for child in root.iter(filtro):
print(child.tag, child.text)
No código acima, a função iter vai retornar um objeto iterable, utilizando como filtro o valor da variável filtro.
Para exibir os resultados, utilizo duas propriedades:
- child**.tag**: Mostra o texto (string) que está <dentro da tag>;
- child**.text**: Mostra o conteúdo da tag;
Esta função vai retornar todos os objetos que são filhos (diretos ou não) do elemento raiz. Sendo assim, o resultado do código acima seria este:
CATALOG
CD
TITLE Empire Burlesque
ARTIST Bob Dylan
COUNTRY USA
COMPANY Columbia
PRICE 10.90
YEAR 1985
CD
TITLE Hide your heart
ARTIST Bonnie Tyler
COUNTRY UK
COMPANY CBS Records
PRICE 9.90
YEAR 1988
...
Neste caso, todos os elementos do documento foram percorridos, mas não existe muito controle de hierarquia…
Listando o titulo dos CDS
for child in root.findall("CD"):
for title in child.findall("TITLE"):
print(title.text)
No código acima, utilizo a função findall para retornar todos os objetos com a tag CD. Como este XML não utiliza atributos, cada detalhe de cada CD é outro elemento que deve ser procurado.
Sendo assim, no segundo for, utilizado a função findall para buscar todas as tags TITLE. Para cada objeto referente a tag title, utilizo print para exibir o conteúdo dela.
O código acima vai mostrar os titulos de todos os cds que existem no catalogo:
Empire Burlesque
Hide your heart
Greatest Hits
Still got the blues
Eros
One night only
Sylvias Mother
...
Existem outras formas de encontrar elementos dentro de um documento como, por exemplo, utilizando um padrão xpath, mas preferi manter este post com as informações básicas e simples. Se precisar, faço uma sequência para este post.
Espero ter ajudado!
*Update 20/12/2018: Já existe um post falando como manipular XML utilizando xpath!*