PYcharm——获取天气

PYcharm------图形化天气组件

openweather官网

免费的天气API网站,注册订阅即可免费使用

python 复制代码
import sys
import requests
import json
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
                             QHBoxLayout, QLabel, QLineEdit, QPushButton,
                             QTextEdit, QComboBox, QGroupBox, QProgressBar,
                             QMessageBox, QSplitter, QCheckBox)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QFont


class ApiWorker(QThread):
    """API请求工作线程"""
    finished = pyqtSignal(dict)
    error = pyqtSignal(str)

    def __init__(self, api_key, city, country_code, lang='zh_cn', original_city=None):
        super().__init__()
        self.api_key = api_key
        self.city = city
        self.country_code = country_code
        self.lang = lang
        self.original_city = original_city  # 保存原始中文城市名

    def run(self):
        try:
            base_url = "http://api.openweathermap.org/data/2.5/weather"
            params = {
                'q': f'{self.city},{self.country_code}',
                'appid': self.api_key,
                'units': 'metric',
                'lang': self.lang
            }

            response = requests.get(base_url, params=params, timeout=10)
            result = {
                'status_code': response.status_code,
                'data': response.json() if response.status_code == 200 else None,
                'raw_text': response.text,
                'request_city': self.city,  # 实际请求的城市名
                'original_city': self.original_city  # 原始输入的城市名
            }
            self.finished.emit(result)

        except Exception as e:
            self.error.emit(str(e))


class CityTranslator:
    """城市名称翻译器"""

    # 中文到英文的城市名称映射
    CITY_TRANSLATIONS = {
        # 中国主要城市
        '北京': 'Beijing', '上海': 'Shanghai', '广州': 'Guangzhou',
        '深圳': 'Shenzhen', '杭州': 'Hangzhou', '成都': 'Chengdu',
        '重庆': 'Chongqing', '武汉': 'Wuhan', '西安': "Xi'an",
        '南京': 'Nanjing', '天津': 'Tianjin', '苏州': 'Suzhou',
        '厦门': 'Xiamen', '青岛': 'Qingdao', '大连': 'Dalian',
        '郑州': 'Zhengzhou', '长沙': 'Changsha', '沈阳': 'Shenyang',
        '宁波': 'Ningbo', '无锡': 'Wuxi', '佛山': 'Foshan',
        '东莞': 'Dongguan', '济南': 'Jinan', '合肥': 'Hefei',
        '福州': 'Fuzhou', '昆明': 'Kunming', '哈尔滨': 'Harbin',
        '长春': 'Changchun', '太原': 'Taiyuan', '南宁': 'Nanning',
        '贵阳': 'Guiyang', '兰州': 'Lanzhou', '银川': 'Yinchuan',
        '西宁': 'Xining', '乌鲁木齐': 'Urumqi', '拉萨': 'Lhasa',

        # 国际城市中文名到英文名
        '伦敦': 'London', '纽约': 'New York', '东京': 'Tokyo',
        '巴黎': 'Paris', '悉尼': 'Sydney', '首尔': 'Seoul',
        '柏林': 'Berlin', '罗马': 'Rome', '莫斯科': 'Moscow',
        '新加坡': 'Singapore', '曼谷': 'Bangkok', '迪拜': 'Dubai',
        '洛杉矶': 'Los Angeles', '芝加哥': 'Chicago', '多伦多': 'Toronto',
        '墨尔本': 'Melbourne', '大阪': 'Osaka', '京都': 'Kyoto',
        '马德里': 'Madrid', '米兰': 'Milan', '维也纳': 'Vienna',
        '阿姆斯特丹': 'Amsterdam', '布鲁塞尔': 'Brussels', '日内瓦': 'Geneva'
    }

    @classmethod
    def translate_city(cls, chinese_city):
        """将中文城市名翻译成英文"""
        # 如果输入已经是英文或者是拼音,直接返回
        if all(ord(c) < 128 for c in chinese_city):
            return chinese_city, False  # False表示没有进行翻译

        # 查找翻译
        translated = cls.CITY_TRANSLATIONS.get(chinese_city)
        if translated:
            return translated, True  # True表示进行了翻译

        # 如果没有找到翻译,返回拼音(简单实现,实际可以使用pinyin库)
        return chinese_city, False


class WeatherApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.setWindowTitle('天气API测试工具')
        self.setGeometry(100, 100, 900, 700)

        # 设置样式
        self.setStyleSheet("""
            QMainWindow {
                background-color: #f0f0f0;
            }
            QGroupBox {
                font-weight: bold;
                border: 2px solid #cccccc;
                border-radius: 5px;
                margin-top: 1ex;
                padding-top: 10px;
            }
            QGroupBox::title {
                subcontrol-origin: margin;
                left: 10px;
                padding: 0 5px 0 5px;
            }
            QPushButton {
                background-color: #4CAF50;
                border: none;
                color: white;
                padding: 8px 16px;
                text-align: center;
                text-decoration: none;
                font-size: 14px;
                margin: 4px 2px;
                border-radius: 4px;
            }
            QPushButton:hover {
                background-color: #45a049;
            }
            QPushButton:disabled {
                background-color: #cccccc;
            }
            QPushButton#preset {
                background-color: #2196F3;
            }
            QPushButton#preset:hover {
                background-color: #1976D2;
            }
            QTextEdit {
                border: 1px solid #cccccc;
                border-radius: 3px;
                padding: 5px;
                font-family: Consolas, Monaco, monospace;
            }
            QLineEdit {
                padding: 5px;
                border: 1px solid #cccccc;
                border-radius: 3px;
            }
            QLabel#translation {
                color: #666;
                font-style: italic;
                background-color: #f8f8f8;
                padding: 3px;
                border-radius: 3px;
            }
        """)

        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        layout = QVBoxLayout(central_widget)

        # 创建分割器
        splitter = QSplitter(Qt.Vertical)
        layout.addWidget(splitter)

        # 上半部分:输入区域
        input_group = QGroupBox("API配置")
        splitter.addWidget(input_group)
        self.setup_input_area(input_group)

        # 下半部分:结果显示区域
        result_group = QGroupBox("测试结果")
        splitter.addWidget(result_group)
        self.setup_result_area(result_group)

        # 设置分割器比例
        splitter.setSizes([350, 350])

        # 状态栏
        self.statusBar().showMessage('准备就绪')

    def setup_input_area(self, parent):
        layout = QVBoxLayout(parent)

        # API密钥输入
        api_layout = QHBoxLayout()
        api_layout.addWidget(QLabel('API密钥:'))
        self.api_key_input = QLineEdit()
        self.api_key_input.setPlaceholderText('请输入OpenWeatherMap API密钥')
        self.api_key_input.setText('请输入OpenWeatherMap API密钥')
        api_layout.addWidget(self.api_key_input)
        layout.addLayout(api_layout)

        # 城市和国家选择
        location_layout = QHBoxLayout()

        city_layout = QVBoxLayout()
        city_layout.addWidget(QLabel('城市名称 (支持中文):'))
        self.city_input = QLineEdit()
        self.city_input.setText('北京')
        self.city_input.setPlaceholderText('输入中文或英文城市名,程序会自动转换')
        self.city_input.textChanged.connect(self.on_city_changed)
        city_layout.addWidget(self.city_input)

        # 城市名翻译显示
        self.translation_label = QLabel('')
        self.translation_label.setObjectName('translation')
        self.translation_label.setVisible(False)
        city_layout.addWidget(self.translation_label)

        country_layout = QVBoxLayout()
        country_layout.addWidget(QLabel('国家代码:'))
        self.country_combo = QComboBox()
        countries = [
            ('中国', 'cn'), ('美国', 'us'), ('英国', 'gb'), ('日本', 'jp'),
            ('德国', 'de'), ('法国', 'fr'), ('加拿大', 'ca'), ('澳大利亚', 'au'),
            ('韩国', 'kr'), ('俄罗斯', 'ru'), ('巴西', 'br'), ('印度', 'in')
        ]
        for country_name, country_code in countries:
            self.country_combo.addItem(f"{country_name} ({country_code})", country_code)
        self.country_combo.setCurrentText('中国 (cn)')
        country_layout.addWidget(self.country_combo)

        location_layout.addLayout(city_layout)
        location_layout.addLayout(country_layout)
        layout.addLayout(location_layout)

        # 语言设置
        lang_layout = QHBoxLayout()
        self.lang_checkbox = QCheckBox('显示中文天气描述')
        self.lang_checkbox.setChecked(True)
        lang_layout.addWidget(self.lang_checkbox)
        lang_layout.addStretch()
        layout.addLayout(lang_layout)

        # 预定义城市按钮
        preset_layout = QVBoxLayout()
        preset_layout.addWidget(QLabel('快速选择城市:'))

        # 中国城市按钮
        chinese_cities_layout = QHBoxLayout()
        chinese_cities = [
            ('北京', 'cn'), ('上海', 'cn'), ('广州', 'cn'),
            ('深圳', 'cn'), ('杭州', 'cn'), ('成都', 'cn')
        ]
        for city, country in chinese_cities:
            btn = QPushButton(city)
            btn.setObjectName("preset")
            btn.clicked.connect(lambda checked, c=city, co=country: self.set_preset_city(c, co))
            chinese_cities_layout.addWidget(btn)
        preset_layout.addLayout(chinese_cities_layout)

        # 国际城市按钮
        intl_cities_layout = QHBoxLayout()
        intl_cities = [
            ('伦敦', 'gb'), ('纽约', 'us'), ('东京', 'jp'),
            ('巴黎', 'fr'), ('悉尼', 'au'), ('首尔', 'kr')
        ]
        for city, country in intl_cities:
            btn = QPushButton(city)
            btn.setObjectName("preset")
            btn.clicked.connect(lambda checked, c=city, co=country: self.set_preset_city(c, co))
            intl_cities_layout.addWidget(btn)
        preset_layout.addLayout(intl_cities_layout)

        layout.addLayout(preset_layout)

        # 测试按钮和进度条
        button_layout = QHBoxLayout()
        self.test_btn = QPushButton('测试API连接')
        self.test_btn.clicked.connect(self.test_api)
        button_layout.addWidget(self.test_btn)

        self.clear_btn = QPushButton('清空结果')
        self.clear_btn.clicked.connect(self.clear_results)
        button_layout.addWidget(self.clear_btn)

        layout.addLayout(button_layout)

        # 进度条
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        layout.addWidget(self.progress_bar)

    def setup_result_area(self, parent):
        layout = QVBoxLayout(parent)

        # 请求信息显示
        request_layout = QVBoxLayout()
        request_layout.addWidget(QLabel('请求信息:'))
        self.request_display = QTextEdit()
        self.request_display.setMaximumHeight(80)
        request_layout.addWidget(self.request_display)
        layout.addLayout(request_layout)

        # 天气信息显示
        weather_layout = QVBoxLayout()
        weather_layout.addWidget(QLabel('天气信息:'))
        self.weather_display = QTextEdit()
        self.weather_display.setMaximumHeight(150)
        weather_layout.addWidget(self.weather_display)
        layout.addLayout(weather_layout)

        # 原始JSON显示
        json_layout = QVBoxLayout()
        json_layout.addWidget(QLabel('原始API响应:'))
        self.json_display = QTextEdit()
        json_layout.addWidget(self.json_display)
        layout.addLayout(json_layout)

    def on_city_changed(self):
        """城市名输入变化时显示翻译信息"""
        city = self.city_input.text().strip()
        if not city:
            self.translation_label.setVisible(False)
            return

        translated_city, was_translated = CityTranslator.translate_city(city)

        if was_translated and city != translated_city:
            self.translation_label.setText(f'将使用英文名查询: {translated_city}')
            self.translation_label.setVisible(True)
        else:
            self.translation_label.setVisible(False)

    def set_preset_city(self, city, country):
        """设置预定义城市"""
        self.city_input.setText(city)
        for i in range(self.country_combo.count()):
            if self.country_combo.itemData(i) == country:
                self.country_combo.setCurrentIndex(i)
                break

    def get_country_code(self):
        """获取选中的国家代码"""
        return self.country_combo.currentData()

    def get_language_code(self):
        """获取语言代码"""
        return 'zh_cn' if self.lang_checkbox.isChecked() else 'en'

    def test_api(self):
        """测试API连接"""
        api_key = self.api_key_input.text().strip()
        original_city = self.city_input.text().strip()
        country_code = self.get_country_code()
        lang = self.get_language_code()

        if not api_key:
            QMessageBox.warning(self, '输入错误', '请输入API密钥')
            return

        if not original_city:
            QMessageBox.warning(self, '输入错误', '请输入城市名称')
            return

        # 翻译城市名
        request_city, was_translated = CityTranslator.translate_city(original_city)

        # 更新UI状态
        self.test_btn.setEnabled(False)
        self.progress_bar.setVisible(True)
        self.progress_bar.setRange(0, 0)

        # 显示请求信息
        request_info = f"输入城市: {original_city}"
        if was_translated and original_city != request_city:
            request_info += f"\n转换城市: {request_city}"
        request_info += f"\n国家代码: {country_code}"
        self.request_display.setText(request_info)

        self.statusBar().showMessage(f'正在查询 {request_city} 的天气...')

        # 在工作线程中执行API请求
        self.worker = ApiWorker(api_key, request_city, country_code, lang, original_city)
        self.worker.finished.connect(self.on_api_finished)
        self.worker.error.connect(self.on_api_error)
        self.worker.start()

    def on_api_finished(self, result):
        """API请求完成"""
        self.test_btn.setEnabled(True)
        self.progress_bar.setVisible(False)

        status_code = result['status_code']
        request_city = result['request_city']
        original_city = result['original_city']

        # 更新请求信息
        current_request_info = self.request_display.toPlainText()
        current_request_info += f"\nAPI请求: {request_city},{result.get('country_code', '')}"
        current_request_info += f"\nHTTP状态: {status_code}"
        self.request_display.setText(current_request_info)

        if status_code == 200:
            data = result['data']
            self.display_weather_info(data, original_city, request_city)
            self.display_json_response(data)
            api_city_name = data.get('name', '未知城市')
            self.statusBar().showMessage(f'成功获取 {api_city_name} 的天气信息')

        elif status_code == 401:
            self.show_error('API密钥无效', '请检查API密钥是否正确且已激活')
            self.statusBar().showMessage('API密钥无效')

        elif status_code == 404:
            self.show_city_not_found_error(original_city, request_city)
            self.statusBar().showMessage('城市未找到')

        elif status_code == 429:
            self.show_error('请求频率超限', '请稍后再试或检查API调用限制')
            self.statusBar().showMessage('请求频率超限')

        else:
            error_msg = f"HTTP错误代码: {status_code}\n响应: {result['raw_text']}"
            self.show_error('请求失败', error_msg)
            self.statusBar().showMessage(f'请求失败: {status_code}')

    def on_api_error(self, error_msg):
        """API请求出错"""
        self.test_btn.setEnabled(True)
        self.progress_bar.setVisible(False)
        self.show_error('请求异常', error_msg)
        self.statusBar().showMessage('请求异常')

    def display_weather_info(self, data, original_city, request_city):
        """显示天气信息"""
        try:
            api_city = data.get('name', 'N/A')
            country = data.get('sys', {}).get('country', 'N/A')
            temp = data.get('main', {}).get('temp', 'N/A')
            feels_like = data.get('main', {}).get('feels_like', 'N/A')
            humidity = data.get('main', {}).get('humidity', 'N/A')
            pressure = data.get('main', {}).get('pressure', 'N/A')
            description = data.get('weather', [{}])[0].get('description', 'N/A')
            wind_speed = data.get('wind', {}).get('speed', 'N/A')

            info_text = f"""
🏙️  位置: {api_city}, {country}
💬  输入: {original_city} → 请求: {request_city}
🌡️  温度: {temp}°C (体感: {feels_like}°C)
🌤️  天气: {description}
💧  湿度: {humidity}%
📈  气压: {pressure} hPa
💨  风速: {wind_speed} m/s
            """.strip()

            self.weather_display.setText(info_text)

        except Exception as e:
            self.weather_display.setText(f"解析天气数据时出错: {str(e)}")

    def show_city_not_found_error(self, original_city, request_city):
        """显示城市未找到的错误信息"""
        error_msg = f"""
找不到城市: "{original_city}"

查询详情:
• 输入城市: {original_city}
• 转换城市: {request_city}
• 国家代码: {self.get_country_code()}

建议:
1. 检查城市名拼写
2. 尝试使用英文名称
3. 检查国家代码是否正确
4. 该城市可能不在API数据库中
        """.strip()

        self.show_error('城市未找到', error_msg)

    def display_json_response(self, data):
        """显示原始JSON响应"""
        try:
            formatted_json = json.dumps(data, indent=2, ensure_ascii=False)
            self.json_display.setText(formatted_json)
        except Exception as e:
            self.json_display.setText(f"格式化JSON时出错: {str(e)}")

    def show_error(self, title, message):
        """显示错误信息"""
        self.weather_display.setText(f"❌ {title}\n{message}")
        self.json_display.setText("")

    def clear_results(self):
        """清空结果显示"""
        self.request_display.clear()
        self.weather_display.clear()
        self.json_display.clear()
        self.translation_label.setVisible(False)
        self.statusBar().showMessage('结果已清空')


def main():
    app = QApplication(sys.argv)

    # 设置应用程序字体
    font = QFont("Microsoft YaHei", 10)
    app.setFont(font)

    window = WeatherApp()
    window.show()

    sys.exit(app.exec_())


if __name__ == '__main__':
    main()
相关推荐
铉铉这波能秀几秒前
LeetCode Hot100 中 enumerate 函数的妙用(2026.2月版)
数据结构·python·算法·leetcode·职场和发展·开发
毕设源码-赖学姐2 分钟前
【开题答辩全过程】以 基于python的电影推荐系统为例,包含答辩的问题和答案
开发语言·python
敲键盘的生活4 分钟前
MoneyPrinter重构之一:用nicegui调用大模型生成视频文案
python·重构·aigc·ai编程·ai写作
小邓睡不饱耶6 分钟前
2026 CSDN榜单封神!3大热门技术+5个大厂案例,新手也能直接抄作业
python·ai
南极星100510 分钟前
我的创作纪念日--128天
java·python·opencv·职场和发展
码界筑梦坊12 分钟前
327-基于Django的兰州空气质量大数据可视化分析系统
python·信息可视化·数据分析·django·毕业设计·数据可视化
Highcharts.js12 分钟前
如何使用Highcharts SVG渲染器?
开发语言·javascript·python·svg·highcharts·渲染器
啊阿狸不会拉杆17 分钟前
《机器学习导论》第 7 章-聚类
数据结构·人工智能·python·算法·机器学习·数据挖掘·聚类
摇滚侠17 分钟前
Java,举例说明,函数式接口,函数式接口实现类,通过匿名内部类实现函数式接口,通过 Lambda 表达式实现函数式接口,演变的过程
java·开发语言·python
禹凕22 分钟前
Python编程——进阶知识(面向对象编程OOP)
开发语言·python