Files
cjgc_upload/globals/ex_apis.py
2026-02-02 11:47:53 +08:00

299 lines
9.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)