天气数据获取与可视化

Admin
发布于 2026-05-25 / 4 阅读
0
0
import requests
import matplotlib.pyplot as plt

def plot_weather():
    # 中国气象局官方公开API,返回标准JSON数据,极其稳定
    # 101190101 是南京的城市代码,可替换为其他城市
    url = "http://t.weather.sojson.com/api/weather/city/101190101"
    headers = {"User-Agent": "Mozilla/5.0"}

    print("正在从气象局API获取天气数据...")
    try:
        res = requests.get(url, headers=headers, timeout=10)
        res.raise_for_status()
        data = res.json() # 直接解析JSON数据
    except Exception as e:
        print(f"数据获取失败: {e}")
        return

    # 校验API返回的状态
    if data.get("status") != 200:
        print(f"API返回错误: {data.get('message')}")
        return

    # 提取未来7天的预报数据
    forecast = data.get("data", {}).get("forecast", [])[:7]
    
    if not forecast:
        print("未获取到预报数据!")
        return

    days = []
    high_temps = []
    low_temps = []

    # 解析每天的数据
    for day in forecast:
        # 日期,格式如 "28日星期六",我们只取前面的日期部分
        date_str = day.get("ymd", "") 
        if date_str:
            # 截取 MM-DD 格式,更美观
            days.append(date_str[5:]) 
        else:
            days.append(day.get("date", ""))
        
        # 最高温和最低温
        high = day.get("high")
        low = day.get("low")
        
        # API返回的温度格式可能是 "高温 31℃" 或纯数字,做兼容处理
        if isinstance(high, str):
            high = high.replace("高温 ", "").replace("℃", "")
        if isinstance(low, str):
            low = low.replace("低温 ", "").replace("℃", "")
            
        try:
            high_temps.append(int(high))
            low_temps.append(int(low))
        except ValueError:
            print(f"温度数据格式异常: {day}")
            return

    # --- 绘制图表 ---
    plt.figure(figsize=(10, 5))
    
    # 绘制最高温折线
    plt.plot(days, high_temps, marker="o", color="red", label="最高温", linewidth=2)
    # 绘制最低温折线
    plt.plot(days, low_temps, marker="o", color="blue", label="最低温", linewidth=2)

    # 在数据点上标注具体温度数值
    for i, temp in enumerate(high_temps):
        plt.text(i, temp + 0.8, f"{temp}°", ha='center', va='bottom', color='red', fontweight='bold')
    for i, temp in enumerate(low_temps):
        plt.text(i, temp - 0.8, f"{temp}°", ha='center', va='top', color='blue', fontweight='bold')

    plt.title("南京近7日温度变化趋势", fontsize=16, pad=15)
    plt.xlabel("日期", fontsize=12)
    plt.ylabel("温度 (℃)", fontsize=12)
    plt.legend(loc='lower right') # 显示图例
    plt.grid(True, linestyle='--', alpha=0.5) # 添加虚线网格
    plt.tight_layout() # 自动调整布局防遮挡
    plt.show()

if __name__ == "__main__":
    plot_weather()

这段Python代码实现了一个**天气数据获取与可视化**的小工具。它通过调用第三方天气API获取南京未来7天的天气预报数据,并使matplotlib库将最高温和最低温绘制成直观的折线图。

下面我将从实现原理、用途和注意事项三个方面为您详细解释:

### 一、 实现原理

代码的执行流程可以分为三个核心步骤:**数据获取**、**数据清洗与解析**、**数据可视化**。

1. 数据获取(网络请求)

- 使用 requests.get() 向指定的URL发送HTTP GET请求。

- 添加 headers 模拟浏览器访问,防止被部分具有反爬机制的服务器拒绝。

- 设置 timeout=10 防止网络不畅时程序无限期挂起。

- 使用 res.raise_for_status() 检查HTTP状态码,若请求失败(如404、500)则主动抛出异常。

- 使用 res.json() 将返回的JSON格式字符串直接解析为Python字典。

2. 数据清洗与解析(提取有效信息)

- 校验API返回的JSON字典中 status 是否为200,确认接口调用成功。

- 通过字典的 get() 方法逐层安全获取数据data -> forecast,并通过切片 [:7] 只取未来7天的数据。

- 日期处理:优先提取 ymd 字段(如"2023-10-28"),并通过切片 [5:] 截取月日部分(如"10-28")使图表横轴更简洁。

- 温度处理:由于API返回的温度字段可能是字符串(如 "高温 31℃"),代码使用了 replace() 方法剔除中文字符和符号,并通过 int() 转换为整型try...except ValueError 捕获了可能的转换异常,增强了代码的鲁棒性。

3. 数据可视化(绘制图表)

- 使用 plt.plot() 绘制最高温(红色)和最低温(蓝色)两条折线,设置 marker="o" 显示数据点。

- 使用 plt.text() 在每个数据点的上方(最高温)和下方(最低温)标注具体的温度数值,通过 hava 参数控制文字对齐方式,避免遮挡折线。

- 添加标题、坐标轴标签、图例和虚线网格,最后调用 plt.tight_layout() 自动调整子图参数,使之填充整个图像区域,防止标签被截断。

---

### 二、 用途

1. 日常天气直观查看:相比于阅读纯文本的天气预报,折线图能更直观地展现温度的起伏趋势,方便出行穿搭和行程规划。

2. 数据可视化学习案例:这是一个非常经典的“数据采集+数据清洗+数据可视化”的端到端示例,适合Python初学者学习如何将杂乱的数据转化为美观的图表。

3. 模板代码:只需修改URL中的城市代码(如将 101190101 改为 101010100 即为北京),就可以快速复用为其他城市的天气可视化工具。

---

### 三、 注意事项

1. API的稳定性与合法性

- 代码注释中写的是“中国气象局官方公开API”,但实际上 t.weather.sojson.com 是一个**第三方聚合API**,并非官方直连接口。第三方接口存在限流、停服或数据格式突然变更的风险。

- 此类接口通常仅供个人学习测试使用,请勿用于商业项目或高频爬取,以免IP被封禁。

2. 中文字体显示问题(常见Bug)

- 代码中使用了中文标题和标签(如 "南京近7日温度变化趋势")。在部分操作系统(如Windows的默认matplotlib配置)下,由于缺少中文字体支持,图表上的中文会显示为**方块或乱码**。

- 解决方法:在绘图前添加以下代码强制指定中文字体:

```python

plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置默认字体为黑体

plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题

```

3. 数据格式的强依赖性

- 代码的数据清洗逻辑(如 replace("高温 ", ""))是针对当前API的返回格式定制的。如果API提供方修改了返回格式(例如不再返回 "高温 " 而是直接返回数字),代码中的 replace 虽然不会报错,但如果格式变化过大,可能导致 int() 转换失败。因此try...except 的异常捕获在这里显得尤为重要。

4. 网络环境依赖

- 该脚本必须在内网连通的环境下运行。如果在无网络或受限的网络环境中requests 会抛出 ConnectionError 等异常,代码中顶层的 try...except Exception as e 已经做了兜底处理,会友好地提示“数据获取失败”。

好的!我为你替换了城市代码,并修改了图表标题。

在这个API中:

- 苏州的城市代码是 101190401

- 台北(台湾代表城市)的城市代码是 101340101

import requests
import matplotlib.pyplot as plt

def plot_weather_comparison():
    # 解决 matplotlib 中文显示成方块的问题
    plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows用黑体
    # plt.rcParams['font.sans-serif'] = ['PingFang HK'] # Mac用户请使用这一行
    plt.rcParams['axes.unicode_minus'] = False    # 正常显示负号

    # 城市配置
    cities = {
        "苏州": "101190401",
        "台北": "101340101"
    }
    
    headers = {"User-Agent": "Mozilla/5.0"}
    
    # 存储两个城市的数据
    all_data = {
        "苏州": {"days": [], "high": [], "low": []},
        "台北": {"days": [], "high": [], "low": []}
    }

    # 1. 依次获取两个城市的数据
    for city_name, city_code in cities.items():
        url = f"http://t.weather.sojson.com/api/weather/city/{city_code}"
        print(f"正在获取 {city_name} 天气数据...")
        
        try:
            res = requests.get(url, headers=headers, timeout=10)
            res.raise_for_status()
            data = res.json()
        except Exception as e:
            print(f"{city_name} 数据获取失败: {e}")
            return

        if data.get("status") != 200:
            print(f"{city_name} API返回错误: {data.get('message')}")
            return

        forecast = data.get("data", {}).get("forecast", [])[:7]
        if not forecast:
            print(f"{city_name} 未获取到预报数据!")
            return

        # 解析数据
        for day in forecast:
            # 日期处理(两个城市的日期应该是一致的,覆盖写入即可)
            date_str = day.get("ymd", "")
            if date_str:
                all_data[city_name]["days"].append(date_str[5:])
            else:
                all_data[city_name]["days"].append(day.get("date", ""))
            
            # 温度处理
            high = day.get("high")
            low = day.get("low")
            
            if isinstance(high, str):
                high = high.replace("高温 ", "").replace("℃", "")
            if isinstance(low, str):
                low = low.replace("低温 ", "").replace("℃", "")
                
            try:
                all_data[city_name]["high"].append(int(high))
                all_data[city_name]["low"].append(int(low))
            except ValueError:
                print(f"{city_name} 温度数据格式异常: {day}")
                return

    # 取任一城市的日期作为X轴(两个城市日期相同)
    days = all_data["苏州"]["days"]

    # 2. 绘制对比图表
    plt.figure(figsize=(12, 6))
    
    # 苏州:实线
    plt.plot(days, all_data["苏州"]["high"], marker="o", color="red", linestyle="-", label="苏州最高温", linewidth=2)
    plt.plot(days, all_data["苏州"]["low"], marker="o", color="blue", linestyle="-", label="苏州最低温", linewidth=2)
    
    # 台北:虚线
    plt.plot(days, all_data["台北"]["high"], marker="^", color="darkorange", linestyle="--", label="台北最高温", linewidth=2)
    plt.plot(days, all_data["台北"]["low"], marker="^", color="teal", linestyle="--", label="台北最低温", linewidth=2)

    # 在数据点上标注具体温度数值
    for i in range(len(days)):
        # 苏州标注
        plt.text(i, all_data["苏州"]["high"][i] + 0.8, f'{all_data["苏州"]["high"][i]}°', ha='center', va='bottom', color='red', fontsize=9)
        plt.text(i, all_data["苏州"]["low"][i] - 0.8, f'{all_data["苏州"]["low"][i]}°', ha='center', va='top', color='blue', fontsize=9)
        
        # 台北标注 (稍微左右偏移一点防止重叠)
        plt.text(i+0.15, all_data["台北"]["high"][i] + 0.8, f'{all_data["台北"]["high"][i]}°', ha='center', va='bottom', color='darkorange', fontsize=9)
        plt.text(i+0.15, all_data["台北"]["low"][i] - 0.8, f'{all_data["台北"]["low"][i]}°', ha='center', va='top', color='teal', fontsize=9)

    # 美化图表
    plt.title("苏州 vs 台北 近7日温度变化对比", fontsize=18, pad=15, fontweight='bold')
    plt.xlabel("日期", fontsize=12)
    plt.ylabel("温度 (℃)", fontsize=12)
    plt.legend(loc='best', fontsize=10) # 自动选择最佳位置显示图例
    plt.grid(True, linestyle='--', alpha=0.5) # 添加虚线网格
    plt.tight_layout() # 自动调整布局防遮挡
    plt.show()

if __name__ == "__main__":
    plot_weather_comparison()

之前使用的 t.weather.sojson.com 接口是基于中国气象局数据的,**只支持国内城市,没有国外城市的代码**。

为了实现苏州与纽约的跨国对比,我们需要更换一个支持全球城市的天气API。这里我为你挑选了 Open-Meteo,这是一个完全免费、无需注册API Key、支持全球城市且极其稳定的开源气象API。

因为API变了,数据获取和解析的逻辑需要完全重写。同时,由于纽约和苏州有**12个小时的时差**,为了公平对比同一天的天气,我在代码中加入了时区处理逻辑。

以下是全新的全球城市对比代码:

import requests
import matplotlib.pyplot as plt
from datetime import datetime
from dateutil import parser

def plot_global_weather_comparison():
    # 解决 matplotlib 中文显示成方块的问题
    plt.rcParams['font.sans-serif'] = ['SimHei']  # Windows用黑体
    # plt.rcParams['font.sans-serif'] = ['PingFang HK'] # Mac用户请使用这一行
    plt.rcParams['axes.unicode_minus'] = False    # 正常显示负号

    # 城市配置:使用经纬度定位全球任何地方
    cities = {
        "苏州": {"latitude": 31.30, "longitude": 120.62, "timezone": "Asia/Shanghai"},
        "纽约": {"latitude": 40.71, "longitude": -74.01, "timezone": "America/New_York"}
    }
    
    headers = {"User-Agent": "Mozilla/5.0"}
    all_data = {}

    # 1. 依次获取两个城市的数据
    for city_name, config in cities.items():
        # Open-Meteo API 地址 (daily=temperature_2m_max,temperature_2m_min 获取最高最低温)
        url = (
            f"https://api.open-meteo.com/v1/forecast?"
            f"latitude={config['latitude']}&longitude={config['longitude']}"
            f"&daily=temperature_2m_max,temperature_2m_min"
            f"&timezone={config['timezone']}"
        )
        
        print(f"正在获取 {city_name} 天气数据...")
        try:
            res = requests.get(url, headers=headers, timeout=10)
            res.raise_for_status()
            data = res.json()
        except Exception as e:
            print(f"{city_name} 数据获取失败: {e}")
            return

        # 解析数据
        daily = data.get("daily", {})
        dates = daily.get("time", [])
        high_temps = daily.get("temperature_2m_max", [])
        low_temps = daily.get("temperature_2m_min", [])

        if not dates or len(dates) < 7:
            print(f"{city_name} 获取到的数据不足7天!")
            return

        # 只取前7天数据,并格式化日期为 MM-DD
        formatted_days = []
        for date_str in dates[:7]:
            # 将 "2023-10-28" 转换为 "10-28"
            dt = parser.parse(date_str)
            formatted_days.append(dt.strftime("%m-%d"))

        all_data[city_name] = {
            "days": formatted_days,
            "high": high_temps[:7],
            "low": low_temps[:7]
        }

    # 取苏州的日期作为X轴
    days = all_data["苏州"]["days"]

    # 2. 绘制对比图表
    plt.figure(figsize=(12, 6))
    
    # 苏州:实线
    plt.plot(days, all_data["苏州"]["high"], marker="o", color="red", linestyle="-", label="苏州最高温", linewidth=2)
    plt.plot(days, all_data["苏州"]["low"], marker="o", color="blue", linestyle="-", label="苏州最低温", linewidth=2)
    
    # 纽约:虚线
    plt.plot(days, all_data["纽约"]["high"], marker="^", color="darkorange", linestyle="--", label="纽约最高温", linewidth=2)
    plt.plot(days, all_data["纽约"]["low"], marker="^", color="teal", linestyle="--", label="纽约最低温", linewidth=2)

    # 在数据点上标注具体温度数值
    for i in range(len(days)):
        # 苏州标注
        plt.text(i, all_data["苏州"]["high"][i] + 0.5, f'{all_data["苏州"]["high"][i]}°', ha='center', va='bottom', color='red', fontsize=9)
        plt.text(i, all_data["苏州"]["low"][i] - 0.5, f'{all_data["苏州"]["low"][i]}°', ha='center', va='top', color='blue', fontsize=9)
        
        # 纽约标注 (稍微左右偏移一点防止重叠)
        plt.text(i+0.15, all_data["纽约"]["high"][i] + 0.5, f'{all_data["纽约"]["high"][i]}°', ha='center', va='bottom', color='darkorange', fontsize=9)
        plt.text(i+0.15, all_data["纽约"]["low"][i] - 0.5, f'{all_data["纽约"]["low"][i]}°', ha='center', va='top', color='teal', fontsize=9)

    # 美化图表
    plt.title("苏州 vs 纽约 近7日温度变化对比", fontsize=18, pad=15, fontweight='bold')
    plt.xlabel("日期 (苏州时区)", fontsize=12)
    plt.ylabel("温度 (℃)", fontsize=12)
    plt.legend(loc='best', fontsize=10) # 自动选择最佳位置显示图例
    plt.grid(True, linestyle='--', alpha=0.5) # 添加虚线网格
    plt.tight_layout() # 自动调整布局防遮挡
    plt.show()

if __name__ == "__main__":
    plot_global_weather_comparison()


评论