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()