From 2cc8b893f73199e1a71fa8f0545fe15498b8a3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=B1=E6=BD=AE?= Date: Sat, 4 Apr 2026 08:01:21 +0800 Subject: [PATCH] remove --- skills/weather/.claude-plugin/plugin.json | 13 - skills/weather/SKILL.md | 149 ------ skills/weather/mcp.py | 123 ----- skills/weather/scripts/weather/__init__.py | 40 -- skills/weather/scripts/weather/city_codes.py | 525 ------------------- skills/weather/scripts/weather/query.py | 399 -------------- skills/weather/scripts/weather_query.py | 137 ----- 7 files changed, 1386 deletions(-) delete mode 100644 skills/weather/.claude-plugin/plugin.json delete mode 100644 skills/weather/SKILL.md delete mode 100644 skills/weather/mcp.py delete mode 100644 skills/weather/scripts/weather/__init__.py delete mode 100644 skills/weather/scripts/weather/city_codes.py delete mode 100644 skills/weather/scripts/weather/query.py delete mode 100644 skills/weather/scripts/weather_query.py diff --git a/skills/weather/.claude-plugin/plugin.json b/skills/weather/.claude-plugin/plugin.json deleted file mode 100644 index 922d903..0000000 --- a/skills/weather/.claude-plugin/plugin.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "weather", - "description": "提供中国天气预报查询功能,支持全国300+城市的当前天气和未来1/3/7/15/40天天气预报查询。", - "mcpServers": { - "weather": { - "transport": "stdio", - "command": "python", - "args": [ - "./skills/weather/mcp.py" - ] - } - } -} diff --git a/skills/weather/SKILL.md b/skills/weather/SKILL.md deleted file mode 100644 index 4b24098..0000000 --- a/skills/weather/SKILL.md +++ /dev/null @@ -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天预报查询 diff --git a/skills/weather/mcp.py b/skills/weather/mcp.py deleted file mode 100644 index 4d38d77..0000000 --- a/skills/weather/mcp.py +++ /dev/null @@ -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()) diff --git a/skills/weather/scripts/weather/__init__.py b/skills/weather/scripts/weather/__init__.py deleted file mode 100644 index 2d48946..0000000 --- a/skills/weather/scripts/weather/__init__.py +++ /dev/null @@ -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" diff --git a/skills/weather/scripts/weather/city_codes.py b/skills/weather/scripts/weather/city_codes.py deleted file mode 100644 index b4de88f..0000000 --- a/skills/weather/scripts/weather/city_codes.py +++ /dev/null @@ -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}") diff --git a/skills/weather/scripts/weather/query.py b/skills/weather/scripts/weather/query.py deleted file mode 100644 index 0e76baa..0000000 --- a/skills/weather/scripts/weather/query.py +++ /dev/null @@ -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结构示例: - #
  • 15日(今天)

    "21"/8℃

    ...

  • - - # 首先尝试查找h1标签的日期 - date_pattern = r']*>(\d+日|[^<]*?(今天)|[^<]*?(明天)|[^<]*?(后天)|周[一二三四五六七])' - 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'', 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']*>([^<]{1,20})

    ', 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']*>.*?

    ([^<]+)

    .*?]*>([^<]+)

    .*?(\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']}") diff --git a/skills/weather/scripts/weather_query.py b/skills/weather/scripts/weather_query.py deleted file mode 100644 index 98adfd6..0000000 --- a/skills/weather/scripts/weather_query.py +++ /dev/null @@ -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())