first commit

This commit is contained in:
2026-02-09 15:50:41 +08:00
commit 4e49793416
84 changed files with 255593 additions and 0 deletions

298
globals/ex_apis.py Normal file
View File

@@ -0,0 +1,298 @@
# external_apis.py
import requests
import json
import logging
import random
from typing import Dict, Tuple, Optional
import time
class WeatherAPI:
def __init__(self):
self.logger = logging.getLogger(__name__)
# 使用腾讯天气API
self.base_url = "https://wis.qq.com/weather/common"
def parse_city(self, city_string: str) -> Tuple[str, str, str]:
"""
解析城市字符串,返回省份、城市、区县
参数:
city_string: 完整的地址字符串
返回:
(province, city, county)
"""
# 匹配省份或自治区
province_regex = r"(.*?)(省|自治区)"
# 匹配城市或州
city_regex = r"(.*?省|.*?自治区)(.*?市|.*?州)"
# 匹配区、县或镇
county_regex = r"(.*?市|.*?州)(.*?)(区|县|镇)"
province = ""
city = ""
county = ""
import re
# 先尝试匹配省份或自治区
province_match = re.search(province_regex, city_string)
if province_match:
province = province_match.group(1).strip()
# 然后尝试匹配城市或州
city_match = re.search(city_regex, city_string)
if city_match:
city = city_match.group(2).strip()
else:
# 如果没有匹配到城市,则可能是直辖市或者直接是区/县
city = city_string
# 最后尝试匹配区、县或镇
county_match = re.search(county_regex, city_string)
if county_match:
county = county_match.group(2).strip()
# 如果有区、县或镇,那么前面的城市部分需要重新解析
if city_match:
city = city_match.group(2).strip()
# 特殊情况处理,去除重复的省市名称
if city and province and city.startswith(province):
city = city.replace(province, "").strip()
if county and city and county.startswith(city):
county = county.replace(city, "").strip()
# 去除后缀
city = city.rstrip('市州')
if county:
county = county.rstrip('区县镇')
# self.logger.info(f"解析结果 - 省份: {province}, 城市: {city}, 区县: {county}")
return province, city, county
def get_weather_by_qq_api(self, province: str, city: str, county: str) -> Optional[Dict]:
"""
使用腾讯天气API获取天气信息
参数:
province: 省份
city: 城市
county: 区县
返回:
天气信息字典 or None
"""
try:
params = {
'source': 'pc',
'weather_type': 'observe',
'province': province,
'city': city,
'county': county
}
response = requests.get(self.base_url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
if data.get('status') == 200:
observe_data = data.get('data', {}).get('observe', {})
return {
'weather': observe_data.get('weather', ''),
'temperature': observe_data.get('degree', ''),
'pressure': observe_data.get('pressure', '1013')
}
else:
self.logger.error(f"腾讯天气API错误: {data.get('message')}")
return None
except Exception as e:
self.logger.error(f"腾讯天气API调用失败: {str(e)}")
return None
def normalize_weather_text(self, weather_text: str) -> str:
"""
将天气描述标准化为: 晴;阴;雨;雪;风;其他
参数:
weather_text: 原始天气描述
返回:
标准化后的天气文本
"""
if not weather_text:
return '其他'
weather_text_lower = weather_text.lower()
# 晴
if any(word in weather_text_lower for word in ['', 'sunny', 'clear']):
return ''
# 阴
elif any(word in weather_text_lower for word in ['', '多云', 'cloudy', 'overcast']):
return ''
# 雨
elif any(word in weather_text_lower for word in ['', 'rain', 'drizzle', 'shower']):
return ''
# 雪
elif any(word in weather_text_lower for word in ['', 'snow']):
return ''
# 风
elif any(word in weather_text_lower for word in ['', 'wind']):
return ''
# 其他
else:
return '其他'
def adjust_pressure(self, pressure: float) -> float:
"""
调整气压值低于700时填700-750之间随机一个高于700就按实际情况填
参数:
pressure: 原始气压值
返回:
调整后的气压值
"""
try:
pressure_float = float(pressure)
if pressure_float < 700:
adjusted_pressure = random.randint(700, 750)
self.logger.info(f"气压值 {pressure_float} 低于700调整为: {adjusted_pressure:.1f}")
return round(adjusted_pressure, 1)
else:
self.logger.info(f"使用实际气压值: {pressure_float}")
return round(pressure_float, 1)
except (ValueError, TypeError):
self.logger.warning(f"气压值格式错误: {pressure}使用默认值720")
return round(random.randint(700, 750), 1)
def get_weather_by_address(self, address: str, max_retries: int = 2) -> Optional[Dict]:
"""
根据地址获取天气信息
参数:
address: 地址字符串
max_retries: 最大重试次数
返回:
{
'weather': '晴/阴/雨/雪/风/其他',
'temperature': 温度值,
'pressure': 气压值
} or None
"""
# self.logger.info(f"开始获取地址 '{address}' 的天气信息")
# 首先解析地址
province, city, county = self.parse_city(address)
if not province and not city:
self.logger.error("无法解析地址")
return self.get_fallback_weather()
# 获取天气信息
weather_data = None
for attempt in range(max_retries):
try:
# self.logger.info(f"尝试获取天气信息 (第{attempt + 1}次)")
weather_data = self.get_weather_by_qq_api(province, city, county)
if weather_data:
break
time.sleep(1) # 短暂延迟后重试
except Exception as e:
self.logger.warning(f"{attempt + 1}次尝试失败: {str(e)}")
time.sleep(1)
if not weather_data:
self.logger.warning("获取天气信息失败,使用备用数据")
return self.get_fallback_weather()
# 处理天气数据
try:
# 标准化天气文本
normalized_weather = self.normalize_weather_text(weather_data['weather'])
# 调整气压值
adjusted_pressure = self.adjust_pressure(weather_data['pressure'])
# 处理温度
temperature = float(weather_data['temperature'])
result = {
'weather': normalized_weather,
'temperature': round(temperature, 1),
'pressure': adjusted_pressure
}
self.logger.info(f"成功获取天气信息: {result}")
return result
except Exception as e:
self.logger.error(f"处理天气数据时出错: {str(e)}")
return self.get_fallback_weather()
def get_fallback_weather(self) -> Dict:
"""
获取备用天气数据当所有API都失败时使用
返回:
默认天气数据
"""
self.logger.info("使用备用天气数据")
return {
'weather': '',
'temperature': round(random.randint(15, 30), 1),
'pressure': round(random.randint(700, 750), 1)
}
def get_weather_simple(self, address: str) -> Tuple[str, float, float]:
"""
简化接口:直接返回天气、温度、气压
参数:
address: 地址字符串
返回:
(weather, temperature, pressure)
"""
weather_data = self.get_weather_by_address(address)
if weather_data:
return weather_data['weather'], weather_data['temperature'], weather_data['pressure']
else:
fallback = self.get_fallback_weather()
return fallback['weather'], fallback['temperature'], fallback['pressure']
# 创建全局实例
weather_api = WeatherAPI()
# 直接可用的函数
def get_weather_by_address(address: str) -> Optional[Dict]:
"""
根据地址获取天气信息(直接调用函数)
参数:
address: 地址字符串
返回:
{
'weather': '晴/阴/雨/雪/风/其他',
'temperature': 温度值,
'pressure': 气压值
} or None
"""
return weather_api.get_weather_by_address(address)
def get_weather_simple(address: str) -> Tuple[str, float, float]:
"""
简化接口:直接返回天气、温度、气压
参数:
address: 地址字符串
返回:
(weather, temperature, pressure)
"""
return weather_api.get_weather_simple(address)