Todo projeto que deseja raspar dados da web se resume ao seguinte loop:
- Fazer uma requisição para uma URL;
- Processar a resposta (HTML, XML ou JSON);
- Extrair os dados;
- Deduzir as próximas URLs a visitar;
- Repetir o loop.
A parte mais difícil aqui é processar o HTML. É um processo todo delicado, o HTML é cheio de detalhes para tolerar tags que não fecham, símbolos largados nos lugares errados e por aí vai. Por isso é difícil ter um parser (processador) de HTML nas bibliotecas-padrão de qualquer linguagem, seja Python, Ruby, Javascript ou PHP. Já XML e JSON são formatos muito mais estritos e tem parsers nativos em qualquer linguagem.
Vamos ver como pegar os gastos da Copa do Mundo 2014, expostos em XML. Alguns browsers vão exibir o XML como uma árvore, facilitando a visualização da estrutura – meu browser favorito, o Firefox, faz isso.
Nesse XML você pode ver um elemento maior, o collection
, com muitos elementos
copa:empreendimento
dentro. Esse prefixo copa:
corresponde a um
namespace, um recurso do XML para poder misturar elementos de vocabulários
distintos. É importante prestar atenção nisso para podermos informar o nosso
parser de XML.
Para começar o loop, vamos carregar a URL e popular uma árvore de elementos – uma abstração do Python para podermos manipular mais facilmente esses dados:
from xml.etree import ElementTree
from urllib.request import urlopen
data_url = "http://www.portaltransparencia.gov.br/copa2014/api/rest/empreendimento"
with urlopen(data_url) as datafile:
data = ElementTree.parse(datafile)
Repare como carregar uma URL no Python 3 tem uma sintaxe confortável, idêntica
à sintaxe de abrir arquivos. Agora pra seguir o loop, vamos extrair o que nos
interessa: o gasto (valorTotalPrevisto
) de cada empreendimento iniciado ou
concluído (cujo andamento
não esteja no estado 1
, Não iniciado
).
spending = [
float(element.find('./valorTotalPrevisto').text)
for element
in data.iterfind('.//copa:empreendimento', namespaces={'copa': data_url[:46]})
if element.find('./andamento/id').text != '1']
Pegar elementos de um ElementTree
é fácil usando o método iterfind
(retorna
um iterável, pra usar com for) ou findall
(retorna uma lista propriamente
dita). Já pegar o conteúdo de um elemento exige apenas chamar o atributo
.text
. Fácil, não?
Isso daria certo se os dados fossem consistentes, mas… outro porém! O XML é
estrito – as tags fecham, os símbolos estão no lugar certo, está tudo certo,
mas os dados em si não são consistentes. Nem todo elemento
copa:empreendimento
tem um elemento valorTotalPrevisto
dentro. E agora?
É simples: vamos encapsular o processamento desse valor em um método simples, que retorna zero quando não existe valor total previsto (pra facilitar a soma, depois).
def get_cost(element):
cost = element.find('./valorTotalPrevisto')
return 0 if (cost is None) else float(cost.text)
Agora basta chamar o get_cost
na nossa compreensão de lista:
spending = [
get_cost(element)
for element
in data.iterfind('.//copa:empreendimento', namespaces={'copa': data_url[:46]})
if element.find('./andamento/id').text != '1']
E aí podemos finalmente somar todos os valores encontrados e imprimir usando o
poderoso método format
do Python
(estude!).
print('Foram gastos {total:.2f} dinheiros do governo brasileiro'.format(
total=sum(spending)))
Bônus: Uma versão mais idiomática (PYTHONICA) do código está disponível no meu Gist. Fique à vontade para contribuir, comentar, melhorar, etc :)
Agradecimentos ao Fernando Masanori que começou a brincadeira com esses dados!