O Python é uma linguagem muito versátil. Nesse post, iremos usá-lo para acessar o site do Instituto Nacional de Pesquisas Espaciais (INPE). Nosso objetivo é criar um pequeno Python app para previsão do tempo que aceita uma cidade como entrada e retorna a previsão para os próximos dias.
Esse aplicativo será usado num post futuro para a criação de um chatbot baseado em regras.
Requerimentos
Para instalações com pip:
pip install lxml
pip install requests
pip install Unidecode
Para instalações com o Anaconda:
conda install -c anaconda lxml
conda install -c anaconda requests
conda install -c conda-forge unidecode
Módulo que será usado:
- pprint – para imprimir os resultados obtidos de forma mais legível
Objetivos - criar um Python app para previsão do tempo
O INPE disponibiliza dados com previsões do tempo para todo o Brasil em XML. A requisição desses dados é feita via uma URL que, na maioria das vezes, deve ter o código da localidade desejada inserida em seu conteúdo. Portanto, temos dois objetivos:
- Obter o código numérico correspondente para cada localidade informada por um usuário do nosso aplicativo.
- Usar esse código para obter a previsão do tempo para os próximos dias.
Descrição geral dos procedimentos
Iremos criar duas funções para acessar os dados do INPE.
A primeira função tem como argumento o nome de uma cidade fornecida por um usuário do programa. Esse nome precisa ser inserido dinamicamente na URL do INPE.
Se o acesso ao site do INPE for bem-sucedido, nossa primeira função retornará o código numérico da cidade. Ele será o argumento de uma segunda função.
O objetivo da segunda função é acessar outra página do INPE para obter a previsão do tempo.
Importação das bibliotecas e módulo
Para começar, vamos fazer as importações necessárias.
from lxml import etree
import pprint
import requests
from unidecode import unidecode
Criação da primeira função
Nossa primeira função (busca_codigo) tem como argumento o nome de uma cidade do território brasileiro. Ele será usado para completar dinamicamente a URL do INPE que precisamos acessar.
É preciso converter todas as letras do nome para minúsculas com o comando lower (linha 2). Adicionalmente, é necessário remover qualquer acentuação que o nome fornecido possa ter com o comando unidecode (linha 3). Em seguida, usamos o comando requests.get() (linha 5) para acessar a URL como no código abaixo:
def busca_codigo(cidade):
cidade = cidade.lower()
cidade = unidecode(cidade)
url = f"http://servicos.cptec.inpe.br/XML/listaCidades?city={cidade}"
response = requests.get(url)
Para verificar se o acesso foi bem-sucedido, é fundamental confirmar o status da resposta HTTP como 200 (linha 6 no código abaixo).
Status HTTP retirados daqui.
Nessa etapa, também é importante criar uma mensagem para informar ao usuário do app quando houver uma falha no acesso (linhas 8 e 9). Veja o código abaixo.
def busca_codigo(cidade):
cidade = cidade.lower()
cidade = unidecode(cidade)
url = f"http://servicos.cptec.inpe.br/XML/listaCidades?city={cidade}"
response = requests.get(url)
if response.status_code == 200:
print('sucesso')
else:
return "Houve um problema com sua solicitação. Por favor, confira e digite novamente."
Vamos testar se nossa função está correta até aqui verificando o acesso para a cidade de São Paulo. Digite o código abaixo fora da função, salve e execute o arquivo.
cidade = 'São Paulo'
nome_id = busca_codigo(cidade)
O resultado obtido deve ser a palavra sucesso escrita diretamente no terminal.
Obtendo o arquivo XML
Em seguida, é preciso pegar o conteúdo da página do INPE em XML (linha 4 abaixo).
def busca_codigo(cidade):
...
if response.status_code == 200:
xml_data = response.content
print(xml_data)
...
Executando esse código, nós obtemos uma resposta XML como a mostrada abaixo. Para extrair o conteúdo necessário (id) do arquivo XML, utilizaremos a biblioteca lxml.
b"S\xe3o Paulo SP 244 S\xe3o Paulo das Miss\xf5es RS 5019 S\xe3o Paulo de Oliven\xe7a AM 5020 S\xe3o Paulo do Potengi RN 5021 "
A funcionalidade etree da biblioteca lxml será usada para converter o conteúdo XML num elemento de mais fácil inspeção (linha 5). Essa conversão possibilita usar os termos presentes no XML (por exemplo, nome, id) para buscar diretamente o texto correspondente de cada um. Digite a linha 5 abaixo em seu código:
def busca_codigo(cidade):
...
if response.status_code == 200:
xml_data = response.content
texto = etree.fromstring(xml_data)
...
Extração do código numérico
Por fim, é preciso garantir que o conteúdo da resposta obtida tem o código numérico desejado. Em caso de erro, a URL do INPE retorna None. Portanto, é fundamental confirmar que nosso conteúdo é mesmo o código da localidade usando uma estrutura condicional (linhas 4-8 abaixo). Se o resultado for correto (linhas 6-8 abaixo), a função extrairá o id utilizando o elemento texto criado com a funcionalidade etree (linha 7).
def busca_codigo(cidade):
...
texto = etree.fromstring(xml_data)
if texto.find('cidade') == None:
return "Houve um problema com sua solicitação. Por favor, confira e digite novamente."
else:
nome_id = int(texto.find('cidade').find('id').text)
return nome_id
...
Testando a primeira função
Nossa primeira função está terminada. Vamos executar o código e testar uma nova cidade. O código da função completa com seu chamado é mostrado abaixo.
def busca_codigo(cidade):
cidade = cidade.lower()
cidade = unidecode(cidade)
url = f"http://servicos.cptec.inpe.br/XML/listaCidades?city={cidade}"
response = requests.get(url)
if response.status_code == 200:
xml_data = response.content
texto = etree.fromstring(xml_data)
if texto.find('cidade') == None:
return 'Houve um problema com sua solicitação. Por favor, confira e digite novamente.'
else:
nome_id = int(texto.find('cidade').find('id').text)
return nome_id
else:
return 'Houve um problema com sua solicitação. Por favor, confira e digite novamente.'
cidade = 'Manaus'
nome_id = busca_codigo(cidade)
print(nome_id)
Se seu código estiver correto, você deverá ver o número 234 impresso no terminal.
Verificando o retorno correto
A segunda função (busca_clima) que será criada tem como argumento o código numérico (nome_id) obtido anteriormente.
Porém, antes de chamá-la, é necessário garantir novamente que o código numérico foi retornado com sucesso. Para isso, a segunda função precisa ser chamada apenas numa estrutura condicional que verifica se o código numérico é do tipo int. Se essa condicional falhar, como no exemplo abaixo (linha 4), o código não foi localizado e o usuário da aplicação receberá uma mensagem indicando o erro.
def busca_clima(nome_id):
pass
cidade = 'Rio de Janeio'
nome_id = busca_codigo(cidade)
if type(nome_id)==int:
busca_clima(nome_id)
else:
print(nome_id)
Resultado em caso de erro no nome da cidade.
Houve um problema com sua solicitação. Por favor, confira e digite novamente.
A segunda função
A estrutura da segunda função é bastante parecida à função anterior. Ela inicia com o acesso à URL da previsão do tempo usando o código numérico (nome_id) como argumento dinâmico. É preciso verificar com uma estrutura condicional se o status do acesso é 200. Em caso de erro, ela retornará uma mensagem acusando a falha. Digite e execute o código abaixo. Sua execução deve escrever a palavra sucesso no terminal.
def busca_clima(nome_id):
url = f"http://servicos.cptec.inpe.br/XML/cidade/7dias/{nome_id}/previsao.xml"
response = requests.get(url, stream=True)
if response.status_code == 200:
print("sucesso")
else:
print('Houve um problema com a sua solicitação. Por favor, confira e digite novamente.')
cidade = 'Rio de Janeiro'
nome_id = busca_codigo(cidade)
if type(nome_id)==int:
busca_clima(nome_id)
else:
print(nome_id)
Na sequência, o conteúdo da página é obtido e convertido em elemento para inspeção pela funcionalidade etree.
def busca_clima(nome_id):
...
if response.status_code == 200:
xml_data = response.content
root = etree.fromstring(xml_data)
...
Abaixo, veja um exemplo de arquivo XML que o INPE retorna com a previsão completa para a localidade buscada. É esse tipo de arquivo que a funcionalidade etree facilita a inspeção. Ela é capaz de extrair diretamente o texto de cada elemento do arquivo.
b"S\xe3o Paulo SP 2023-10-08 2023-10-09 c 26 19 12.0 2023-10-10 pn 21 16 12.0 2023-10-11 pn 32 16 12.0 2023-10-12 pn 32 21 12.0 2023-10-13 pn 25 17 12.0 2023-10-14 pn 20 15 12.0 "
Os dados do INPE
O INPE informa que, para cada localidade, fornece a previsão para os próximos 7 dias, mas nossos arquivos retornaram dados para apenas 6 dias.
Para cada dia, a previsão do INPE tem um código descritivo do clima (por exemplo, a letra c significa chuva), a temperatura mínima e a máxima e o índice UV.
Iremos utilizar um loop para salvar todos esses dados num dicionário. Adicionalmente, para tornar nosso arquivo compreensível para um usuário, nossa função irá converter o código da previsão do INPE (tempo na função busca_clima) usando um dicionário com a descrição correspondente. O dicionário é mostrado abaixo. Para usá-lo, é necessário copiá-lo e importá-lo no arquivo do seu código (adicione no início do código a linha: from clima_codigos import clima_dic).
clima_dic = {
"ec": "Encoberto com Chuvas Isoladas",
"ci": "Chuvas Isoladas",
"c": "Chuva",
"in": "Instável",
"pp": "Possilidade de Pancadas de Chuva",
"cm": "Chuva pela Manhã",
"cn": "Chuva a Noite",
"pt": "Pancadas de Chuva a Tarde",
"pm": "Pancadas de Chuva pela Manhã",
"np": "Nublado e Pancadas de Chuva",
"pc": "Pancadas de Chuva",
"pn": "Parcialmente Nublado",
"cv": "Chuvisco",
"ch": "Chuvoso",
"t": "Tempestade",
"ps": "Predomínio de Sol",
"e": "Encoberto",
"n": "Nublado",
"cl": "Céu Claro",
"nv": "Nevoeiro",
"g": "Geada",
"ne": "Neve",
"nd": "Não Definido",
"pnt": "Pancadas de Chuva a Noite",
"psc": "Possibilidade de Chuva",
"pcm": "Possibilidade de Chuva pela Manhã",
"pct": "Possibilidade de Chuva a Tarde",
"pcn": "Possibilidade de Chuva a Noite",
"npt": "Nublado com Pancadas a Tarde",
"npn": "Nublado com Pancadas a Noite",
"ncn": "Nublado com Poss. de Chuva a Noite",
"nct": "Nublado com Poss. de Chuva a Tarde",
"ncm": "Nublado com Poss. de Chuva pela Manhã",
"npm": "Nublado com Pancadas pela Manhã",
"npp": "Nublado com Possibilidade de Chuva",
"vn": "Variação de Nebulosidade",
"ct": "Chuva a Tarde",
"ppn": "Poss. de Panc. de Chuva a Noite",
"ppt": "Poss. de Panc. de Chuva a Tarde",
"ppm": "Poss. de Panc. de Chuva pela Manhã"}
Para salvar todas as informações do INPE de maneira organizada, utilizaremos um contador para enumerar cada dia da previsão com seus dados correspondentes (linha 7 e linha 14). Após o término do loop, o dicionário é impresso com o auxílio do comando pprint para torná-lo mais legível.
def busca_clima(nome_id):
...
root = etree.fromstring(xml_data)
dados = {}
contador = 0
for previsao in root.findall('previsao'):
contador += 1
dia = (previsao.find('dia').text)
tempo = (previsao.find('tempo').text)
prev = clima_dic[tempo]
Tmax = (previsao.find('maxima').text)
Tmin = (previsao.find('minima').text)
iuv = (previsao.find('iuv').text)
dados['dia '+str(contador)] = (dia, prev, Tmax, Tmin, iuv)
pprint.pprint(dados)
...
Pronto! Agora é só salvar tudo e testar com várias cidades diferentes. O código completo da segunda função é mostrado abaixo.
def busca_clima(nome_id):
url = f"http://servicos.cptec.inpe.br/XML/cidade/7dias/{nome_id}/previsao.xml"
response = requests.get(url, stream=True)
if response.status_code == 200:
xml_data = response.content
root = etree.fromstring(xml_data)
dados = {}
contador = 0
for previsao in root.findall('previsao'):
contador += 1
dia = (previsao.find('dia').text)
tempo = (previsao.find('tempo').text)
prev = clima_dic[tempo]
Tmax = (previsao.find('maxima').text)
Tmin = (previsao.find('minima').text)
iuv = (previsao.find('iuv').text)
dados['dia '+str(contador)] = (dia, prev, Tmax, Tmin, iuv)
pprint.pprint(dados)
else:
print('Houve um problema com a sua solicitação. Por favor, confira e digite novamente.')
cidade = 'Rio de Janeiro'
nome_id = busca_codigo(cidade)
if type(nome_id)==int:
busca_clima(nome_id)
else:
print(nome_id)
Veja um exemplo de previsão obtida para o Rio de janeiro.
{'dia 1': ('2023-10-08', 'Chuva', '29', '24', '11.0'),
'dia 2': ('2023-10-09', 'Parcialmente Nublado', '29', '23', '11.0'),
'dia 3': ('2023-10-10', 'Parcialmente Nublado', '27', '22', '12.0'),
'dia 4': ('2023-10-11', 'Parcialmente Nublado', '30', '22', '12.0'),
'dia 5': ('2023-10-12', 'Parcialmente Nublado', '32', '23', '11.0'),
'dia 6': ('2023-10-13', 'Parcialmente Nublado', '24', '22', '11.0')}
Conclusão
Nesse post, usamos o Python para criar um pequeno aplicativo para previsão do tempo com dados obtidos dinamicamente do site do INPE. Para quem tiver interesse, o INPE tem outros arquivos XML disponíveis e esse código pode ser facilmente modificado para retornar outros dados.