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()
相关推荐
ζั͡山 ั͡有扶苏 ั͡✾2 小时前
从零搭建 Data-Juicer:一站式大模型数据预处理与可视化平台完整教程
python·data-juicer
SkylerHu2 小时前
tornado+gunicorn部署设置max_body_size
python·tornado·gunicorn
独行soc3 小时前
2025年渗透测试面试题总结-234(题目+回答)
网络·python·安全·web安全·渗透测试·1024程序员节·安全狮
木头左4 小时前
年化波动率匹配原则在ETF网格区间选择中的应用
python
清空mega4 小时前
从零开始搭建 flask 博客实验(3)
后端·python·flask
程序员小远4 小时前
7个常见的Jmeter压测问题
自动化测试·软件测试·python·测试工具·测试用例·压力测试·性能测试
红尘炼丹客4 小时前
《DeepSeek-OCR: Contexts Optical Compression》速览
人工智能·python·自然语言处理·ocr
☼←安于亥时→❦4 小时前
Playwright 安装与使用
python·playwright
谢娘蓝桥5 小时前
Mac 安装 Xcode 及qt 环境安装
ide·macos·xcode
大佬,救命!!!5 小时前
python实现象棋
开发语言·python·学习笔记·pygame·少儿编程·记录成长