# 打造自己的天气数据可视化工具:从API调用到图表展示


背景介绍

在物联网与智能应用的浪潮下,实时天气数据的获取与可视化成为许多项目的基础需求。开发一个天气工具不仅能帮我们快速了解天气,更能锻炼网络请求、数据解析、可视化的综合编程能力。本文将带你实现一个基于OpenWeatherMap API的天气工具,涵盖从API调用到图表展示的完整流程。

思路分析

要实现这个工具,需解决四个核心问题:
1. 网络请求:向OpenWeatherMap API发送HTTP请求,传递城市和API密钥。
2. 数据解析:从JSON响应中提取温度、湿度等关键数据。
3. 数据可视化:用Matplotlib绘制柱状图(对比温度/湿度/气压)和雷达图(多维度展示)。
4. 异常处理:捕获网络错误、API错误码(如城市不存在),提供友好提示。

代码实现

1. 依赖安装

首先安装所需Python库:

pip install requests matplotlib

2. 核心代码:天气工具链

(1)天气数据获取函数

import requests
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec

def get_weather(city, api_key, country_code=''):
    """
    调用OpenWeatherMap API获取天气数据
    :param city: 城市名称(英文),如"Beijing"
    :param api_key: OpenWeatherMap API密钥
    :param country_code: 国家代码(如"CN"),可选
    :return: 天气数据(JSON格式)或None(出错时)
    """
    # 拼接城市+国家代码(如"Beijing,CN")
    if country_code:
        city = f"{city},{country_code}"
    base_url = "http://api.openweathermap.org/data/2.5/weather"
    params = {
        'q': city,
        'appid': api_key,
        'units': 'metric'  # 温度(°C)、风速(m/s)、气压(hPa)
    }
    try:
        response = requests.get(base_url, params=params, timeout=10)
        response.raise_for_status()  # 触发HTTP错误(如404)
        return response.json()
    except requests.exceptions.ConnectionError:
        print("❌ 错误:网络连接失败,请检查网络或API服务状态。")
        return None
    except requests.exceptions.Timeout:
        print("⌛ 错误:请求超时,请稍后重试。")
        return None
    except requests.exceptions.HTTPError as e:
        if response.status_code == 404:
            print(f"🔍 错误:城市「{city}」未找到,请检查名称或国家代码(如Beijing,CN)。")
        else:
            print(f"API错误(状态码{response.status_code}):{e}")
        return None
    except Exception as e:
        print(f"⚠️ 未知错误:{str(e)}")
        return None

(2)天气数据可视化函数

def plot_weather(data):
    """
    可视化天气数据:柱状图(温度/湿度/气压)+ 雷达图(多维度对比)
    :param data: 解析后的天气数据(字典格式)
    """
    # 提取核心数据
    city = data['name']
    temp = data['main']['temp']       # 温度(°C)
    humidity = data['main']['humidity']  # 湿度(%)
    pressure = data['main']['pressure']  # 气压(hPa)
    wind_speed = data['wind']['speed']   # 风速(m/s)
    weather_desc = data['weather'][0]['description']  # 天气描述

    # -------------- 柱状图:温度、湿度、气压 --------------
    bar_labels = ['温度 (°C)', '湿度 (%)', '气压 (hPa)']
    bar_values = [temp, humidity, pressure]

    # -------------- 雷达图:多维度归一化展示 --------------
    radar_labels = ['温度', '湿度', '气压', '风速']
    radar_values = [temp, humidity, pressure, wind_speed]
    max_radar = max(radar_values)  # 归一化基准
    radar_values = [v / max_radar for v in radar_values]  # 缩放到0-1
    radar_values.append(radar_values[0])  # 闭合雷达图
    radar_angles = np.linspace(0, 2*np.pi, len(radar_labels), endpoint=True).tolist()
    radar_angles.append(radar_angles[0])  # 闭合角度

    # -------------- 绘制图表(GridSpec布局) --------------
    fig = plt.figure(figsize=(12, 6))
    gs = GridSpec(1, 2, width_ratios=[1, 1])  # 左右子图宽度1:1

    # 子图1:柱状图
    ax_bar = fig.add_subplot(gs[0, 0])
    x = np.arange(len(bar_labels))
    bars = ax_bar.bar(x, bar_values, color=['#FF6B6B', '#4ECDC4', '#FFD166'])
    ax_bar.set_xticks(x)
    ax_bar.set_xticklabels(bar_labels, rotation=15, ha='right')
    ax_bar.set_title(f'{city} - 天气数据对比(柱状图)', fontsize=12)
    ax_bar.set_ylabel('数值')
    # 柱子上标注数值
    for bar, val in zip(bars, bar_values):
        ax_bar.text(
            bar.get_x() + bar.get_width()/2, 
            bar.get_height() + 0.5, 
            f'{val:.1f}', 
            ha='center', 
            va='bottom'
        )

    # 子图2:雷达图
    ax_radar = fig.add_subplot(gs[0, 1], polar=True)
    ax_radar.plot(radar_angles, radar_values, 'o-', linewidth=2, color='#2A9D8F')
    ax_radar.fill(radar_angles, radar_values, alpha=0.2, color='#2A9D8F')
    ax_radar.set_thetagrids(
        np.degrees(radar_angles[:-1]),  # 角度转度数(排除最后一个闭合点)
        radar_labels
    )
    ax_radar.set_title(f'{city} - 天气数据对比(雷达图)', fontsize=12, pad=20)
    ax_radar.set_ylim(0, 1)  # 归一化后范围0-1

    # 底部显示天气描述
    plt.figtext(0.5, 0.01, f'天气状况:{weather_desc}', ha='center', fontsize=10)
    plt.tight_layout()
    plt.show()

(3)主程序:用户交互与流程控制

def main():
    """主程序:处理用户输入,调用工具链"""
    print("🌤️ 天气数据获取与可视化工具")
    print("-------------------------")
    # 1. 获取API密钥(用户需提前注册OpenWeatherMap)
    api_key = input("请输入你的OpenWeatherMap API密钥:").strip()
    if not api_key:
        print("错误:API密钥不能为空!")
        return

    # 2. 获取城市名称(格式:城市名,国家代码,如Beijing,CN)
    city = input("请输入城市名称(英文,格式:城市名,国家代码,如Beijing,CN):").strip()
    if not city:
        print("错误:城市名称不能为空!")
        return

    # 3. 调用API获取天气数据
    weather_data = get_weather(city, api_key)
    if not weather_data:
        print("程序终止:天气数据获取失败。")
        return

    # 4. 打印文本信息
    print("\n📊 天气数据(文本版):")
    print(f"城市:{weather_data['name']}")
    print(f"温度:{weather_data['main']['temp']}°C")
    print(f"湿度:{weather_data['main']['humidity']}%")
    print(f"气压:{weather_data['main']['pressure']}hPa")
    print(f"风速:{weather_data['wind']['speed']}m/s")
    print(f"天气状况:{weather_data['weather'][0]['description']}")

    # 5. 可视化数据
    plot_weather(weather_data)

if __name__ == "__main__":
    main()

代码解析与扩展

1. API调用的鲁棒性

get_weather通过try-except捕获了网络错误(连接失败、超时)、HTTP错误(如404城市不存在)和未知异常,确保程序在异常下仍能给出友好提示。

2. 数据可视化的巧思

  • 柱状图:直观对比温度、湿度、气压的数值差异,柱子上标注具体数值。
  • 雷达图:通过归一化将多维度数据(温度、湿度、气压、风速)映射到同一尺度,展示数据的相对关系。
  • 布局优化:使用GridSpec实现左右分栏,底部添加天气描述,提升可读性。

3. 扩展方向

  • 中文城市支持:通过pinyin库转换中文为拼音,或调用支持中文的API(如和风天气)。
  • 历史趋势:扩展API为onecall接口,获取多日预报,绘制折线图展示趋势。
  • 图形界面:用Tkinter/PyQt封装为桌面应用,提升交互体验。

运行测试

  1. 安装依赖:pip install requests matplotlib
  2. 注册OpenWeatherMap,获取API密钥。
  3. 运行程序:python weather_tool.py,输入密钥和城市(如Beijing,CN)。
  4. 预期输出:
    • 文本信息:清晰展示天气数据。
    • 可视化图表:柱状图(温度、湿度、气压)和雷达图(多维度对比)。

总结

本项目通过网络请求+数据解析+可视化+异常处理的组合,实现了一个实用的天气工具。从中级以下开发者的角度,你将学到:
– 如何与RESTful API交互,处理复杂的响应结构。
– 如何用Matplotlib创建多子图、多类型的可视化图表。
– 如何通过分层的异常处理,让程序在各种错误场景下保持健壮性。

这个项目代码结构清晰、依赖简单,适合作为网络编程+数据可视化的入门实践。如果你想进一步挑战,可以尝试扩展功能(如多城市对比、历史天气),或封装为Web服务(如Flask应用)。

现在,你可以动手实践,体验从“API调用”到“数据可视化”的完整流程,感受代码转化为实用工具的乐趣!