Cursor 写的,获取城市天气.

import requests
import json
from datetime import datetime
import time
from typing import Dict, Optional, Tuple
import os
import re
import urllib.parse
import sys

class WeatherAPI:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.openweathermap.org/data/2.5/weather"
        self.geocoding_url = "https://api.openweathermap.org/geo/1.0/direct"
        self.cache_file = "weather_cache.json"
        self.cache_duration = 1800  # 缓存时间30分钟
        # 加载城市数据
        self.city_mapping = self._load_city_data()
        # 国家代码映射
        self.country_mapping = {
            "CN": "中国",
            "HK": "香港",
            "MO": "澳门",
            "TW": "台湾"
        }

    def _load_city_data(self) -> Dict:
        """加载城市数据"""
        city_file = "cities.json"
        if os.path.exists(city_file):
            try:
                with open(city_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except:
                return self._get_default_cities()
        return self._get_default_cities()

    def _get_default_cities(self) -> Dict:
        """获取默认城市数据"""
        return {
            # 直辖市
            "北京": {"name": "Beijing", "country": "CN", "lat": 39.9042, "lon": 116.4074},
            "上海": {"name": "Shanghai", "country": "CN", "lat": 31.2304, "lon": 121.4737},
            "天津": {"name": "Tianjin", "country": "CN", "lat": 39.3434, "lon": 117.3616},
            "重庆": {"name": "Chongqing", "country": "CN", "lat": 29.4316, "lon": 106.9123},
            
            # 河北省
            "石家庄": {"name": "Shijiazhuang", "country": "CN", "lat": 38.0428, "lon": 114.5149},
            "唐山": {"name": "Tangshan", "country": "CN", "lat": 39.6305, "lon": 118.1804},
            "秦皇岛": {"name": "Qinhuangdao", "country": "CN", "lat": 39.9398, "lon": 119.5886},
            "邯郸": {"name": "Handan", "country": "CN", "lat": 36.6256, "lon": 114.5391},
            "邢台": {"name": "Xingtai", "country": "CN", "lat": 37.0682, "lon": 114.5048},
            "保定": {"name": "Baoding", "country": "CN", "lat": 38.8738, "lon": 115.4646},
            "张家口": {"name": "Zhangjiakou", "country": "CN", "lat": 40.8244, "lon": 114.8877},
            "承德": {"name": "Chengde", "country": "CN", "lat": 40.9724, "lon": 117.9332},
            "沧州": {"name": "Cangzhou", "country": "CN", "lat": 38.3034, "lon": 116.8388},
            "廊坊": {"name": "Langfang", "country": "CN", "lat": 39.5372, "lon": 116.6838},
            "衡水": {"name": "Hengshui", "country": "CN", "lat": 37.7351, "lon": 115.6703},
            
            # 山西省
            "太原": {"name": "Taiyuan", "country": "CN", "lat": 37.8706, "lon": 112.5489},
            "大同": {"name": "Datong", "country": "CN", "lat": 40.0768, "lon": 113.2952},
            "阳泉": {"name": "Yangquan", "country": "CN", "lat": 37.8570, "lon": 113.5833},
            "长治": {"name": "Changzhi", "country": "CN", "lat": 36.1958, "lon": 113.1135},
            "晋城": {"name": "Jincheng", "country": "CN", "lat": 35.4907, "lon": 112.8513},
            "朔州": {"name": "Shuozhou", "country": "CN", "lat": 39.3313, "lon": 112.4334},
            "晋中": {"name": "Jinzhong", "country": "CN", "lat": 37.6870, "lon": 112.7527},
            "运城": {"name": "Yuncheng", "country": "CN", "lat": 35.0228, "lon": 111.0039},
            "忻州": {"name": "Xinzhou", "country": "CN", "lat": 38.4167, "lon": 112.7335},
            "临汾": {"name": "Linfen", "country": "CN", "lat": 36.0881, "lon": 111.5189},
            "吕梁": {"name": "Lvliang", "country": "CN", "lat": 37.5174, "lon": 111.1343},
            
            # 内蒙古自治区
            "呼和浩特": {"name": "Hohhot", "country": "CN", "lat": 40.8424, "lon": 111.7498},
            "包头": {"name": "Baotou", "country": "CN", "lat": 40.6572, "lon": 109.8403},
            "乌海": {"name": "Wuhai", "country": "CN", "lat": 39.6547, "lon": 106.7924},
            "赤峰": {"name": "Chifeng", "country": "CN", "lat": 42.2580, "lon": 118.8869},
            "通辽": {"name": "Tongliao", "country": "CN", "lat": 43.6525, "lon": 122.2433},
            "鄂尔多斯": {"name": "Ordos", "country": "CN", "lat": 39.6087, "lon": 109.7811},
            "呼伦贝尔": {"name": "Hulunbuir", "country": "CN", "lat": 49.2153, "lon": 119.7559},
            "巴彦淖尔": {"name": "Bayan Nur", "country": "CN", "lat": 40.7432, "lon": 107.3879},
            "乌兰察布": {"name": "Ulanqab", "country": "CN", "lat": 41.0341, "lon": 113.1141},
            
            # 辽宁省
            "沈阳": {"name": "Shenyang", "country": "CN", "lat": 41.8057, "lon": 123.4315},
            "大连": {"name": "Dalian", "country": "CN", "lat": 38.9140, "lon": 121.6147},
            "鞍山": {"name": "Anshan", "country": "CN", "lat": 41.1078, "lon": 122.9956},
            "抚顺": {"name": "Fushun", "country": "CN", "lat": 41.8811, "lon": 123.9572},
            "本溪": {"name": "Benxi", "country": "CN", "lat": 41.2941, "lon": 123.7669},
            "丹东": {"name": "Dandong", "country": "CN", "lat": 40.1292, "lon": 124.3947},
            "锦州": {"name": "Jinzhou", "country": "CN", "lat": 41.0957, "lon": 121.1272},
            "营口": {"name": "Yingkou", "country": "CN", "lat": 40.6668, "lon": 122.2334},
            "阜新": {"name": "Fuxin", "country": "CN", "lat": 42.0216, "lon": 121.6701},
            "辽阳": {"name": "Liaoyang", "country": "CN", "lat": 41.2681, "lon": 123.2374},
            "盘锦": {"name": "Panjin", "country": "CN", "lat": 41.1199, "lon": 122.0696},
            "铁岭": {"name": "Tieling", "country": "CN", "lat": 42.2233, "lon": 123.7250},
            "朝阳": {"name": "Chaoyang", "country": "CN", "lat": 41.5735, "lon": 120.4511},
            "葫芦岛": {"name": "Huludao", "country": "CN", "lat": 40.7110, "lon": 120.8369},
            
            # 吉林省
            "长春": {"name": "Changchun", "country": "CN", "lat": 43.8171, "lon": 125.3235},
            "吉林": {"name": "Jilin", "country": "CN", "lat": 43.8378, "lon": 126.5496},
            "四平": {"name": "Siping", "country": "CN", "lat": 43.1664, "lon": 124.3508},
            "辽源": {"name": "Liaoyuan", "country": "CN", "lat": 42.9023, "lon": 125.1445},
            "通化": {"name": "Tonghua", "country": "CN", "lat": 41.7285, "lon": 125.9397},
            "白山": {"name": "Baishan", "country": "CN", "lat": 41.9429, "lon": 126.4238},
            "松原": {"name": "Songyuan", "country": "CN", "lat": 45.1418, "lon": 124.8259},
            "白城": {"name": "Baicheng", "country": "CN", "lat": 45.6196, "lon": 122.8411},
            
            # 黑龙江省
            "哈尔滨": {"name": "Harbin", "country": "CN", "lat": 45.8038, "lon": 126.5340},
            "齐齐哈尔": {"name": "Qiqihar", "country": "CN", "lat": 47.3543, "lon": 123.9182},
            "鸡西": {"name": "Jixi", "country": "CN", "lat": 45.2952, "lon": 130.9693},
            "鹤岗": {"name": "Hegang", "country": "CN", "lat": 47.3320, "lon": 130.2773},
            "双鸭山": {"name": "Shuangyashan", "country": "CN", "lat": 46.6434, "lon": 131.1591},
            "大庆": {"name": "Daqing", "country": "CN", "lat": 46.5907, "lon": 125.1038},
            "伊春": {"name": "Yichun", "country": "CN", "lat": 47.7275, "lon": 128.8994},
            "佳木斯": {"name": "Jiamusi", "country": "CN", "lat": 46.8098, "lon": 130.3188},
            "七台河": {"name": "Qitaihe", "country": "CN", "lat": 45.7750, "lon": 131.0156},
            "牡丹江": {"name": "Mudanjiang", "country": "CN", "lat": 44.5517, "lon": 129.6332},
            "黑河": {"name": "Heihe", "country": "CN", "lat": 50.2452, "lon": 127.4995},
            "绥化": {"name": "Suihua", "country": "CN", "lat": 46.6524, "lon": 126.9689},
            "大兴安岭": {"name": "Daxinganling", "country": "CN", "lat": 52.3352, "lon": 124.7115},
            
            # 香港特别行政区
            "香港": {"name": "Hong Kong", "country": "HK", "lat": 22.3193, "lon": 114.1694},
            "九龙": {"name": "Kowloon", "country": "HK", "lat": 22.3167, "lon": 114.1833},
            "新界": {"name": "New Territories", "country": "HK", "lat": 22.4167, "lon": 114.1667},
            
            # 澳门特别行政区
            "澳门": {"name": "Macau", "country": "MO", "lat": 22.1987, "lon": 113.5439},
            "氹仔": {"name": "Taipa", "country": "MO", "lat": 22.1600, "lon": 113.5583},
            "路氹城": {"name": "Cotai", "country": "MO", "lat": 22.1533, "lon": 113.5583},
            
            # 台湾地区
            "台北": {"name": "Taipei", "country": "TW", "lat": 25.0330, "lon": 121.5654},
            "新北": {"name": "New Taipei", "country": "TW", "lat": 25.0120, "lon": 121.4657},
            "桃园": {"name": "Taoyuan", "country": "TW", "lat": 24.9936, "lon": 121.3010},
            "台中": {"name": "Taichung", "country": "TW", "lat": 24.1477, "lon": 120.6736},
            "台南": {"name": "Tainan", "country": "TW", "lat": 23.1417, "lon": 120.2513},
            "高雄": {"name": "Kaohsiung", "country": "TW", "lat": 22.6273, "lon": 120.3014},
            "基隆": {"name": "Keelung", "country": "TW", "lat": 25.1283, "lon": 121.7419},
            "新竹": {"name": "Hsinchu", "country": "TW", "lat": 24.8138, "lon": 120.9675},
            "嘉义": {"name": "Chiayi", "country": "TW", "lat": 23.4794, "lon": 120.4491},
            
            # 华东地区
            "南京": {"name": "Nanjing", "country": "CN", "lat": 32.0603, "lon": 118.7969},
            "苏州": {"name": "Suzhou", "country": "CN", "lat": 31.2990, "lon": 120.5853},
            "无锡": {"name": "Wuxi", "country": "CN", "lat": 31.4897, "lon": 120.3123},
            "常州": {"name": "Changzhou", "country": "CN", "lat": 31.8112, "lon": 119.9741},
            "南通": {"name": "Nantong", "country": "CN", "lat": 31.9802, "lon": 120.8943},
            "扬州": {"name": "Yangzhou", "country": "CN", "lat": 32.3947, "lon": 119.4129},
            "徐州": {"name": "Xuzhou", "country": "CN", "lat": 34.2044, "lon": 117.2858},
            "连云港": {"name": "Lianyungang", "country": "CN", "lat": 34.5969, "lon": 119.2216},
            "淮安": {"name": "Huaian", "country": "CN", "lat": 33.5975, "lon": 119.0153},
            "盐城": {"name": "Yancheng", "country": "CN", "lat": 33.3477, "lon": 120.1636},
            "宿迁": {"name": "Suqian", "country": "CN", "lat": 33.9631, "lon": 118.2752},
            "泰州": {"name": "Taizhou", "country": "CN", "lat": 32.4558, "lon": 119.9231},
            
            # 华南地区
            "广州": {"name": "Guangzhou", "country": "CN", "lat": 23.1291, "lon": 113.2644},
            "深圳": {"name": "Shenzhen", "country": "CN", "lat": 22.5431, "lon": 114.0579},
            "珠海": {"name": "Zhuhai", "country": "CN", "lat": 22.2707, "lon": 113.5767},
            "汕头": {"name": "Shantou", "country": "CN", "lat": 23.3541, "lon": 116.6819},
            "韶关": {"name": "Shaoguan", "country": "CN", "lat": 24.8104, "lon": 113.5972},
            "佛山": {"name": "Foshan", "country": "CN", "lat": 23.0215, "lon": 113.1214},
            "江门": {"name": "Jiangmen", "country": "CN", "lat": 22.5787, "lon": 113.0934},
            "湛江": {"name": "Zhanjiang", "country": "CN", "lat": 21.2707, "lon": 110.3594},
            "茂名": {"name": "Maoming", "country": "CN", "lat": 21.6629, "lon": 110.9254},
            "肇庆": {"name": "Zhaoqing", "country": "CN", "lat": 23.0472, "lon": 112.4651},
            "惠州": {"name": "Huizhou", "country": "CN", "lat": 23.1115, "lon": 114.4162},
            "梅州": {"name": "Meizhou", "country": "CN", "lat": 24.2886, "lon": 116.1222},
            "汕尾": {"name": "Shanwei", "country": "CN", "lat": 22.7862, "lon": 115.3753},
            "河源": {"name": "Heyuan", "country": "CN", "lat": 23.7435, "lon": 114.6978},
            "阳江": {"name": "Yangjiang", "country": "CN", "lat": 21.8579, "lon": 111.9822},
            "清远": {"name": "Qingyuan", "country": "CN", "lat": 23.6818, "lon": 113.0560},
            "东莞": {"name": "Dongguan", "country": "CN", "lat": 23.0207, "lon": 113.7518},
            "中山": {"name": "Zhongshan", "country": "CN", "lat": 22.5176, "lon": 113.3928},
            "潮州": {"name": "Chaozhou", "country": "CN", "lat": 23.6571, "lon": 116.6222},
            "揭阳": {"name": "Jieyang", "country": "CN", "lat": 23.5499, "lon": 116.3728},
            "云浮": {"name": "Yunfu", "country": "CN", "lat": 22.9151, "lon": 112.0444},
            
            # 华中地区
            "武汉": {"name": "Wuhan", "country": "CN", "lat": 30.5928, "lon": 114.3055},
            "黄石": {"name": "Huangshi", "country": "CN", "lat": 30.2048, "lon": 115.0389},
            "十堰": {"name": "Shiyan", "country": "CN", "lat": 32.6294, "lon": 110.7980},
            "宜昌": {"name": "Yichang", "country": "CN", "lat": 30.6919, "lon": 111.2865},
            "襄阳": {"name": "Xiangyang", "country": "CN", "lat": 32.0090, "lon": 112.1222},
            "鄂州": {"name": "Ezhou", "country": "CN", "lat": 30.3961, "lon": 114.8900},
            "荆门": {"name": "Jingmen", "country": "CN", "lat": 31.0354, "lon": 112.1993},
            "孝感": {"name": "Xiaogan", "country": "CN", "lat": 30.9246, "lon": 113.9169},
            "荆州": {"name": "Jingzhou", "country": "CN", "lat": 30.3348, "lon": 112.2384},
            "黄冈": {"name": "Huanggang", "country": "CN", "lat": 30.4461, "lon": 114.8722},
            "咸宁": {"name": "Xianning", "country": "CN", "lat": 29.8413, "lon": 114.3225},
            "随州": {"name": "Suizhou", "country": "CN", "lat": 31.6902, "lon": 113.3748},
            "恩施": {"name": "Enshi", "country": "CN", "lat": 30.2722, "lon": 109.4792},
            "仙桃": {"name": "Xiantao", "country": "CN", "lat": 30.3625, "lon": 113.4545},
            "天门": {"name": "Tianmen", "country": "CN", "lat": 30.6633, "lon": 113.1661},
            "潜江": {"name": "Qianjiang", "country": "CN", "lat": 30.4020, "lon": 112.8990},
            "神农架": {"name": "Shennongjia", "country": "CN", "lat": 31.7449, "lon": 110.6758},
            
            # 湖南省主要城市
            "长沙": {"name": "Changsha", "country": "CN", "lat": 28.2278, "lon": 112.9388},
            "株洲": {"name": "Zhuzhou", "country": "CN", "lat": 27.8273, "lon": 113.1340},
            "湘潭": {"name": "Xiangtan", "country": "CN", "lat": 27.8297, "lon": 112.9440},
            "衡阳": {"name": "Hengyang", "country": "CN", "lat": 26.8982, "lon": 112.5719},
            "邵阳": {"name": "Shaoyang", "country": "CN", "lat": 27.2389, "lon": 111.4677},
            "岳阳": {"name": "Yueyang", "country": "CN", "lat": 29.3572, "lon": 113.1291},
            "常德": {"name": "Changde", "country": "CN", "lat": 29.0316, "lon": 111.6985},
            "张家界": {"name": "Zhangjiajie", "country": "CN", "lat": 29.1274, "lon": 110.4792},
            "益阳": {"name": "Yiyang", "country": "CN", "lat": 28.5539, "lon": 112.3550},
            "郴州": {"name": "Chenzhou", "country": "CN", "lat": 25.7705, "lon": 113.0147},
            "永州": {"name": "Yongzhou", "country": "CN", "lat": 26.4203, "lon": 111.6080},
            "怀化": {"name": "Huaihua", "country": "CN", "lat": 27.5700, "lon": 110.0019},
            "娄底": {"name": "Loudi", "country": "CN", "lat": 27.7281, "lon": 111.9964},
            "湘西": {"name": "Xiangxi", "country": "CN", "lat": 28.3120, "lon": 109.7374},
            
            # 河南省主要城市
            "郑州": {"name": "Zhengzhou", "country": "CN", "lat": 34.7472, "lon": 113.6250},
            "开封": {"name": "Kaifeng", "country": "CN", "lat": 34.7971, "lon": 114.3075},
            "洛阳": {"name": "Luoyang", "country": "CN", "lat": 34.6197, "lon": 112.4539},
            "平顶山": {"name": "Pingdingshan", "country": "CN", "lat": 33.7350, "lon": 113.1928},
            "安阳": {"name": "Anyang", "country": "CN", "lat": 36.0997, "lon": 114.3931},
            "鹤壁": {"name": "Hebi", "country": "CN", "lat": 35.7482, "lon": 114.2972},
            "新乡": {"name": "Xinxiang", "country": "CN", "lat": 35.3030, "lon": 113.9268},
            "焦作": {"name": "Jiaozuo", "country": "CN", "lat": 35.2159, "lon": 113.2418},
            "濮阳": {"name": "Puyang", "country": "CN", "lat": 35.7618, "lon": 115.0293},
            "许昌": {"name": "Xuchang", "country": "CN", "lat": 34.0229, "lon": 113.8261},
            "漯河": {"name": "Luohe", "country": "CN", "lat": 33.5815, "lon": 114.0168},
            "三门峡": {"name": "Sanmenxia", "country": "CN", "lat": 34.7733, "lon": 111.2001},
            "南阳": {"name": "Nanyang", "country": "CN", "lat": 32.9908, "lon": 112.5283},
            "商丘": {"name": "Shangqiu", "country": "CN", "lat": 34.4142, "lon": 115.6505},
            "信阳": {"name": "Xinyang", "country": "CN", "lat": 32.1470, "lon": 114.0928},
            "周口": {"name": "Zhoukou", "country": "CN", "lat": 33.6254, "lon": 114.6969},
            "驻马店": {"name": "Zhumadian", "country": "CN", "lat": 32.9773, "lon": 114.0251},
            
            # 江西省主要城市
            "南昌": {"name": "Nanchang", "country": "CN", "lat": 28.6820, "lon": 115.8579},
            "景德镇": {"name": "Jingdezhen", "country": "CN", "lat": 29.2926, "lon": 117.2074},
            "萍乡": {"name": "Pingxiang", "country": "CN", "lat": 27.6229, "lon": 113.8522},
            "九江": {"name": "Jiujiang", "country": "CN", "lat": 29.7051, "lon": 116.0018},
            "新余": {"name": "Xinyu", "country": "CN", "lat": 27.8174, "lon": 114.9173},
            "鹰潭": {"name": "Yingtan", "country": "CN", "lat": 28.2602, "lon": 117.0692},
            "赣州": {"name": "Ganzhou", "country": "CN", "lat": 25.8454, "lon": 114.9348},
            "吉安": {"name": "Jian", "country": "CN", "lat": 27.1138, "lon": 114.9935},
            "宜春": {"name": "Yichun", "country": "CN", "lat": 27.8111, "lon": 114.4161},
            "抚州": {"name": "Fuzhou", "country": "CN", "lat": 27.9492, "lon": 116.3581},
            "上饶": {"name": "Shangrao", "country": "CN", "lat": 28.4548, "lon": 117.9434},
            
            # 西南地区
            "成都": {"name": "Chengdu", "country": "CN", "lat": 30.5728, "lon": 104.0668},
            "自贡": {"name": "Zigong", "country": "CN", "lat": 29.3392, "lon": 104.7784},
            "攀枝花": {"name": "Panzhihua", "country": "CN", "lat": 26.5823, "lon": 101.7186},
            "泸州": {"name": "Luzhou", "country": "CN", "lat": 28.8717, "lon": 105.4419},
            "德阳": {"name": "Deyang", "country": "CN", "lat": 31.1311, "lon": 104.3980},
            "绵阳": {"name": "Mianyang", "country": "CN", "lat": 31.4678, "lon": 104.6791},
            "广元": {"name": "Guangyuan", "country": "CN", "lat": 32.4355, "lon": 105.8433},
            "遂宁": {"name": "Suining", "country": "CN", "lat": 30.5332, "lon": 105.5933},
            "内江": {"name": "Neijiang", "country": "CN", "lat": 29.5835, "lon": 105.0584},
            "乐山": {"name": "Leshan", "country": "CN", "lat": 29.5521, "lon": 103.7654},
            "南充": {"name": "Nanchong", "country": "CN", "lat": 30.8373, "lon": 106.1107},
            "宜宾": {"name": "Yibin", "country": "CN", "lat": 28.7513, "lon": 104.6419},
            "广安": {"name": "Guangan", "country": "CN", "lat": 30.4739, "lon": 106.6333},
            "达州": {"name": "Dazhou", "country": "CN", "lat": 31.2096, "lon": 107.4680},
            "雅安": {"name": "Yaan", "country": "CN", "lat": 30.0156, "lon": 103.0424},
            "眉山": {"name": "Meishan", "country": "CN", "lat": 30.0753, "lon": 103.8485},
            "资阳": {"name": "Ziyang", "country": "CN", "lat": 30.1222, "lon": 104.6419},
            "阿坝": {"name": "Aba", "country": "CN", "lat": 32.8994, "lon": 101.7008},
            "甘孜": {"name": "Ganzi", "country": "CN", "lat": 30.0495, "lon": 101.9623},
            "凉山": {"name": "Liangshan", "country": "CN", "lat": 27.8816, "lon": 102.2673},
            
            # 西北地区
            "西安": {"name": "Xi'an", "country": "CN", "lat": 34.3416, "lon": 108.9398},
            "铜川": {"name": "Tongchuan", "country": "CN", "lat": 34.9087, "lon": 108.9455},
            "宝鸡": {"name": "Baoji", "country": "CN", "lat": 34.3609, "lon": 107.2372},
            "咸阳": {"name": "Xianyang", "country": "CN", "lat": 34.3296, "lon": 108.7085},
            "渭南": {"name": "Weinan", "country": "CN", "lat": 34.4994, "lon": 109.5101},
            "延安": {"name": "Yanan", "country": "CN", "lat": 36.5854, "lon": 109.4897},
            "汉中": {"name": "Hanzhong", "country": "CN", "lat": 33.0678, "lon": 107.0233},
            "榆林": {"name": "Yulin", "country": "CN", "lat": 38.2854, "lon": 109.7346},
            "安康": {"name": "Ankang", "country": "CN", "lat": 32.6903, "lon": 109.0293},
            "商洛": {"name": "Shangluo", "country": "CN", "lat": 33.8704, "lon": 109.9405},
            
            # 其他重要城市
            "青岛": {"name": "Qingdao", "country": "CN", "lat": 36.0671, "lon": 120.3826},
            "厦门": {"name": "Xiamen", "country": "CN", "lat": 24.4798, "lon": 118.0894},
            "宁波": {"name": "Ningbo", "country": "CN", "lat": 29.8684, "lon": 121.5440},
            "济南": {"name": "Jinan", "country": "CN", "lat": 36.6510, "lon": 117.1205},
            "杭州": {"name": "Hangzhou", "country": "CN", "lat": 30.2741, "lon": 120.1551},
            "合肥": {"name": "Hefei", "country": "CN", "lat": 31.8639, "lon": 117.2805},
            "兰州": {"name": "Lanzhou", "country": "CN", "lat": 36.0611, "lon": 103.8343},
            "西宁": {"name": "Xining", "country": "CN", "lat": 36.6232, "lon": 101.7804},
            "银川": {"name": "Yinchuan", "country": "CN", "lat": 38.4872, "lon": 106.2309},
            "乌鲁木齐": {"name": "Urumqi", "country": "CN", "lat": 43.8256, "lon": 87.6168},
            "拉萨": {"name": "Lhasa", "country": "CN", "lat": 29.6500, "lon": 91.1000},
            "南宁": {"name": "Nanning", "country": "CN", "lat": 22.8170, "lon": 108.3665},
            "福州": {"name": "Fuzhou", "country": "CN", "lat": 26.0745, "lon": 119.2965}
        }

    def _clean_city_name(self, city: str) -> Optional[Dict]:
        """清理城市名称并返回城市信息"""
        # 移除首尾空格
        city = city.strip()
        
        # 检查是否在映射中
        if city in self.city_mapping:
            return self.city_mapping[city]
            
        # 尝试使用地理编码API获取城市信息
        try:
            encoded_city = urllib.parse.quote(f"{city},CN")
            params = {
                'q': encoded_city,
                'limit': 1,
                'appid': self.api_key
            }
            response = requests.get(self.geocoding_url, params=params, timeout=10, verify=True)
            response.raise_for_status()
            data = response.json()
            
            if data and len(data) > 0:
                city_info = {
                    'name': data[0]['name'],
                    'country': 'CN',
                    'lat': data[0]['lat'],
                    'lon': data[0]['lon']
                }
                # 保存到缓存
                self.city_mapping[city] = city_info
                self._save_city_data()
                return city_info
        except Exception as e:
            print(f"获取城市信息失败: {e}")
            
        return None

    def _save_city_data(self):
        """保存城市数据到文件"""
        try:
            with open("cities.json", 'w', encoding='utf-8') as f:
                json.dump(self.city_mapping, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存城市数据失败: {e}")

    def _get_coordinates(self, city: str) -> Optional[Tuple[float, float]]:
        """获取城市的经纬度坐标"""
        try:
            # URL编码城市名称
            encoded_city = urllib.parse.quote(city)
            params = {
                'q': encoded_city,
                'limit': 1,
                'appid': self.api_key
            }
            response = requests.get(self.geocoding_url, params=params, timeout=10)
            response.raise_for_status()
            data = response.json()
            
            if data and len(data) > 0:
                return data[0]['lat'], data[0]['lon']
            return None
        except Exception as e:
            print(f"获取坐标失败: {e}")
            return None

    def _load_cache(self) -> Dict:
        """加载缓存数据"""
        if os.path.exists(self.cache_file):
            try:
                with open(self.cache_file, 'r', encoding='utf-8') as f:
                    return json.load(f)
            except:
                return {}
        return {}

    def _save_cache(self, cache_data: Dict):
        """保存缓存数据"""
        with open(self.cache_file, 'w', encoding='utf-8') as f:
            json.dump(cache_data, f, ensure_ascii=False, indent=2)

    def _format_temperature(self, temp: float) -> str:
        """格式化温度显示"""
        return f"{temp:.1f}°C"

    def _format_wind_speed(self, speed: float) -> str:
        """格式化风速显示"""
        return f"{speed:.1f} m/s"

    def _format_time(self, timestamp: int) -> str:
        """格式化时间显示"""
        return datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')

    def get_weather(self, city: str) -> Optional[Dict]:
        """获取天气信息"""
        # 获取城市信息
        city_info = self._clean_city_name(city)
        if not city_info:
            print("\n找不到该城市,请使用支持的城市名称")
            print("\n支持的城市名称示例:")
            for cn, info in self.city_mapping.items():
                print(f"- {cn} ({info['name']})")
            return None
        
        # 检查缓存
        cache = self._load_cache()
        current_time = time.time()
        
        if city in cache:
            cached_data = cache[city]
            if current_time - cached_data['timestamp'] < self.cache_duration:
                return cached_data['data']

        try:
            # 使用经纬度直接查询
            params = {
                'lat': city_info['lat'],
                'lon': city_info['lon'],
                'appid': self.api_key,
                'units': 'metric',
                'lang': 'zh_cn'  # 使用中文返回天气描述
            }

            # 添加重试机制
            max_retries = 3
            retry_count = 0
            while retry_count < max_retries:
                try:
                    response = requests.get(self.base_url, params=params, timeout=10, verify=True)
                    response.raise_for_status()
                    break
                except requests.exceptions.RequestException as e:
                    retry_count += 1
                    if retry_count == max_retries:
                        raise e
                    time.sleep(1)  # 等待1秒后重试

            weather_data = response.json()

            # 提取并格式化天气信息
            result = {
                'city': city_info['name'],
                'country': city_info['country'],
                'temperature': self._format_temperature(weather_data['main']['temp']),
                'feels_like': self._format_temperature(weather_data['main']['feels_like']),
                'weather': weather_data['weather'][0]['description'],
                'humidity': f"{weather_data['main']['humidity']}%",
                'wind_speed': self._format_wind_speed(weather_data['wind']['speed']),
                'pressure': f"{weather_data['main']['pressure']} hPa",
                'visibility': f"{weather_data['visibility'] / 1000:.1f} km",
                'sunrise': self._format_time(weather_data['sys']['sunrise']),
                'sunset': self._format_time(weather_data['sys']['sunset']),
                'update_time': self._format_time(weather_data['dt'])
            }

            # 更新缓存
            cache[city] = {
                'timestamp': current_time,
                'data': result
            }
            self._save_cache(cache)

            return result

        except requests.exceptions.RequestException as e:
            print(f"请求失败: {e}")
            if hasattr(e, 'response') and e.response is not None:
                print(f"状态码: {e.response.status_code}")
                print(f"错误信息: {e.response.text}")
                if e.response.status_code == 401:
                    print("\nAPI密钥可能已过期或无效,请检查API密钥")
                elif e.response.status_code == 429:
                    print("\nAPI请求次数超限,请稍后再试")
            return None
        except Exception as e:
            print(f"发生错误: {e}")
            return None

    def _get_chinese_name(self, city_info: Dict) -> str:
        """获取城市的中文名称"""
        # 遍历city_mapping找到对应的中文名称
        for cn_name, info in self.city_mapping.items():
            if info['name'] == city_info['name']:
                return cn_name
        return city_info['name']

    def display_weather(self, weather_data: Dict):
        """显示天气信息"""
        if not weather_data:
            return

        # 获取城市的中文名称
        city_info = {'name': weather_data['city'], 'country': weather_data['country']}
        chinese_city_name = self._get_chinese_name(city_info)
        chinese_country_name = self.country_mapping.get(weather_data['country'], weather_data['country'])

        print("\n" + "="*50)
        print(f"城市: {chinese_city_name} ({chinese_country_name})")
        print(f"更新时间: {weather_data['update_time']}")
        print("-"*50)
        print(f"温度: {weather_data['temperature']}")
        print(f"体感温度: {weather_data['feels_like']}")
        print(f"天气状况: {weather_data['weather']}")
        print(f"湿度: {weather_data['humidity']}")
        print(f"风速: {weather_data['wind_speed']}")
        print(f"气压: {weather_data['pressure']}")
        print(f"能见度: {weather_data['visibility']}")
        print("-"*50)
        print(f"日出时间: {weather_data['sunrise']}")
        print(f"日落时间: {weather_data['sunset']}")
        print("="*50 + "\n")

def main():
    # API密钥
    api_key = "f7bc9b8398543952ca08898e13ccab2b"
    
    # 创建WeatherAPI实例
    weather_api = WeatherAPI(api_key)
    
    print("欢迎使用天气查询系统!")
    print("支持中文城市名称查询")
    print("\n支持的城市包括:")
    print("1. 直辖市:北京、上海、天津、重庆")
    print("2. 华北地区:")
    print("   河北省:石家庄、唐山、秦皇岛、邯郸、邢台、保定、张家口、承德、沧州、廊坊、衡水")
    print("   山西省:太原、大同、阳泉、长治、晋城、朔州、晋中、运城、忻州、临汾、吕梁")
    print("   内蒙古自治区:呼和浩特、包头、乌海、赤峰、通辽、鄂尔多斯、呼伦贝尔、巴彦淖尔、乌兰察布")
    print("3. 东北地区:")
    print("   辽宁省:沈阳、大连、鞍山、抚顺、本溪、丹东、锦州、营口、阜新、辽阳、盘锦、铁岭、朝阳、葫芦岛")
    print("   吉林省:长春、吉林、四平、辽源、通化、白山、松原、白城")
    print("   黑龙江省:哈尔滨、齐齐哈尔、鸡西、鹤岗、双鸭山、大庆、伊春、佳木斯、七台河、牡丹江、黑河、绥化、大兴安岭")
    print("4. 华东地区:")
    print("   江苏省:南京、苏州、无锡、常州、南通、扬州、徐州、连云港、淮安、盐城、宿迁、泰州")
    print("   浙江省:杭州、宁波、温州、嘉兴、湖州、绍兴、金华、衢州、舟山、台州、丽水")
    print("   安徽省:合肥、芜湖、蚌埠、淮南、马鞍山、淮北、铜陵、安庆、黄山、滁州、阜阳、宿州、六安、亳州、池州、宣城")
    print("   福建省:福州、厦门、莆田、三明、泉州、漳州、南平、龙岩、宁德")
    print("   江西省:南昌、景德镇、萍乡、九江、新余、鹰潭、赣州、吉安、宜春、抚州、上饶")
    print("   山东省:济南、青岛、淄博、枣庄、东营、烟台、潍坊、济宁、泰安、威海、日照、临沂、德州、聊城、滨州、菏泽")
    print("5. 华南地区:")
    print("   广东省:广州、深圳、珠海、汕头、韶关、佛山、江门、湛江、茂名、肇庆、惠州、梅州、汕尾、河源、阳江、清远、东莞、中山、潮州、揭阳、云浮")
    print("   广西壮族自治区:南宁、柳州、桂林、梧州、北海、防城港、钦州、贵港、玉林、百色、贺州、河池、来宾、崇左")
    print("   海南省:海口、三亚、三沙、儋州")
    print("6. 华中地区:")
    print("   湖北省:武汉、黄石、十堰、宜昌、襄阳、鄂州、荆门、孝感、荆州、黄冈、咸宁、随州、恩施、仙桃、天门、潜江、神农架")
    print("   湖南省:长沙、株洲、湘潭、衡阳、邵阳、岳阳、常德、张家界、益阳、郴州、永州、怀化、娄底、湘西")
    print("   河南省:郑州、开封、洛阳、平顶山、安阳、鹤壁、新乡、焦作、濮阳、许昌、漯河、三门峡、南阳、商丘、信阳、周口、驻马店")
    print("7. 西南地区:")
    print("   四川省:成都、自贡、攀枝花、泸州、德阳、绵阳、广元、遂宁、内江、乐山、南充、宜宾、广安、达州、雅安、眉山、资阳、阿坝、甘孜、凉山")
    print("   贵州省:贵阳、六盘水、遵义、安顺、毕节、铜仁、黔西南、黔东南、黔南")
    print("   云南省:昆明、曲靖、玉溪、保山、昭通、丽江、普洱、临沧、楚雄、红河、文山、西双版纳、大理、德宏、怒江、迪庆")
    print("   西藏自治区:拉萨、日喀则、昌都、林芝、山南、那曲、阿里")
    print("8. 西北地区:")
    print("   陕西省:西安、铜川、宝鸡、咸阳、渭南、延安、汉中、榆林、安康、商洛")
    print("   甘肃省:兰州、嘉峪关、金昌、白银、天水、武威、张掖、平凉、酒泉、庆阳、定西、陇南、临夏、甘南")
    print("   青海省:西宁、海东、海北、黄南、海南、果洛、玉树、海西")
    print("   宁夏回族自治区:银川、石嘴山、吴忠、固原、中卫")
    print("   新疆维吾尔自治区:乌鲁木齐、克拉玛依、吐鲁番、哈密、昌吉、博尔塔拉、巴音郭楞、阿克苏、克孜勒苏、喀什、和田、伊犁、塔城、阿勒泰、石河子、阿拉尔、图木舒克、五家渠、北屯、铁门关、双河、可克达拉、昆玉、胡杨河")
    print("9. 特别行政区:")
    print("   香港特别行政区:香港、九龙、新界")
    print("   澳门特别行政区:澳门、氹仔、路氹城")
    print("10. 台湾地区:")
    print("    台北、新北、桃园、台中、台南、高雄、基隆、新竹、嘉义")
    print("\n提示:")
    print("- 输入'q'退出程序")
    print("- 输入'list'查看所有支持的城市")
    print("- 支持模糊搜索,如输入'北京'或'beijing'都可以")
    print("- 如果输入的城市不在列表中,系统会自动尝试获取该城市的天气信息\n")
    
    while True:
        try:
            city = input("请输入中国城市名称: ").strip()
            if city.lower() == 'q':
                break
            elif city.lower() == 'list':
                print("\n支持的城市列表:")
                for cn, info in weather_api.city_mapping.items():
                    print(f"- {cn} ({info['name']})")
                print()
                continue
                
            if not city:
                print("城市名称不能为空!")
                continue
                
            weather_data = weather_api.get_weather(city)
            weather_api.display_weather(weather_data)
        except KeyboardInterrupt:
            print("\n程序已退出")
            break
        except Exception as e:
            print(f"发生错误: {e}")
            continue

if __name__ == "__main__":
    main()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Diviner.K-star

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值