remove
This commit is contained in:
parent
f3165944d2
commit
2cc8b893f7
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "weather",
|
|
||||||
"description": "提供中国天气预报查询功能,支持全国300+城市的当前天气和未来1/3/7/15/40天天气预报查询。",
|
|
||||||
"mcpServers": {
|
|
||||||
"weather": {
|
|
||||||
"transport": "stdio",
|
|
||||||
"command": "python",
|
|
||||||
"args": [
|
|
||||||
"./skills/weather/mcp.py"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,149 +0,0 @@
|
|||||||
---
|
|
||||||
name: weather
|
|
||||||
description: 提供中国天气预报查询功能,支持全国300+城市的当前天气和未来1/3/7/15/40天天气预报查询。当需要查询天气情况、温度、风向等信息时使用。
|
|
||||||
---
|
|
||||||
|
|
||||||
# 天气预报查询 (Weather)
|
|
||||||
|
|
||||||
## 功能概述
|
|
||||||
|
|
||||||
天气预报查询 skill 提供中国天气网数据查询服务,支持全国城市天气查询、当前天气查询和城市搜索功能。
|
|
||||||
|
|
||||||
## 命令行调用
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查询杭州7天天气预报
|
|
||||||
python scripts/weather_query.py query 杭州
|
|
||||||
|
|
||||||
# 查询北京3天天气预报
|
|
||||||
python scripts/weather_query.py query 北京 --days 3
|
|
||||||
|
|
||||||
# 查询深圳当前天气
|
|
||||||
python scripts/weather_query.py query 深圳 --current
|
|
||||||
|
|
||||||
# 搜索带"州"字的城市
|
|
||||||
python scripts/weather_query.py search 州
|
|
||||||
|
|
||||||
# 输出JSON格式的天气数据
|
|
||||||
python scripts/weather_query.py json 杭州 --days 3
|
|
||||||
```
|
|
||||||
|
|
||||||
## 命令列表
|
|
||||||
|
|
||||||
| 命令 | 功能 | 说明 |
|
|
||||||
|-----|------|-----|
|
|
||||||
| `query` | 天气预报查询 | 查询指定城市的天气预报或当前天气 |
|
|
||||||
| `search` | 城市搜索 | 根据关键词搜索城市 |
|
|
||||||
| `json` | JSON输出 | 输出JSON格式的天气数据 |
|
|
||||||
|
|
||||||
## 参数说明
|
|
||||||
|
|
||||||
### query 命令参数
|
|
||||||
|
|
||||||
| 参数 | 必填 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| `city` | 是 | 城市名称,如"杭州"、"北京"、"上海" |
|
|
||||||
| `--days` | 否 | 查询天数,可选1/3/7/15/40,默认7天 |
|
|
||||||
| `--current`, `-c` | 否 | 查询当前天气而非预报 |
|
|
||||||
|
|
||||||
### search 命令参数
|
|
||||||
|
|
||||||
| 参数 | 必填 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| `keyword` | 是 | 搜索关键词 |
|
|
||||||
| `--limit` | 否 | 返回结果数量限制,默认20 |
|
|
||||||
|
|
||||||
### json 命令参数
|
|
||||||
|
|
||||||
| 参数 | 必填 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| `city` | 是 | 城市名称 |
|
|
||||||
| `--days` | 否 | 查询天数,默认7天 |
|
|
||||||
|
|
||||||
## 返回数据格式
|
|
||||||
|
|
||||||
### 天气预报文本输出示例
|
|
||||||
|
|
||||||
```
|
|
||||||
📍 杭州 天气预报
|
|
||||||
==============================
|
|
||||||
|
|
||||||
📅 15日(今天) (今天)
|
|
||||||
🌤️ 晴
|
|
||||||
🌡️ 21/8℃
|
|
||||||
💨 东北风 <3级
|
|
||||||
|
|
||||||
📅 16日(明天)
|
|
||||||
🌤️ 晴转多云
|
|
||||||
🌡️ 21/9℃
|
|
||||||
💨 东风 <3级
|
|
||||||
|
|
||||||
📅 17日(后天)
|
|
||||||
🌤️ 小雨转多云
|
|
||||||
🌡️ 13/9℃
|
|
||||||
💨 东风 <3级
|
|
||||||
|
|
||||||
⏰ 更新时间: 07:30
|
|
||||||
```
|
|
||||||
|
|
||||||
### JSON输出示例
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"city": "杭州",
|
|
||||||
"update_time": "07:30",
|
|
||||||
"forecast": [
|
|
||||||
{
|
|
||||||
"date": "15日(今天)",
|
|
||||||
"weather": "晴",
|
|
||||||
"temperature": "21/8℃",
|
|
||||||
"wind": "东北风 <3级"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "16日(明天)",
|
|
||||||
"weather": "晴转多云",
|
|
||||||
"temperature": "21/9℃",
|
|
||||||
"wind": "东风 <3级"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"date": "17日(后天)",
|
|
||||||
"weather": "小雨转多云",
|
|
||||||
"temperature": "13/9℃",
|
|
||||||
"wind": "东风 <3级"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 支持的城市
|
|
||||||
|
|
||||||
覆盖全国34个省级行政区的300+主要城市,包括:
|
|
||||||
|
|
||||||
- **直辖市**: 北京、上海、天津、重庆
|
|
||||||
- **省会城市**: 广州、成都、武汉、西安、南京等
|
|
||||||
- **地级市**: 深圳、宁波、青岛、大连等
|
|
||||||
- **区县级**: 杭州下辖的萧山、余杭、临安等
|
|
||||||
|
|
||||||
使用 `search` 命令可查询完整城市列表。
|
|
||||||
|
|
||||||
## 城市编码规则
|
|
||||||
|
|
||||||
城市编码格式:`101XXYYZZ`
|
|
||||||
|
|
||||||
| 部分 | 说明 | 示例 |
|
|
||||||
|------|------|------|
|
|
||||||
| XX | 省份代码 (01-34) | 浙江=21 |
|
|
||||||
| YY | 城市代码 | 杭州=01 |
|
|
||||||
| ZZ | 区县代码 | 城区=01 |
|
|
||||||
|
|
||||||
示例:
|
|
||||||
- 杭州:101210101
|
|
||||||
- 萧山:101210102
|
|
||||||
- 北京:101010100
|
|
||||||
|
|
||||||
## 约束条件
|
|
||||||
|
|
||||||
- 数据来源于中国天气网 (www.weather.com.cn)
|
|
||||||
- 数据仅供参考,请以官方发布为准
|
|
||||||
- 每日更新时间约为07:30和11:00
|
|
||||||
- 支持1天、3天、7天、15天、40天预报查询
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
天气查询MCP服务器
|
|
||||||
提供中国天气网天气预报查询功能
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
# 将 scripts 目录加入 sys.path,以便导入 weather 模块
|
|
||||||
SCRIPTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "scripts")
|
|
||||||
if SCRIPTS_DIR not in sys.path:
|
|
||||||
sys.path.insert(0, SCRIPTS_DIR)
|
|
||||||
|
|
||||||
# 将 mcp 目录加入 sys.path,以便导入 mcp_common
|
|
||||||
MCP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "mcp")
|
|
||||||
MCP_DIR = os.path.abspath(MCP_DIR)
|
|
||||||
if MCP_DIR not in sys.path:
|
|
||||||
sys.path.insert(0, MCP_DIR)
|
|
||||||
|
|
||||||
from mcp_common import (
|
|
||||||
create_error_response,
|
|
||||||
create_initialize_response,
|
|
||||||
create_ping_response,
|
|
||||||
create_tools_list_response,
|
|
||||||
handle_mcp_streaming,
|
|
||||||
)
|
|
||||||
|
|
||||||
from weather import query_weather, find_city_code
|
|
||||||
|
|
||||||
|
|
||||||
# MCP 工具定义
|
|
||||||
WEATHER_TOOLS = [
|
|
||||||
{
|
|
||||||
"name": "weather_query",
|
|
||||||
"description": "查询指定城市的天气预报,支持1/3/7/15/40天预报。返回格式化的天气文本,包含日期、天气状况、温度、风向等信息。",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"city": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "城市名称,如:杭州、北京、上海、深圳"
|
|
||||||
},
|
|
||||||
"days": {
|
|
||||||
"type": "integer",
|
|
||||||
"description": "查询天数,可选1/3/7/15/40,默认7天",
|
|
||||||
"enum": [1, 3, 7, 15, 40],
|
|
||||||
"default": 7
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["city"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def handle_weather_query(city: str, days: int = 7) -> Dict[str, Any]:
|
|
||||||
"""处理天气预报查询"""
|
|
||||||
try:
|
|
||||||
city_code = find_city_code(city)
|
|
||||||
if not city_code:
|
|
||||||
return {
|
|
||||||
"content": [{"type": "text", "text": f"未找到城市 \"{city}\""}]
|
|
||||||
}
|
|
||||||
|
|
||||||
result = query_weather(city, days)
|
|
||||||
if result:
|
|
||||||
return {"content": [{"type": "text", "text": result}]}
|
|
||||||
else:
|
|
||||||
return {"content": [{"type": "text", "text": f"无法获取 {city} 的天气预报"}]}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return {"content": [{"type": "text", "text": f"查询天气预报出错: {str(e)}"}]}
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]:
|
|
||||||
"""Handle MCP request"""
|
|
||||||
try:
|
|
||||||
method = request.get("method")
|
|
||||||
params = request.get("params", {})
|
|
||||||
request_id = request.get("id")
|
|
||||||
|
|
||||||
if method == "initialize":
|
|
||||||
return create_initialize_response(request_id, "weather-query")
|
|
||||||
|
|
||||||
elif method == "ping":
|
|
||||||
return create_ping_response(request_id)
|
|
||||||
|
|
||||||
elif method == "tools/list":
|
|
||||||
return create_tools_list_response(request_id, WEATHER_TOOLS)
|
|
||||||
|
|
||||||
elif method == "tools/call":
|
|
||||||
tool_name = params.get("name")
|
|
||||||
arguments = params.get("arguments", {})
|
|
||||||
|
|
||||||
if tool_name == "weather_query":
|
|
||||||
city = arguments.get("city", "")
|
|
||||||
days = arguments.get("days", 7)
|
|
||||||
if not city:
|
|
||||||
return create_error_response(request_id, -32602, "Missing required parameter: city")
|
|
||||||
result = handle_weather_query(city, days)
|
|
||||||
return {"jsonrpc": "2.0", "id": request_id, "result": result}
|
|
||||||
|
|
||||||
else:
|
|
||||||
return create_error_response(request_id, -32601, f"Unknown tool: {tool_name}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
return create_error_response(request_id, -32601, f"Unknown method: {method}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return create_error_response(request.get("id"), -32603, f"Internal error: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
"""Main entry point."""
|
|
||||||
await handle_mcp_streaming(handle_request)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
中国天气网天气查询模块
|
|
||||||
提供全国城市天气预报查询功能
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .query import (
|
|
||||||
WeatherQuery,
|
|
||||||
WeatherInfo,
|
|
||||||
query_weather,
|
|
||||||
query_current_weather,
|
|
||||||
search_city,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .city_codes import (
|
|
||||||
CITY_CODES,
|
|
||||||
PROVINCE_CODES,
|
|
||||||
CITY_NAME_TO_CODE,
|
|
||||||
find_city_code,
|
|
||||||
get_city_name,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
# 查询类
|
|
||||||
"WeatherQuery",
|
|
||||||
"WeatherInfo",
|
|
||||||
# 便捷函数
|
|
||||||
"query_weather",
|
|
||||||
"query_current_weather",
|
|
||||||
"search_city",
|
|
||||||
# 城市编码
|
|
||||||
"CITY_CODES",
|
|
||||||
"PROVINCE_CODES",
|
|
||||||
"CITY_NAME_TO_CODE",
|
|
||||||
"find_city_code",
|
|
||||||
"get_city_name",
|
|
||||||
]
|
|
||||||
|
|
||||||
__version__ = "1.0.0"
|
|
||||||
__author__ = "Weather Skill"
|
|
||||||
@ -1,525 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
中国天气网城市编码数据库
|
|
||||||
数据来源:www.weather.com.cn
|
|
||||||
城市编码格式:101XXYYZZ
|
|
||||||
- XX:省份代码(01-34)
|
|
||||||
- YY:城市代码
|
|
||||||
- ZZ:区县代码
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 省级编码
|
|
||||||
PROVINCE_CODES = {
|
|
||||||
"10101": "北京",
|
|
||||||
"10102": "上海",
|
|
||||||
"10103": "天津",
|
|
||||||
"10104": "重庆",
|
|
||||||
"10105": "黑龙江",
|
|
||||||
"10106": "吉林",
|
|
||||||
"10107": "辽宁",
|
|
||||||
"10108": "内蒙古",
|
|
||||||
"10109": "河北",
|
|
||||||
"10110": "山西",
|
|
||||||
"10111": "陕西",
|
|
||||||
"10112": "山东",
|
|
||||||
"10113": "新疆",
|
|
||||||
"10114": "西藏",
|
|
||||||
"10115": "青海",
|
|
||||||
"10116": "甘肃",
|
|
||||||
"10117": "宁夏",
|
|
||||||
"10118": "河南",
|
|
||||||
"10119": "江苏",
|
|
||||||
"10120": "湖北",
|
|
||||||
"10121": "浙江",
|
|
||||||
"10122": "安徽",
|
|
||||||
"10123": "福建",
|
|
||||||
"10124": "江西",
|
|
||||||
"10125": "湖南",
|
|
||||||
"10126": "贵州",
|
|
||||||
"10127": "四川",
|
|
||||||
"10128": "广东",
|
|
||||||
"10129": "云南",
|
|
||||||
"10130": "广西",
|
|
||||||
"10131": "海南",
|
|
||||||
"10132": "香港",
|
|
||||||
"10133": "澳门",
|
|
||||||
"10134": "台湾",
|
|
||||||
}
|
|
||||||
|
|
||||||
# 主要城市编码(市级主城区)
|
|
||||||
CITY_CODES = {
|
|
||||||
# 北京市
|
|
||||||
"101010100": "北京",
|
|
||||||
# 上海市
|
|
||||||
"101020100": "上海",
|
|
||||||
# 天津市
|
|
||||||
"101030100": "天津",
|
|
||||||
# 重庆市
|
|
||||||
"101040100": "重庆",
|
|
||||||
|
|
||||||
# 浙江省
|
|
||||||
"101210101": "杭州",
|
|
||||||
"101210102": "萧山",
|
|
||||||
"101210103": "桐庐",
|
|
||||||
"101210104": "淳安",
|
|
||||||
"101210105": "建德",
|
|
||||||
"101210106": "余杭",
|
|
||||||
"101210107": "临安",
|
|
||||||
"101210108": "富阳",
|
|
||||||
"101210201": "湖州",
|
|
||||||
"101210301": "嘉兴",
|
|
||||||
"101210401": "宁波",
|
|
||||||
"101210501": "绍兴",
|
|
||||||
"101210601": "台州",
|
|
||||||
"101210701": "温州",
|
|
||||||
"101210801": "丽水",
|
|
||||||
"101210901": "金华",
|
|
||||||
"101211001": "衢州",
|
|
||||||
"101211101": "舟山",
|
|
||||||
|
|
||||||
# 江苏省
|
|
||||||
"101190101": "南京",
|
|
||||||
"101190401": "无锡",
|
|
||||||
"101190501": "常州",
|
|
||||||
"101190601": "苏州",
|
|
||||||
"101190801": "南通",
|
|
||||||
"101190901": "连云港",
|
|
||||||
"101191001": "淮安",
|
|
||||||
"101191101": "盐城",
|
|
||||||
"101191201": "扬州",
|
|
||||||
"101191301": "镇江",
|
|
||||||
"101191401": "泰州",
|
|
||||||
"101191501": "宿迁",
|
|
||||||
|
|
||||||
# 广东省
|
|
||||||
"101280101": "广州",
|
|
||||||
"101280601": "深圳",
|
|
||||||
"101280701": "珠海",
|
|
||||||
"101280801": "汕头",
|
|
||||||
"101280901": "佛山",
|
|
||||||
"101281001": "韶关",
|
|
||||||
"101281101": "湛江",
|
|
||||||
"101281201": "肇庆",
|
|
||||||
"101281301": "江门",
|
|
||||||
"101281401": "茂名",
|
|
||||||
"101281501": "惠州",
|
|
||||||
"101281601": "梅州",
|
|
||||||
"101281701": "汕尾",
|
|
||||||
"101281801": "河源",
|
|
||||||
"101281901": "阳江",
|
|
||||||
"101282001": "清远",
|
|
||||||
"101282101": "东莞",
|
|
||||||
"101282201": "中山",
|
|
||||||
"101282301": "潮州",
|
|
||||||
"101282401": "揭阳",
|
|
||||||
"101282501": "云浮",
|
|
||||||
|
|
||||||
# 四川省
|
|
||||||
"101270101": "成都",
|
|
||||||
"101270201": "自贡",
|
|
||||||
"101270301": "攀枝花",
|
|
||||||
"101270401": "泸州",
|
|
||||||
"101270501": "德阳",
|
|
||||||
"101270601": "绵阳",
|
|
||||||
"101270701": "广元",
|
|
||||||
"101270801": "遂宁",
|
|
||||||
"101270901": "内江",
|
|
||||||
"101271001": "乐山",
|
|
||||||
"101271101": "南充",
|
|
||||||
"101271201": "眉山",
|
|
||||||
"101271301": "宜宾",
|
|
||||||
"101271401": "广安",
|
|
||||||
"101271501": "达州",
|
|
||||||
"101271601": "雅安",
|
|
||||||
"101271701": "巴中",
|
|
||||||
"101271801": "资阳",
|
|
||||||
"101271906": "阿坝",
|
|
||||||
"101272001": "甘孜",
|
|
||||||
"101272101": "凉山",
|
|
||||||
|
|
||||||
# 湖北省
|
|
||||||
"101200101": "武汉",
|
|
||||||
"101200201": "黄石",
|
|
||||||
"101200301": "十堰",
|
|
||||||
"101200401": "宜昌",
|
|
||||||
"101200501": "襄阳",
|
|
||||||
"101200601": "鄂州",
|
|
||||||
"101200701": "荆门",
|
|
||||||
"101200801": "孝感",
|
|
||||||
"101200901": "荆州",
|
|
||||||
"101201001": "黄冈",
|
|
||||||
"101201101": "咸宁",
|
|
||||||
"101201201": "随州",
|
|
||||||
"101201301": "恩施",
|
|
||||||
|
|
||||||
# 湖南省
|
|
||||||
"101250101": "长沙",
|
|
||||||
"101250201": "株洲",
|
|
||||||
"101250301": "湘潭",
|
|
||||||
"101250401": "衡阳",
|
|
||||||
"101250501": "邵阳",
|
|
||||||
"101250601": "岳阳",
|
|
||||||
"101250701": "常德",
|
|
||||||
"101250801": "张家界",
|
|
||||||
"101250901": "益阳",
|
|
||||||
"101251001": "郴州",
|
|
||||||
"101251101": "永州",
|
|
||||||
"101251201": "怀化",
|
|
||||||
"101251301": "娄底",
|
|
||||||
"101251401": "湘西",
|
|
||||||
|
|
||||||
# 河南省
|
|
||||||
"101180101": "郑州",
|
|
||||||
"101180201": "开封",
|
|
||||||
"101180301": "洛阳",
|
|
||||||
"101180401": "平顶山",
|
|
||||||
"101180501": "安阳",
|
|
||||||
"101180601": "鹤壁",
|
|
||||||
"101180701": "新乡",
|
|
||||||
"101180801": "焦作",
|
|
||||||
"101180901": "濮阳",
|
|
||||||
"101181001": "许昌",
|
|
||||||
"101181101": "漯河",
|
|
||||||
"101181201": "三门峡",
|
|
||||||
"101181301": "南阳",
|
|
||||||
"101181401": "商丘",
|
|
||||||
"101181501": "信阳",
|
|
||||||
"101181601": "周口",
|
|
||||||
"101181701": "驻马店",
|
|
||||||
"101181801": "济源",
|
|
||||||
|
|
||||||
# 山东省
|
|
||||||
"101120101": "济南",
|
|
||||||
"101120201": "青岛",
|
|
||||||
"101120301": "淄博",
|
|
||||||
"101120401": "枣庄",
|
|
||||||
"101120501": "东营",
|
|
||||||
"101120601": "烟台",
|
|
||||||
"101120701": "潍坊",
|
|
||||||
"101120801": "济宁",
|
|
||||||
"101120901": "泰安",
|
|
||||||
"101121001": "威海",
|
|
||||||
"101121101": "日照",
|
|
||||||
"101121201": "临沂",
|
|
||||||
"101121301": "德州",
|
|
||||||
"101121401": "聊城",
|
|
||||||
"101121501": "滨州",
|
|
||||||
"101121601": "菏泽",
|
|
||||||
|
|
||||||
# 福建省
|
|
||||||
"101230101": "福州",
|
|
||||||
"101230201": "厦门",
|
|
||||||
"101230301": "莆田",
|
|
||||||
"101230401": "三明",
|
|
||||||
"101230501": "泉州",
|
|
||||||
"101230601": "漳州",
|
|
||||||
"101230701": "南平",
|
|
||||||
"101230801": "龙岩",
|
|
||||||
"101230901": "宁德",
|
|
||||||
|
|
||||||
# 安徽省
|
|
||||||
"101220101": "合肥",
|
|
||||||
"101220201": "芜湖",
|
|
||||||
"101220301": "蚌埠",
|
|
||||||
"101220401": "淮南",
|
|
||||||
"101220501": "马鞍山",
|
|
||||||
"101220601": "淮北",
|
|
||||||
"101220701": "铜陵",
|
|
||||||
"101220801": "安庆",
|
|
||||||
"101220901": "黄山",
|
|
||||||
"101221001": "滁州",
|
|
||||||
"101221101": "阜阳",
|
|
||||||
"101221201": "宿州",
|
|
||||||
"101221301": "六安",
|
|
||||||
"101221401": "亳州",
|
|
||||||
"101221501": "池州",
|
|
||||||
"101221601": "宣城",
|
|
||||||
|
|
||||||
# 陕西省
|
|
||||||
"101110101": "西安",
|
|
||||||
"101110201": "铜川",
|
|
||||||
"101110301": "宝鸡",
|
|
||||||
"101110401": "咸阳",
|
|
||||||
"101110501": "渭南",
|
|
||||||
"101110601": "延安",
|
|
||||||
"101110701": "汉中",
|
|
||||||
"101110801": "榆林",
|
|
||||||
"101110901": "安康",
|
|
||||||
"101111001": "商洛",
|
|
||||||
|
|
||||||
# 辽宁省
|
|
||||||
"101070101": "沈阳",
|
|
||||||
"101070201": "大连",
|
|
||||||
"101070301": "鞍山",
|
|
||||||
"101070401": "抚顺",
|
|
||||||
"101070501": "本溪",
|
|
||||||
"101070601": "丹东",
|
|
||||||
"101070701": "锦州",
|
|
||||||
"101070801": "营口",
|
|
||||||
"101070901": "阜新",
|
|
||||||
"101071001": "辽阳",
|
|
||||||
"101071101": "盘锦",
|
|
||||||
"101071201": "铁岭",
|
|
||||||
"101071301": "朝阳",
|
|
||||||
"101071401": "葫芦岛",
|
|
||||||
|
|
||||||
# 江西省
|
|
||||||
"101240101": "南昌",
|
|
||||||
"101240201": "景德镇",
|
|
||||||
"101240301": "萍乡",
|
|
||||||
"101240401": "九江",
|
|
||||||
"101240501": "新余",
|
|
||||||
"101240601": "鹰潭",
|
|
||||||
"101240701": "赣州",
|
|
||||||
"101240801": "吉安",
|
|
||||||
"101240901": "宜春",
|
|
||||||
"101241001": "抚州",
|
|
||||||
"101241101": "上饶",
|
|
||||||
|
|
||||||
# 云南省
|
|
||||||
"101290101": "昆明",
|
|
||||||
"101290201": "曲靖",
|
|
||||||
"101290301": "玉溪",
|
|
||||||
"101290401": "保山",
|
|
||||||
"101290501": "昭通",
|
|
||||||
"101290601": "丽江",
|
|
||||||
"101290701": "普洱",
|
|
||||||
"101290801": "临沧",
|
|
||||||
"101290901": "楚雄",
|
|
||||||
"101291001": "红河",
|
|
||||||
"101291101": "文山",
|
|
||||||
"101291201": "西双版纳",
|
|
||||||
"101291301": "大理",
|
|
||||||
"101291401": "德宏",
|
|
||||||
"101291501": "怒江",
|
|
||||||
"101291601": "迪庆",
|
|
||||||
|
|
||||||
# 广西省
|
|
||||||
"101300101": "南宁",
|
|
||||||
"101300201": "柳州",
|
|
||||||
"101300301": "桂林",
|
|
||||||
"101300401": "梧州",
|
|
||||||
"101300501": "北海",
|
|
||||||
"101300601": "防城港",
|
|
||||||
"101300701": "钦州",
|
|
||||||
"101300801": "贵港",
|
|
||||||
"101300901": "玉林",
|
|
||||||
"101301001": "百色",
|
|
||||||
"101301101": "贺州",
|
|
||||||
"101301201": "河池",
|
|
||||||
"101301301": "来宾",
|
|
||||||
"101301401": "崇左",
|
|
||||||
|
|
||||||
# 贵州省
|
|
||||||
"101260101": "贵阳",
|
|
||||||
"101260201": "六盘水",
|
|
||||||
"101260301": "遵义",
|
|
||||||
"101260401": "安顺",
|
|
||||||
"101260501": "毕节",
|
|
||||||
"101260601": "铜仁",
|
|
||||||
"101260901": "黔西南",
|
|
||||||
"101261001": "黔东南",
|
|
||||||
"101261101": "黔南",
|
|
||||||
|
|
||||||
# 河北省
|
|
||||||
"101090101": "石家庄",
|
|
||||||
"101090201": "唐山",
|
|
||||||
"101090301": "秦皇岛",
|
|
||||||
"101090401": "邯郸",
|
|
||||||
"101090501": "邢台",
|
|
||||||
"101090601": "保定",
|
|
||||||
"101090701": "张家口",
|
|
||||||
"101090801": "承德",
|
|
||||||
"101090901": "沧州",
|
|
||||||
"101091001": "廊坊",
|
|
||||||
"101091101": "衡水",
|
|
||||||
|
|
||||||
# 山西省
|
|
||||||
"101100101": "太原",
|
|
||||||
"101100201": "大同",
|
|
||||||
"101100301": "阳泉",
|
|
||||||
"101100401": "长治",
|
|
||||||
"101100501": "晋城",
|
|
||||||
"101100601": "朔州",
|
|
||||||
"101100701": "晋中",
|
|
||||||
"101100801": "运城",
|
|
||||||
"101100901": "忻州",
|
|
||||||
"101101001": "临汾",
|
|
||||||
"101101101": "吕梁",
|
|
||||||
|
|
||||||
# 黑龙江省
|
|
||||||
"101050101": "哈尔滨",
|
|
||||||
"101050201": "齐齐哈尔",
|
|
||||||
"101050301": "鸡西",
|
|
||||||
"101050401": "鹤岗",
|
|
||||||
"101050501": "双鸭山",
|
|
||||||
"101050601": "大庆",
|
|
||||||
"101050701": "伊春",
|
|
||||||
"101050801": "佳木斯",
|
|
||||||
"101050901": "七台河",
|
|
||||||
"101051001": "牡丹江",
|
|
||||||
"101051101": "黑河",
|
|
||||||
"101051201": "绥化",
|
|
||||||
"101051301": "大兴安岭",
|
|
||||||
|
|
||||||
# 吉林省
|
|
||||||
"101060101": "长春",
|
|
||||||
"101060201": "吉林",
|
|
||||||
"101060301": "四平",
|
|
||||||
"101060401": "辽源",
|
|
||||||
"101060501": "通化",
|
|
||||||
"101060601": "白山",
|
|
||||||
"101060701": "松原",
|
|
||||||
"101060801": "白城",
|
|
||||||
"101060901": "延边",
|
|
||||||
|
|
||||||
# 内蒙古
|
|
||||||
"101080101": "呼和浩特",
|
|
||||||
"101080201": "包头",
|
|
||||||
"101080301": "乌海",
|
|
||||||
"101080401": "赤峰",
|
|
||||||
"101080501": "通辽",
|
|
||||||
"101080601": "鄂尔多斯",
|
|
||||||
"101080701": "呼伦贝尔",
|
|
||||||
"101080801": "巴彦淖尔",
|
|
||||||
"101080901": "乌兰察布",
|
|
||||||
"101081001": "兴安",
|
|
||||||
"101081101": "锡林郭勒",
|
|
||||||
"101081201": "阿拉善",
|
|
||||||
|
|
||||||
# 新疆
|
|
||||||
"101130101": "乌鲁木齐",
|
|
||||||
"101130201": "克拉玛依",
|
|
||||||
"101130301": "吐鲁番",
|
|
||||||
"101130401": "哈密",
|
|
||||||
"101130501": "昌吉",
|
|
||||||
"101130601": "博尔塔拉",
|
|
||||||
"101130701": "巴音郭楞",
|
|
||||||
"101130801": "阿克苏",
|
|
||||||
"101130901": "克孜勒苏",
|
|
||||||
"101131001": "喀什",
|
|
||||||
"101131101": "和田",
|
|
||||||
"101131201": "伊犁",
|
|
||||||
"101131301": "塔城",
|
|
||||||
"101131401": "阿勒泰",
|
|
||||||
"101131501": "石河子",
|
|
||||||
"101131601": "阿拉尔",
|
|
||||||
"101131701": "图木舒克",
|
|
||||||
"101131801": "五家渠",
|
|
||||||
"101131901": "北屯",
|
|
||||||
"101132001": "铁门关",
|
|
||||||
"101132101": "双河",
|
|
||||||
"101132201": "可克达拉",
|
|
||||||
"101132301": "昆玉",
|
|
||||||
"101132401": "胡杨河",
|
|
||||||
|
|
||||||
# 西藏
|
|
||||||
"101140101": "拉萨",
|
|
||||||
"101140201": "日喀则",
|
|
||||||
"101140301": "昌都",
|
|
||||||
"101140401": "林芝",
|
|
||||||
"101140501": "山南",
|
|
||||||
"101140601": "那曲",
|
|
||||||
"101140701": "阿里",
|
|
||||||
|
|
||||||
# 青海省
|
|
||||||
"101150101": "西宁",
|
|
||||||
"101150201": "海东",
|
|
||||||
"101150301": "海北",
|
|
||||||
"101150401": "黄南",
|
|
||||||
"101150501": "海南",
|
|
||||||
"101150601": "果洛",
|
|
||||||
"101150701": "玉树",
|
|
||||||
"101150801": "海西",
|
|
||||||
|
|
||||||
# 甘肃省
|
|
||||||
"101160101": "兰州",
|
|
||||||
"101160201": "嘉峪关",
|
|
||||||
"101160301": "金昌",
|
|
||||||
"101160401": "白银",
|
|
||||||
"101160501": "天水",
|
|
||||||
"101160601": "武威",
|
|
||||||
"101160701": "张掖",
|
|
||||||
"101160801": "平凉",
|
|
||||||
"101160901": "酒泉",
|
|
||||||
"101161001": "庆阳",
|
|
||||||
"101161101": "定西",
|
|
||||||
"101161201": "陇南",
|
|
||||||
"101161301": "临夏",
|
|
||||||
"101161401": "甘南",
|
|
||||||
|
|
||||||
# 宁夏
|
|
||||||
"101170101": "银川",
|
|
||||||
"101170201": "石嘴山",
|
|
||||||
"101170301": "吴忠",
|
|
||||||
"101170401": "固原",
|
|
||||||
"101170501": "中卫",
|
|
||||||
|
|
||||||
# 海南省
|
|
||||||
"101310101": "海口",
|
|
||||||
"101310201": "三亚",
|
|
||||||
"101310301": "三沙",
|
|
||||||
"101310401": "儋州",
|
|
||||||
"101310501": "五指山",
|
|
||||||
"101310601": "琼海",
|
|
||||||
"101310701": "文昌",
|
|
||||||
"101310801": "万宁",
|
|
||||||
"101310901": "东方",
|
|
||||||
"101311001": "定安",
|
|
||||||
"101311101": "屯昌",
|
|
||||||
"101311201": "澄迈",
|
|
||||||
"101311301": "临高",
|
|
||||||
"101311401": "白沙",
|
|
||||||
"101311501": "昌江",
|
|
||||||
"101311601": "乐东",
|
|
||||||
"101311701": "陵水",
|
|
||||||
"101311801": "保亭",
|
|
||||||
"101311901": "琼中",
|
|
||||||
}
|
|
||||||
|
|
||||||
# 城市名称到编码的映射(用于模糊搜索)
|
|
||||||
CITY_NAME_TO_CODE = {v: k for k, v in CITY_CODES.items()}
|
|
||||||
|
|
||||||
|
|
||||||
def find_city_code(city_name: str) -> str:
|
|
||||||
"""
|
|
||||||
根据城市名称查找编码
|
|
||||||
|
|
||||||
Args:
|
|
||||||
city_name: 城市名称
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
城市编码,未找到返回 None
|
|
||||||
"""
|
|
||||||
# 精确匹配
|
|
||||||
if city_name in CITY_NAME_TO_CODE:
|
|
||||||
return CITY_NAME_TO_CODE[city_name]
|
|
||||||
|
|
||||||
# 模糊匹配
|
|
||||||
for name, code in CITY_NAME_TO_CODE.items():
|
|
||||||
if city_name in name or name in city_name:
|
|
||||||
return code
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def get_city_name(code: str) -> str:
|
|
||||||
"""
|
|
||||||
根据编码获取城市名称
|
|
||||||
|
|
||||||
Args:
|
|
||||||
code: 城市编码
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
城市名称,未找到返回 None
|
|
||||||
"""
|
|
||||||
return CITY_CODES.get(code)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# 测试代码
|
|
||||||
print("测试城市编码查询:")
|
|
||||||
test_cities = ["杭州", "北京", "上海", "深圳", "成都"]
|
|
||||||
for city in test_cities:
|
|
||||||
code = find_city_code(city)
|
|
||||||
print(f"{city}: {code}")
|
|
||||||
@ -1,399 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
中国天气网天气查询模块
|
|
||||||
数据来源:www.weather.com.cn
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
from typing import Optional, Dict, Any, List
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
from .city_codes import find_city_code, get_city_name, CITY_CODES
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class WeatherInfo:
|
|
||||||
"""天气信息数据类"""
|
|
||||||
city: str # 城市名称
|
|
||||||
date: str # 日期
|
|
||||||
weather: str # 天气状况
|
|
||||||
temperature: str # 温度范围
|
|
||||||
wind: str # 风向风力
|
|
||||||
humidity: str = "" # 湿度
|
|
||||||
update_time: str = "" # 更新时间
|
|
||||||
|
|
||||||
|
|
||||||
class WeatherQuery:
|
|
||||||
"""天气查询类"""
|
|
||||||
|
|
||||||
BASE_URL = "https://www.weather.com.cn"
|
|
||||||
HEADERS = {
|
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
||||||
"Referer": "https://www.weather.com.cn/",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, timeout: int = 10):
|
|
||||||
"""
|
|
||||||
初始化天气查询
|
|
||||||
|
|
||||||
Args:
|
|
||||||
timeout: 请求超时时间(秒)
|
|
||||||
"""
|
|
||||||
self.timeout = timeout
|
|
||||||
self.client = httpx.Client(headers=self.HEADERS, timeout=timeout, follow_redirects=True)
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
"""关闭客户端"""
|
|
||||||
if hasattr(self, 'client'):
|
|
||||||
self.client.close()
|
|
||||||
|
|
||||||
def _parse_weather_html(self, html: str, city: str) -> List[WeatherInfo]:
|
|
||||||
"""
|
|
||||||
解析天气HTML页面
|
|
||||||
|
|
||||||
Args:
|
|
||||||
html: HTML内容
|
|
||||||
city: 城市名称
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
天气信息列表
|
|
||||||
"""
|
|
||||||
weather_list = []
|
|
||||||
|
|
||||||
# 提取更新时间
|
|
||||||
update_time_match = re.search(r'(\d{2}:\d{2})\s*更新', html)
|
|
||||||
update_time = update_time_match.group(1) if update_time_match else ""
|
|
||||||
|
|
||||||
# 方法1: 尝试从HTML中直接提取天气预报数据
|
|
||||||
# 查找所有包含天气信息的li标签
|
|
||||||
# HTML结构示例:
|
|
||||||
# <li><h1>15日(今天)</h1><p>晴</p><p><generic>"21"</generic>/<generic>8℃</generic></p><p>...</p></li>
|
|
||||||
|
|
||||||
# 首先尝试查找h1标签的日期
|
|
||||||
date_pattern = r'<h1[^>]*>(\d+日|[^<]*?(今天)|[^<]*?(明天)|[^<]*?(后天)|周[一二三四五六七])</h1>'
|
|
||||||
date_matches = list(re.finditer(date_pattern, html))
|
|
||||||
|
|
||||||
for date_match in date_matches:
|
|
||||||
# 获取日期
|
|
||||||
date_str = date_match.group(1)
|
|
||||||
|
|
||||||
# 从日期位置开始,向后查找天气信息
|
|
||||||
start_pos = date_match.end()
|
|
||||||
# 找到下一个h1或li结束的位置
|
|
||||||
next_h1 = re.search(r'<h1|</li>', html[start_pos:start_pos + 500])
|
|
||||||
if next_h1:
|
|
||||||
end_pos = start_pos + next_h1.start()
|
|
||||||
else:
|
|
||||||
end_pos = start_pos + 500
|
|
||||||
|
|
||||||
weather_section = html[start_pos:end_pos]
|
|
||||||
|
|
||||||
# 提取天气状况(通常在p标签中)
|
|
||||||
weather_match = re.search(r'<p[^>]*>([^<]{1,20})</p>', weather_section)
|
|
||||||
weather = weather_match.group(1).strip() if weather_match else ""
|
|
||||||
|
|
||||||
# 提取温度(格式如: "21"/8℃ 或 21/8℃)
|
|
||||||
temp_pattern = r'>(\d{1,3})<.*?/.*?>(\d{1,3})℃?'
|
|
||||||
temp_match = re.search(temp_pattern, weather_section)
|
|
||||||
if temp_match:
|
|
||||||
temp_high = temp_match.group(1)
|
|
||||||
temp_low = temp_match.group(2)
|
|
||||||
temperature = f"{temp_high}/{temp_low}℃"
|
|
||||||
else:
|
|
||||||
# 尝试另一种温度格式
|
|
||||||
temp_match2 = re.search(r'(\d{1,3})[/-](\d{1,3})', weather_section)
|
|
||||||
if temp_match2:
|
|
||||||
temperature = f"{temp_match2.group(1)}/{temp_match2.group(2)}℃"
|
|
||||||
else:
|
|
||||||
temperature = ""
|
|
||||||
|
|
||||||
# 提取风向风力
|
|
||||||
# 提取风向(如:东北风、东风等)
|
|
||||||
wind_dir_match = re.search(r'(东风|南风|西风|北风|东北风|东南风|西北风|西南风)', weather_section)
|
|
||||||
wind_dir = wind_dir_match.group(1) if wind_dir_match else ""
|
|
||||||
# 提取风力等级
|
|
||||||
wind_level_match = re.search(r'<(\d级|无持续风向|微风|和风)>', weather_section)
|
|
||||||
wind_level = wind_level_match.group(1) if wind_level_match else ""
|
|
||||||
if not wind_level:
|
|
||||||
wind_level_match = re.search(r'(\d级|<\d级)', weather_section)
|
|
||||||
wind_level = wind_level_match.group(1) if wind_level_match else ""
|
|
||||||
|
|
||||||
wind = f"{wind_dir} {wind_level}".strip() if wind_dir or wind_level else ""
|
|
||||||
|
|
||||||
weather_list.append(WeatherInfo(
|
|
||||||
city=city,
|
|
||||||
date=date_str,
|
|
||||||
weather=weather,
|
|
||||||
temperature=temperature,
|
|
||||||
wind=wind,
|
|
||||||
update_time=update_time
|
|
||||||
))
|
|
||||||
|
|
||||||
# 方法2: 如果上述方法失败,尝试查找所有li标签的内容
|
|
||||||
if not weather_list:
|
|
||||||
li_pattern = r'<li[^>]*>.*?<h1>([^<]+)</h1>.*?<p[^>]*>([^<]+)</p>.*?(\d+).*?/.*?(\d+)'
|
|
||||||
matches = re.findall(li_pattern, html, re.DOTALL)
|
|
||||||
for match in matches:
|
|
||||||
date, weather, temp_high, temp_low = match
|
|
||||||
weather_list.append(WeatherInfo(
|
|
||||||
city=city,
|
|
||||||
date=date.strip(),
|
|
||||||
weather=weather.strip(),
|
|
||||||
temperature=f"{temp_high}/{temp_low}℃",
|
|
||||||
wind="",
|
|
||||||
update_time=update_time
|
|
||||||
))
|
|
||||||
|
|
||||||
return weather_list
|
|
||||||
|
|
||||||
def _get_weather_api(self, city_code: str) -> Optional[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
通过API获取天气数据
|
|
||||||
|
|
||||||
Args:
|
|
||||||
city_code: 城市编码
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
天气数据字典
|
|
||||||
"""
|
|
||||||
api_url = f"{self.BASE_URL}/data/sk/2d/{city_code}.html"
|
|
||||||
try:
|
|
||||||
response = self.client.get(api_url)
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.text
|
|
||||||
# 移除可能的JSONP回调
|
|
||||||
data = re.sub(r'^.*?\(', '', data)
|
|
||||||
data = re.sub(r'\);*$', '', data)
|
|
||||||
return json.loads(data)
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
def query_by_code(self, city_code: str, days: int = 7) -> Optional[List[WeatherInfo]]:
|
|
||||||
"""
|
|
||||||
根据城市编码查询天气
|
|
||||||
|
|
||||||
Args:
|
|
||||||
city_code: 城市编码
|
|
||||||
days: 查询天数(1, 7, 15, 40)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
天气信息列表
|
|
||||||
"""
|
|
||||||
city_name = get_city_name(city_code) or "未知"
|
|
||||||
|
|
||||||
# 根据天数选择不同的URL
|
|
||||||
if days == 1:
|
|
||||||
url = f"{self.BASE_URL}/weather1d/{city_code}.shtml"
|
|
||||||
elif days == 15:
|
|
||||||
url = f"{self.BASE_URL}/weather15d/{city_code}.shtml"
|
|
||||||
elif days == 40:
|
|
||||||
url = f"{self.BASE_URL}/weather40d/{city_code}.shtml"
|
|
||||||
else: # 默认7天
|
|
||||||
url = f"{self.BASE_URL}/weather/{city_code}.shtml"
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = self.client.get(url)
|
|
||||||
if response.status_code == 200:
|
|
||||||
weather_list = self._parse_weather_html(response.text, city_name)
|
|
||||||
return weather_list
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def query(self, city: str, days: int = 7) -> Optional[List[WeatherInfo]]:
|
|
||||||
"""
|
|
||||||
根据城市名称查询天气
|
|
||||||
|
|
||||||
Args:
|
|
||||||
city: 城市名称
|
|
||||||
days: 查询天数(1, 7, 15, 40)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
天气信息列表
|
|
||||||
"""
|
|
||||||
city_code = find_city_code(city)
|
|
||||||
if not city_code:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return self.query_by_code(city_code, days)
|
|
||||||
|
|
||||||
def query_current(self, city: str) -> Optional[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
查询当前天气
|
|
||||||
|
|
||||||
Args:
|
|
||||||
city: 城市名称
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
当前天气信息
|
|
||||||
"""
|
|
||||||
city_code = find_city_code(city)
|
|
||||||
if not city_code:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# 获取实时天气API
|
|
||||||
api_url = f"{self.BASE_URL}/data/sk/2d/{city_code}.html"
|
|
||||||
try:
|
|
||||||
response = self.client.get(api_url)
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.text
|
|
||||||
# 移除可能的JSONP回调
|
|
||||||
data = re.sub(r'^.*?\(', '', data)
|
|
||||||
data = re.sub(r'\);*$', '', data)
|
|
||||||
weather_data = json.loads(data)
|
|
||||||
|
|
||||||
# 解析返回的数据
|
|
||||||
if weather_data.get("weatherinfo"):
|
|
||||||
info = weather_data["weatherinfo"]
|
|
||||||
return {
|
|
||||||
"city": info.get("city", ""),
|
|
||||||
"temp": f"{info.get('temp', '')}°C",
|
|
||||||
"weather": info.get("weather", ""),
|
|
||||||
"wind": f"{info.get('wd', '')}{info.get('ws', '')}",
|
|
||||||
"humidity": f"{info.get('sd', '')}",
|
|
||||||
"time": info.get("time", ""),
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def format_weather(self, weather_info: List[WeatherInfo], days: int = 7) -> str:
|
|
||||||
"""
|
|
||||||
格式化天气信息为可读文本
|
|
||||||
|
|
||||||
Args:
|
|
||||||
weather_info: 天气信息列表
|
|
||||||
days: 显示天数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
格式化的天气文本
|
|
||||||
"""
|
|
||||||
if not weather_info:
|
|
||||||
return "未能获取天气信息"
|
|
||||||
|
|
||||||
result = [f"📍 {weather_info[0].city} 天气预报"]
|
|
||||||
result.append("=" * 30)
|
|
||||||
|
|
||||||
for i, info in enumerate(weather_info[:days]):
|
|
||||||
if i == 0:
|
|
||||||
result.append(f"\n📅 {info.date} (今天)")
|
|
||||||
else:
|
|
||||||
result.append(f"\n📅 {info.date}")
|
|
||||||
|
|
||||||
result.append(f" 🌤️ {info.weather}")
|
|
||||||
result.append(f" 🌡️ {info.temperature}")
|
|
||||||
if info.wind:
|
|
||||||
result.append(f" 💨 {info.wind}")
|
|
||||||
|
|
||||||
if weather_info[0].update_time:
|
|
||||||
result.append(f"\n⏰ 更新时间: {weather_info[0].update_time}")
|
|
||||||
|
|
||||||
return "\n".join(result)
|
|
||||||
|
|
||||||
def search_city(self, keyword: str) -> List[Dict[str, str]]:
|
|
||||||
"""
|
|
||||||
搜索城市
|
|
||||||
|
|
||||||
Args:
|
|
||||||
keyword: 搜索关键词
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
匹配的城市列表
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
keyword_lower = keyword.lower()
|
|
||||||
|
|
||||||
for code, name in CITY_CODES.items():
|
|
||||||
if keyword_lower in name.lower():
|
|
||||||
results.append({"code": code, "name": name})
|
|
||||||
|
|
||||||
# 按匹配度排序
|
|
||||||
results.sort(key=lambda x: (
|
|
||||||
not x["name"].startswith(keyword),
|
|
||||||
len(x["name"])
|
|
||||||
))
|
|
||||||
|
|
||||||
return results[:20] # 返回前20个结果
|
|
||||||
|
|
||||||
|
|
||||||
# 便捷函数
|
|
||||||
def query_weather(city: str, days: int = 7) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
查询天气(便捷函数)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
city: 城市名称
|
|
||||||
days: 查询天数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
格式化的天气文本
|
|
||||||
"""
|
|
||||||
query = WeatherQuery()
|
|
||||||
weather_info = query.query(city, days)
|
|
||||||
if weather_info:
|
|
||||||
return query.format_weather(weather_info, days)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def query_current_weather(city: str) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
查询当前天气(便捷函数)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
city: 城市名称
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
格式化的当前天气文本
|
|
||||||
"""
|
|
||||||
query = WeatherQuery()
|
|
||||||
current = query.query_current(city)
|
|
||||||
if current:
|
|
||||||
return f"📍 {current['city']} 当前天气\n" \
|
|
||||||
f"🌡️ 温度: {current['temp']}\n" \
|
|
||||||
f"🌤️ 天气: {current['weather']}\n" \
|
|
||||||
f"💨 风向风力: {current['wind']}\n" \
|
|
||||||
f"💧 湿度: {current['humidity']}\n" \
|
|
||||||
f"⏰ 更新时间: {current['time']}"
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def search_city(keyword: str) -> List[Dict[str, str]]:
|
|
||||||
"""
|
|
||||||
搜索城市(便捷函数)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
keyword: 搜索关键词
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
匹配的城市列表
|
|
||||||
"""
|
|
||||||
query = WeatherQuery()
|
|
||||||
return query.search_city(keyword)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# 测试代码
|
|
||||||
print("测试天气查询:")
|
|
||||||
|
|
||||||
# 测试当前天气
|
|
||||||
print("\n=== 当前天气 ===")
|
|
||||||
current = query_current_weather("杭州")
|
|
||||||
print(current)
|
|
||||||
|
|
||||||
# 测试7天天气预报
|
|
||||||
print("\n=== 7天天气预报 ===")
|
|
||||||
weather = query_weather("杭州", 7)
|
|
||||||
print(weather)
|
|
||||||
|
|
||||||
# 测试城市搜索
|
|
||||||
print("\n=== 城市搜索 ===")
|
|
||||||
cities = search_city("州")
|
|
||||||
for city in cities[:5]:
|
|
||||||
print(f" {city['name']}: {city['code']}")
|
|
||||||
@ -1,137 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
中国天气网天气查询命令行工具
|
|
||||||
数据来源:www.weather.com.cn
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
from weather import query_weather, query_current_weather, search_city, find_city_code
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_query(args):
|
|
||||||
"""查询天气预报"""
|
|
||||||
city_code = find_city_code(args.city)
|
|
||||||
if not city_code:
|
|
||||||
print(f"错误:未找到城市 \"{args.city}\",请使用 search 命令搜索城市")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if args.current:
|
|
||||||
# 查询当前天气
|
|
||||||
result = query_current_weather(args.city)
|
|
||||||
if result:
|
|
||||||
print(result)
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
print(f"错误:无法获取 {args.city} 的当前天气")
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
# 查询天气预报
|
|
||||||
days = args.days or 7
|
|
||||||
result = query_weather(args.city, days)
|
|
||||||
if result:
|
|
||||||
print(result)
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
print(f"错误:无法获取 {args.city} 的天气预报")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_search(args):
|
|
||||||
"""搜索城市"""
|
|
||||||
cities = search_city(args.keyword)
|
|
||||||
if not cities:
|
|
||||||
print(f"未找到包含 \"{args.keyword}\" 的城市")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
print(f"找到 {len(cities)} 个匹配的城市:")
|
|
||||||
print("-" * 40)
|
|
||||||
for city in cities[:args.limit]:
|
|
||||||
print(f" {city['name']}: {city['code']}")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def cmd_json(args):
|
|
||||||
"""输出JSON格式的天气数据"""
|
|
||||||
city_code = find_city_code(args.city)
|
|
||||||
if not city_code:
|
|
||||||
print(f"错误:未找到城市 \"{args.city}\"")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
from weather import WeatherQuery
|
|
||||||
query = WeatherQuery()
|
|
||||||
weather_info = query.query(args.city, args.days or 7)
|
|
||||||
|
|
||||||
if weather_info:
|
|
||||||
data = {
|
|
||||||
"city": weather_info[0].city,
|
|
||||||
"update_time": weather_info[0].update_time,
|
|
||||||
"forecast": [
|
|
||||||
{
|
|
||||||
"date": info.date,
|
|
||||||
"weather": info.weather,
|
|
||||||
"temperature": info.temperature,
|
|
||||||
"wind": info.wind,
|
|
||||||
}
|
|
||||||
for info in weather_info
|
|
||||||
]
|
|
||||||
}
|
|
||||||
print(json.dumps(data, ensure_ascii=False, indent=2))
|
|
||||||
return 0
|
|
||||||
else:
|
|
||||||
print(f"错误:无法获取 {args.city} 的天气数据")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="中国天气网天气查询工具",
|
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
||||||
epilog="""
|
|
||||||
示例:
|
|
||||||
%(prog)s query 杭州 # 查询杭州7天天气预报
|
|
||||||
%(prog)s query 北京 --days 3 # 查询北京3天天气预报
|
|
||||||
%(prog)s query 深圳 --current # 查询深圳当前天气
|
|
||||||
%(prog)s search 州 # 搜索带"州"字的城市
|
|
||||||
%(prog)s json 杭州 --days 3 # 输出JSON格式的天气数据
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
subparsers = parser.add_subparsers(dest="command", help="子命令")
|
|
||||||
|
|
||||||
# query 命令
|
|
||||||
query_parser = subparsers.add_parser("query", help="查询天气预报")
|
|
||||||
query_parser.add_argument("city", help="城市名称,如:杭州、北京、上海")
|
|
||||||
query_parser.add_argument("--days", type=int, choices=[1, 3, 7, 15, 40],
|
|
||||||
help="查询天数 (1, 3, 7, 15, 40),默认7天")
|
|
||||||
query_parser.add_argument("--current", "-c", action="store_true",
|
|
||||||
help="查询当前天气而非预报")
|
|
||||||
query_parser.set_defaults(func=cmd_query)
|
|
||||||
|
|
||||||
# search 命令
|
|
||||||
search_parser = subparsers.add_parser("search", help="搜索城市")
|
|
||||||
search_parser.add_argument("keyword", help="搜索关键词")
|
|
||||||
search_parser.add_argument("--limit", type=int, default=20,
|
|
||||||
help="返回结果数量限制,默认20")
|
|
||||||
search_parser.set_defaults(func=cmd_search)
|
|
||||||
|
|
||||||
# json 命令
|
|
||||||
json_parser = subparsers.add_parser("json", help="输出JSON格式的天气数据")
|
|
||||||
json_parser.add_argument("city", help="城市名称")
|
|
||||||
json_parser.add_argument("--days", type=int, choices=[1, 3, 7, 15, 40],
|
|
||||||
help="查询天数,默认7天")
|
|
||||||
json_parser.set_defaults(func=cmd_json)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if not args.command:
|
|
||||||
parser.print_help()
|
|
||||||
return 1
|
|
||||||
|
|
||||||
return args.func(args)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
Loading…
Reference in New Issue
Block a user