Ir al contenido principal

Aplicación de clima usando APIs

Introducción

Las APIs (Application Programming Interfaces) son interfaces que permiten a diferentes aplicaciones comunicarse entre sí. En el mundo del desarrollo, las APIs son fundamentales para acceder a servicios externos y obtener datos actualizados sin tener que crearlos nosotros mismos. Una aplicación de clima utilizando APIs es un proyecto práctico ideal para aprender cómo interactuar con servicios web externos desde Python. En este artículo, crearemos una aplicación que obtenga y muestre datos meteorológicos en tiempo real para cualquier ciudad del mundo.

Comprensión de las APIs de clima

Antes de comenzar a programar, es importante entender qué son las APIs de clima y cómo funcionaremos con ellas:

  • Las APIs de clima proporcionan datos meteorológicos actualizados en formato estructurado (generalmente JSON)
  • La mayoría requieren una clave API (API key) para autenticarse
  • Suelen tener límites de peticiones (rate limits) según el tipo de suscripción
  • Ofrecen diferentes endpoints para distintos tipos de datos (clima actual, pronóstico, histórico, etc.)

Para este proyecto utilizaremos la API de OpenWeatherMap, que ofrece un plan gratuito con un límite razonable de peticiones diarias.

Preparación del entorno

Para comenzar, necesitamos instalar la biblioteca requests, que facilita el trabajo con peticiones HTTP:

# Instalar la biblioteca requests (ejecutar en terminal)
# pip install requests

Estructura básica del proyecto

Crearemos la estructura básica de nuestra aplicación:

import requests
import json
from datetime import datetime

# Clave API (regístrate en OpenWeatherMap para obtener tu propia clave)
API_KEY = "tu_clave_api_aqui"
BASE_URL = "https://api.openweathermap.org/data/2.5/weather"

def obtener_datos_clima(ciudad, pais=None):
    """
    Obtiene los datos del clima para una ciudad específica.
    
    Args:
        ciudad (str): Nombre de la ciudad
        pais (str, opcional): Código de país de dos letras (ISO 3166)
        
    Returns:
        dict: Datos del clima si la petición es exitosa, None en caso contrario
    """
    # Construir la consulta de búsqueda
    query = ciudad
    if pais:
        query = f"{ciudad},{pais}"
        
    # Parámetros de la petición
    params = {
        "q": query,
        "appid": API_KEY,
        "units": "metric",  # Usar unidades métricas (Celsius)
        "lang": "es"        # Respuestas en español
    }
    
    try:
        # Realizar la petición GET
        respuesta = requests.get(BASE_URL, params=params)
        
        # Verificar si la petición fue exitosa (código 200)
        if respuesta.status_code == 200:
            return respuesta.json()
        else:
            print(f"Error: {respuesta.status_code} - {respuesta.json().get('message', 'Error desconocido')}")
            return None
            
    except requests.exceptions.RequestException as e:
        print(f"Error de conexión: {e}")
        return None

def main():
    print("APLICACIÓN DE CLIMA")
    print("==================")
    
    ciudad = input("Introduce el nombre de la ciudad: ")
    pais = input("Introduce el código del país (opcional, pulsa Enter para omitir): ")
    
    if not pais:
        pais = None
    
    # Obtener datos del clima
    datos_clima = obtener_datos_clima(ciudad, pais)
    
    if datos_clima:
        mostrar_datos_clima(datos_clima)

def mostrar_datos_clima(datos):
    # Implementaremos esto más adelante
    pass

if __name__ == "__main__":
    main()

Procesamiento y visualización de datos

Ahora implementemos la función para mostrar los datos del clima de forma clara y ordenada:

def mostrar_datos_clima(datos):
    """
    Muestra los datos del clima de forma legible.
    
    Args:
        datos (dict): Datos del clima obtenidos de la API
    """
    # Extraer datos relevantes
    nombre_ciudad = datos["name"]
    pais = datos["sys"]["country"]
    
    # Temperatura y sensación térmica
    temp = datos["main"]["temp"]
    temp_min = datos["main"]["temp_min"]
    temp_max = datos["main"]["temp_max"]
    sensacion = datos["main"]["feels_like"]
    
    # Humedad y presión
    humedad = datos["main"]["humidity"]
    presion = datos["main"]["pressure"]
    
    # Viento
    velocidad_viento = datos["wind"]["speed"]
    direccion_viento = datos["wind"]["deg"]
    
    # Descripción del clima
    descripcion = datos["weather"][0]["description"]
    
    # Hora de la última actualización (convertir timestamp a formato legible)
    timestamp = datos["dt"]
    hora_actualizacion = datetime.fromtimestamp(timestamp).strftime("%H:%M:%S")
    
    # Amanecer y atardecer
    amanecer = datetime.fromtimestamp(datos["sys"]["sunrise"]).strftime("%H:%M:%S")
    atardecer = datetime.fromtimestamp(datos["sys"]["sunset"]).strftime("%H:%M:%S")
    
    # Mostrar la información
    print("\nDATOS METEOROLÓGICOS")
    print("===================")
    print(f"Ciudad: {nombre_ciudad}, {pais}")
    print(f"Condición: {descripcion.capitalize()}")
    print(f"Temperatura: {temp}°C (mín: {temp_min}°C, máx: {temp_max}°C)")
    print(f"Sensación térmica: {sensacion}°C")
    print(f"Humedad: {humedad}%")
    print(f"Presión atmosférica: {presion} hPa")
    print(f"Viento: {velocidad_viento} m/s, dirección: {direccion_viento}°")
    print(f"Amanecer: {amanecer}")
    print(f"Atardecer: {atardecer}")
    print(f"Última actualización: {hora_actualizacion}")

Añadiendo pronóstico a varios días

Vamos a mejorar nuestra aplicación añadiendo la capacidad de mostrar el pronóstico para los próximos días:

def obtener_pronostico(ciudad, pais=None, dias=5):
    """
    Obtiene el pronóstico del tiempo para los próximos días.
    
    Args:
        ciudad (str): Nombre de la ciudad
        pais (str, opcional): Código de país de dos letras
        dias (int): Número de días para el pronóstico (máx. 5 para API gratuita)
        
    Returns:
        dict: Datos del pronóstico si la petición es exitosa, None en caso contrario
    """
    # URL para el pronóstico
    forecast_url = "https://api.openweathermap.org/data/2.5/forecast"
    
    # Construir la consulta de búsqueda
    query = ciudad
    if pais:
        query = f"{ciudad},{pais}"
        
    # Parámetros de la petición
    params = {
        "q": query,
        "appid": API_KEY,
        "units": "metric",
        "lang": "es",
        "cnt": dias * 8  # 8 mediciones por día (cada 3 horas)
    }
    
    try:
        respuesta = requests.get(forecast_url, params=params)
        
        if respuesta.status_code == 200:
            return respuesta.json()
        else:
            print(f"Error: {respuesta.status_code} - {respuesta.json().get('message', 'Error desconocido')}")
            return None
            
    except requests.exceptions.RequestException as e:
        print(f"Error de conexión: {e}")
        return None

def mostrar_pronostico(datos_pronostico):
    """
    Muestra el pronóstico del tiempo para los próximos días.
    
    Args:
        datos_pronostico (dict): Datos del pronóstico obtenidos de la API
    """
    print("\nPRONÓSTICO PARA LOS PRÓXIMOS DÍAS")
    print("================================")
    
    # Agrupar pronósticos por día
    pronosticos_por_dia = {}
    
    for pronostico in datos_pronostico["list"]:
        # Obtener la fecha (sin la hora)
        fecha = datetime.fromtimestamp(pronostico["dt"]).strftime("%Y-%m-%d")
        
        # Añadir este pronóstico al día correspondiente
        if fecha not in pronosticos_por_dia:
            pronosticos_por_dia[fecha] = []
            
        pronosticos_por_dia[fecha].append(pronostico)
    
    # Mostrar pronóstico resumido por día
    for fecha, pronosticos in pronosticos_por_dia.items():
        # Convertir fecha a formato más legible
        fecha_obj = datetime.strptime(fecha, "%Y-%m-%d")
        fecha_legible = fecha_obj.strftime("%A, %d de %B").capitalize()
        
        # Calcular promedios y encontrar valores min/max
        temp_min = min(p["main"]["temp_min"] for p in pronosticos)
        temp_max = max(p["main"]["temp_max"] for p in pronosticos)
        
        # Obtener la descripción más común del día
        descripciones = [p["weather"][0]["description"] for p in pronosticos]
        descripcion_comun = max(set(descripciones), key=descripciones.count)
        
        print(f"\n{fecha_legible}:")
        print(f"  Temperatura: {temp_min:.1f}°C a {temp_max:.1f}°C")
        print(f"  Condición predominante: {descripcion_comun.capitalize()}")

Manejo de errores y validación

Para hacer nuestra aplicación más robusta, añadamos un mejor manejo de errores:

def validar_ciudad(ciudad):
    """
    Valida que la ciudad no esté vacía.
    
    Args:
        ciudad (str): Nombre de la ciudad
        
    Returns:
        bool: True si la ciudad es válida, False en caso contrario
    """
    if not ciudad.strip():
        print("Error: El nombre de la ciudad no puede estar vacío.")
        return False
    return True

def validar_respuesta_api(datos):
    """
    Valida que la respuesta de la API contenga los datos esperados.
    
    Args:
        datos (dict): Datos recibidos de la API
        
    Returns:
        bool: True si los datos son válidos, False en caso contrario
    """
    if not datos:
        return False
        
    campos_requeridos = ["name", "main", "weather", "wind", "sys"]
    for campo in campos_requeridos:
        if campo not in datos:
            print(f"Error: Datos incompletos, falta el campo '{campo}'.")
            return False
    
    return True

Integración final

Ahora integremos todo en nuestro programa principal:

def main():
    print("APLICACIÓN DE CLIMA CON API")
    print("==========================")
    
    while True:
        ciudad = input("\nIntroduce el nombre de la ciudad: ")
        
        if not validar_ciudad(ciudad):
            continue
            
        pais = input("Introduce el código del país (opcional, pulsa Enter para omitir): ")
        
        if not pais:
            pais = None
        
        # Obtener datos del clima actual
        print("\nObteniendo datos del clima actual...")
        datos_clima = obtener_datos_clima(ciudad, pais)
        
        if validar_respuesta_api(datos_clima):
            mostrar_datos_clima(datos_clima)
            
            # Preguntar si quiere ver el pronóstico
            ver_pronostico = input("\n¿Desea ver el pronóstico para los próximos días? (s/n): ")
            
            if ver_pronostico.lower() == 's':
                print("\nObteniendo pronóstico...")
                datos_pronostico = obtener_pronostico(ciudad, pais)
                
                if datos_pronostico:
                    mostrar_pronostico(datos_pronostico)
        
        # Preguntar si quiere consultar otra ciudad
        otra_consulta = input("\n¿Desea consultar el clima de otra ciudad? (s/n): ")
        if otra_consulta.lower() != 's':
            print("\n¡Gracias por usar la aplicación de clima!")
            break

if __name__ == "__main__":
    main()

Ejemplo de ejecución

Veamos un ejemplo de cómo funcionaría nuestra aplicación:

APLICACIÓN DE CLIMA CON API
==========================

Introduce el nombre de la ciudad: Madrid
Introduce el código del país (opcional, pulsa Enter para omitir): ES

Obteniendo datos del clima actual...

DATOS METEOROLÓGICOS
===================
Ciudad: Madrid, ES
Condición: Cielo despejado
Temperatura: 22.5°C (mín: 19.8°C, máx: 24.6°C)
Sensación térmica: 22.1°C
Humedad: 45%
Presión atmosférica: 1018 hPa
Viento: 3.6 m/s, dirección: 230°
Amanecer: 07:23:44
Atardecer: 20:40:01
Última actualización: 15:30:24

¿Desea ver el pronóstico para los próximos días? (s/n): s

Obteniendo pronóstico...

PRONÓSTICO PARA LOS PRÓXIMOS DÍAS
================================

Miércoles, 23 de abril:
  Temperatura: 19.2°C a 24.6°C
  Condición predominante: Cielo despejado

Jueves, 24 de abril:
  Temperatura: 16.8°C a 26.3°C
  Condición predominante: Algunas nubes

Viernes, 25 de abril:
  Temperatura: 17.5°C a 27.1°C
  Condición predominante: Parcialmente nublado

Sábado, 26 de abril:
  Temperatura: 18.3°C a 25.4°C
  Condición predominante: Parcialmente nublado

Domingo, 27 de abril:
  Temperatura: 17.9°C a 24.8°C
  Condición predominante: Lluvia ligera

¿Desea consultar el clima de otra ciudad? (s/n): n

¡Gracias por usar la aplicación de clima!

Mejoras posibles

Nuestra aplicación de clima funciona correctamente, pero podríamos implementar varias mejoras:

  1. Interfaz gráfica: Convertir la aplicación a GUI usando bibliotecas como Tkinter o PyQt
  2. Almacenamiento de ciudades favoritas: Guardar las ciudades consultadas frecuentemente
  3. Visualización de datos: Incorporar gráficos para mostrar la evolución de temperaturas
  4. Alertas meteorológicas: Notificar sobre condiciones extremas o peligrosas
  5. Geolocalización: Detectar automáticamente la ubicación del usuario
  6. Exportación de datos: Permitir guardar los datos en formatos como CSV o PDF

Manejo seguro de claves API

Es importante manejar de forma segura las claves API:

# Alternativa más segura para manejar la clave API
import os
from dotenv import load_dotenv  # Necesita: pip install python-dotenv

# Cargar variables de entorno desde archivo .env
load_dotenv()

# Obtener la clave API desde variable de entorno
API_KEY = os.getenv("OPENWEATHERMAP_API_KEY")

if not API_KEY:
    print("Error: No se ha configurado la clave API.")
    print("Crea un archivo .env con: OPENWEATHERMAP_API_KEY=tu_clave")
    exit(1)

Resumen

En este artículo hemos desarrollado una aplicación de clima completa que utiliza APIs para obtener datos meteorológicos en tiempo real. Hemos aprendido a realizar peticiones HTTP con la biblioteca requests, procesar respuestas JSON, manejar errores y presentar información de forma legible para el usuario. Esta aplicación nos ha permitido aplicar conceptos importantes como la comunicación con servicios web externos, el manejo de datos estructurados y la interacción con el usuario a través de una interfaz por consola. Las APIs son herramientas fundamentales en el desarrollo moderno, y este proyecto nos proporciona una base sólida para seguir explorando su potencial en futuros desarrollos de aplicaciones más complejas.