20252321 实验四 Python综合实践 实验报告

课程:《Python程序设计》
班级: 2523
姓名: 杨淇麟
学号:20252321
实验教师:王志强
实验日期:2026年5月25日
必修/选修: 公选课

一、实验内容
本次实验为Python 综合应用实践,选取天气数据爬取作为核心主题,综合运用网络爬虫、数据解析、数据可视化、智能预警、日志管理、邮件推送、交互系统等技术,实现一套完整的自动化天气查询与智能提醒工具。
实验实现功能包括:
实时天气数据爬取(温度、天气、风力、湿度、紫外线等)
多条件组合智能天气预警
生活气象指数自动生成(穿衣、洗车、运动、防晒、感冒)
本地详细日志记录与管理(保存 / 查看 / 清空)
7 天气温趋势数据可视化(最高温、最低温双折线图)
简易 / 详细模式自由切换
节气与节假日自动识别(含儿童节特殊节日)
邮件自动推送(含文字 + 气温趋势图)
交互式终端菜单操作
二、实验背景
天气信息与日常出行、生活、工作高度相关,手动查询天气步骤繁琐、效率低。为提升使用便捷性,本项目基于 Python 开发轻量化、无后台、绿色安全的天气智能工具,实现:
自动爬取实时天气与 7 天预报
异常天气智能预警(单条件 + 多条件组合)
本地日志永久存储
气温趋势可视化展示
邮箱远程推送提醒
菜单式交互操作
程序可随时关闭、不占用电脑资源、日志可手动删除,完全满足轻量化、实用性、稳定性的使用要求。
三、需求分析

  1. 功能需求
    实时天气爬取
    获取城市温度、天气状况、风速、湿度、紫外线强度、7 天预报数据。
    智能天气预警
    识别高温、低温、降雨、大风、强紫外线天气,并支持多条件组合预警(冷雨、高温暴晒、风雨交加)。
    生活气象指数
    自动生成穿衣、洗车、运动、防晒、感冒五大实用生活建议。
    本地日志管理
    自动保存详细天气记录,支持查看日志、清空日志。
    数据可视化
    绘制 7 天最高温、最低温双折线趋势图,直观展示气温变化。
    节气与节假日
    自动显示当日二十四节气,支持儿童节等特殊节日识别。
    邮件推送
    自动发送完整天气报告,包含文字信息 + 气温趋势图片。
    交互界面
    提供终端菜单,支持查询天气、查看日志、清空日志、退出程序。
    展示模式切换
    支持简易模式(核心信息)、详细模式(完整信息)。
  2. 非功能需求
    程序稳定运行,不崩溃、不报错
    网络异常、城市错误具备完善异常捕获
    关闭程序立即停止所有任务
    日志文件可手动删除
    无后台驻留、不占用系统资源
    界面清晰、操作简单、提示友好

四、功能模块设计
本项目共分为9 大核心功能模块:
天气爬虫模块:调用公开稳定接口wttr.in,获取 JSON 天气数据。
数据解析模块:提取温度、风速、湿度、紫外线、预报等有效信息。
智能预警模块:实现单条件 + 多条件组合天气提醒。
生活指数模块:根据气象数据自动生成穿衣、运动等生活建议。
日志管理模块:本地详细日志存储、查看、清空功能。
数据可视化模块:生成并保存 7 天气温趋势双折线图。
展示模式模块:简易 / 详细模式切换展示。
节气节假日模块:自动计算节气,识别儿童节等节日。
邮件推送模块:构建 HTML 邮件,内嵌气温图表并发送至指定邮箱。

源代码:
import requests
import matplotlib.pyplot as plt
from datetime import datetime, date
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
from email.header import Header
import os

===================== 【配置区:只改这里】 =====================

WEATHER_URL = "http://wttr.in/{}?format=j1"
LOG_FILE = "天气日志.txt"
CHART_FILE = "weather_chart.png" # 图表保存路径

【邮箱配置】

MAIL_SENDER = "你的QQ邮箱@qq.com"
MAIL_PASSWD = "你的QQ邮箱授权码"
MAIL_RECEIVER = "你的QQ邮箱@qq.com"

===================== 节气 + 节假日(修复儿童节) =====================

def get_solar_term_holiday():
today = datetime.now()
m, d = today.month, today.day
jieqi = [
(1,5,"小寒"),(1,20,"大寒"),(2,4,"立春"),(2,19,"雨水"),
(3,5,"惊蛰"),(3,20,"春分"),(4,4,"清明"),(4,20,"谷雨"),
(5,5,"立夏"),(5,21,"小满"),(6,5,"芒种"),(6,21,"夏至"),
(7,7,"小暑"),(7,22,"大暑"),(8,7,"立秋"),(8,23,"处暑"),
(9,7,"白露"),(9,23,"秋分"),(10,8,"寒露"),(10,23,"霜降"),
(11,7,"立冬"),(11,22,"小雪"),(12,7,"大雪"),(12,22,"冬至")
]
solar = "无节气"
for item in jieqi:
if item[0]m and item[1]d:
solar = item[2]
break

【修复:儿童节】

if m == 6 and d == 1:
holiday = "儿童节"
else:
holiday = "无节假日"
return solar, holiday

===================== 获取天气 =====================

def get_weather(city):
try:
res = requests.get(WEATHER_URL.format(city), timeout=15)
data = res.json()
current = data["current_condition"][0]
weather_list = data.get("weather", [])
if len(weather_list) < 1:
print("❌ 无天气数据")
return None

temp = int(current["temp_C"])
desc = current["weatherDesc"][0]["value"]
wind = int(current["windspeedKmph"])
humidity = int(current["humidity"])
uv = int(current.get("uvIndex", 0))

forecast = []
for i in range(min(7, len(weather_list))):
day = weather_list[i]
forecast.append({
"date": day["date"],
"max": int(day["maxtempC"]),
"min": int(day["mintempC"])
})
return {
"city": city, "temp": temp, "desc": desc,
"wind": wind, "humidity": humidity, "uv": uv,
"forecast": forecast
}
except Exception as e:
print(f"❌ 获取失败:{e}")
return None

===================== 多条件组合提醒 =====================

def get_alerts(w):
alerts = []
t = w["temp"]
desc = w["desc"]
wind = w["wind"]
uv = w["uv"]

if t >= 35: alerts.append("🔥 高温炎热")
if t <= 5: alerts.append("❄️ 气温寒冷")
if "雨" in desc: alerts.append("🌧 今日有雨")
if wind >= 25: alerts.append("💨 风力较大")
if uv >= 7: alerts.append("☀️ 紫外线强")

if t <= 10 and "雨" in desc:
alerts.append("🧊 冷雨天气,务必保暖+带伞")
if t >= 32 and uv >= 6:
alerts.append("🥵 高温暴晒,尽量减少外出")
if "雨" in desc and wind >= 20:
alerts.append("🌪️ 风雨交加,出行注意安全")

if not alerts:
alerts.append("☀️ 天气良好,适宜出行")
return alerts

===================== 生活气象指数 =====================

def get_life_index(w):
t = w["temp"]
humidity = w["humidity"]
uv = w["uv"]
desc = w["desc"]

if t >= 28:
clothes = "短袖短裤,清凉透气"
elif t >= 18:
clothes = "长袖/薄T恤,舒适适宜"
elif t >= 10:
clothes = "薄外套,早晚注意保暖"
else:
clothes = "厚外套/羽绒服,防寒保暖"

car = "适宜洗车" if "雨" not in desc else "不宜洗车"
sport = "适宜户外运动" if t < 30 and "雨" not in desc else "建议室内运动"

if uv >= 7:
sun = "强烈防晒,墨镜+帽子+防晒霜"
elif uv >= 4:
sun = "常规防晒"
else:
sun = "无需刻意防晒"

cold = "易感冒,注意防护" if t <= 12 or humidity >= 80 else "感冒风险低"
return {"穿衣": clothes, "洗车": car, "运动": sport, "防晒": sun, "感冒": cold}

===================== 【超级详细日志】 =====================

def save_log(city, w, solar, holiday, alerts, life):
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write("="50 + "\n")
f.write(f"📅 查询时间:{now}\n")
f.write(f"🏙️ 城市:{city}\n")
f.write(f"🌡 温度:{w['temp']}℃ | 天气:{w['desc']}\n")
f.write(f"💨 风速:{w['wind']}km/h | 湿度:{w['humidity']}%\n")
f.write(f"☀️ 紫外线:{w['uv']} | 节气:{solar} | 节假日:{holiday}\n")
f.write(f"⚠️ 预警提醒:{' | '.join(alerts)}\n")
f.write("【生活指数】\n")
for k, v in life.items():
f.write(f" {k}:{v}\n")
f.write("【7天预报】\n")
for day in w["forecast"]:
f.write(f" {day['date']} 最高{day['max']}℃ 最低{day['min']}℃\n")
f.write("="
50 + "\n\n")

===================== 可视化图表(保存到文件) =====================

def plot_forecast(forecast):
try:
dates = [x["date"][5:] for x in forecast]
max_t = [x["max"] for x in forecast]
min_t = [x["min"] for x in forecast]
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.figure(figsize=(10,5))
plt.plot(dates, max_t, "r-o", label="最高温")
plt.plot(dates, min_t, "b-s", label="最低温")
plt.title("7天气温趋势图")
plt.legend()
plt.grid(alpha=0.3)
plt.savefig(CHART_FILE, dpi=150, bbox_inches="tight") # 保存图片
plt.show()
except Exception as e:
print("ℹ️ 图表生成失败:", e)

===================== 【HTML邮件 + 内嵌图片】 =====================

def send_email(city, w, solar, holiday, alerts, life):
try:
# 正文文本
text_content = f"""
🌤 自动天气报告
城市:{city}
温度:{w['temp']}℃
天气:{w['desc']}
风速:{w['wind']}km/h
湿度:{w['humidity']}%
紫外线:{w['uv']}
节气:{solar}
节假日:{holiday}

⚠️ 温馨提醒:

【生活气象指数】
穿衣:{life['穿衣']}
洗车:{life['洗车']}
运动:{life['运动']}
防晒:{life['防晒']}
感冒:{life['感冒']}

【未来7天预报】
"""
for day in w["forecast"]:
text_content += f"{day['date']} {day['min']}~{day['max']}℃\n"

HTML正文(内嵌图片)

html_content = f"""

🌤 {city} 今日天气

温度:{w['temp']}℃    天气:{w['desc']}

风速:{w['wind']}km/h    湿度:{w['humidity']}%

节气:{solar}    节假日:{holiday}


⚠️ 温馨提醒:{' | '.join(alerts)}


【生活气象指数】

  • 穿衣:{life['穿衣']}
  • 洗车:{life['洗车']}
  • 运动:{life['运动']}
  • 防晒:{life['防晒']}
  • 感冒:{life['感冒']}

【7天气温趋势】

7天温度图 """

构建邮件(mixed:文本+HTML+图片)

msg = MIMEMultipart("mixed")
msg["From"] = MAIL_SENDER
msg["To"] = MAIL_RECEIVER
msg["Subject"] = Header(f"{city}今日天气|{holiday}", "utf-8")

纯文本部分

msg.attach(MIMEText(text_content, "plain", "utf-8"))

HTML部分

msg.attach(MIMEText(html_content, "html", "utf-8"))

内嵌图片

if os.path.exists(CHART_FILE):
with open(CHART_FILE, "rb") as f:
img = MIMEImage(f.read())
img.add_header("Content-ID", "<weather_chart>")
img.add_header("Content-Disposition", "inline", filename=CHART_FILE)
msg.attach(img)

发送

server = smtplib.SMTP_SSL("smtp.qq.com", 465)
server.login(MAIL_SENDER, MAIL_PASSWD)
server.sendmail(MAIL_SENDER, [MAIL_RECEIVER], msg.as_string())
server.quit()
print("✅ 邮件发送成功(含图片)")
except Exception as e:
print(f"❌ 邮件失败:{e}")

===================== 简易 / 详细模式 =====================

def show_weather(w, simple=True):
solar, holiday = get_solar_term_holiday()
alerts = get_alerts(w)
life = get_life_index(w)

if simple:
print("\n===== 简易天气 =")
print(f"城市:{w['city']} | {w['temp']}℃ | {w['desc']}")
print(f"节气:{solar} | {holiday}")
print("提醒:", " | ".join(alerts[:2]))
else:
print("\n
= 详细天气 =====")
print(f"城市:{w['city']}")
print(f"温度:{w['temp']}℃ | 天气:{w['desc']}")
print(f"风速:{w['wind']}km/h | 湿度:{w['humidity']}% | 紫外线:{w['uv']}")
print(f"节气:{solar} | 节假日:{holiday}")
print("\n⚠️ 预警提醒:")
for a in alerts:
print(f"• {a}")
print("\n【生活气象指数】")
for k, v in life.items():
print(f"- {k}:{v}")

save_log(w['city'], w, solar, holiday, alerts, life)
return solar, holiday, alerts, life

===================== 主程序 =====================

def main():
while True:
print("\n===== 天气智能工具 =====")
print("1. 查询天气")
print("2. 查看日志")
print("3. 清空日志")
print("4. 退出")
choice = input("请选择:")

if choice == "1":
city = input("请输入城市:")
w = get_weather(city)
if not w:
continue
is_detail = input("显示详细模式?(y/n,默认n):").lower() == "y"
solar, holiday, alerts, life = show_weather(w, simple=not is_detail)
plot_forecast(w["forecast"])
send_email(city, w, solar, holiday, alerts, life)

elif choice == "2":
if os.path.exists(LOG_FILE):
with open(LOG_FILE, "r", encoding="utf-8") as f:
print(f.read())
else:
print("暂无日志")

elif choice == "3":
open(LOG_FILE, "w", encoding="utf-8").close()
print("✅ 日志已清空")

elif choice == "4":
print("👋 再见")
break

if name == "main":
main()

五、运行流程(含视频)
启动程序 → 显示交互式主菜单
用户选择功能(查询天气 / 查看日志 / 清空日志 / 退出)
若选择查询天气:
输入城市名称 → 选择展示模式
爬取天气数据 → 解析数据
生成智能预警 → 生成生活气象指数
获取当日节气与节假日
按简易 / 详细模式展示天气信息
保存详细日志 → 生成气温趋势图
发送含图片的完整天气邮件
功能执行完毕 → 返回主菜单
选择退出 → 程序结束运行

最后把实验代码推送到码云:
终端依次输入:
git add .
git commit -m "天气查询程序"
git push ifyou master

屏幕截图 2026-06-02 185244

屏幕截图 2026-06-02 185418

https://gitee.com/five-little-sheep/ifyou/blob/master/WeatherWechatRemind.py

运行视频:

六、实现过程

  1. 安装依赖库
    plaintext
    pip install requests matplotlib holidays
    requests:网络请求,爬取天气数据
    matplotlib:生成气温趋势可视化图表
    holidays:节假日识别
  2. 接入天气接口
    采用公开免费接口wttr.in,以 JSON 格式返回实时天气与预报数据。
  3. 数据解析
    安全解析温度、天气描述、风速、湿度、紫外线、7 天预报,使用异常处理避免程序崩溃。
  4. 智能预警与生活指数
    根据温度、天气、风速、紫外线实现单条件预警
    新增多条件组合判断:冷雨、高温强晒、风雨交加
    自动生成穿衣、洗车、运动、防晒、感冒指数
  5. 日志存储
    使用 TXT 文件存储超详细日志,包含查询时间、城市、气象数据、预警、生活指数、7 天预报。
  6. 数据可视化
    使用matplotlib绘制双折线图,设置中文字体,自动保存图片用于邮件推送。
  7. 节气与节假日
    内置二十四节气算法,手动添加儿童节(6 月 1 日)判断,无需第三方库依赖。
  8. 邮件推送
    使用 Python 内置smtplib实现邮件发送,构建 HTML 格式邮件,内嵌气温趋势图,支持 QQ 邮箱。
  9. 交互菜单
    实现循环主菜单,支持模式切换、错误输入提示、操作结果反馈。
  10. 异常处理
    对网络请求、数据解析、文件操作、图表生成、邮件发送全流程添加try-except捕获,确保程序 100% 稳定不报错。

七、实验结果
程序稳定运行,无任何崩溃、无报错
成功获取实时天气 + 7 天预报,数据准确
智能预警正常,支持多条件组合提醒
生活气象指数自动生成,实用性强
详细日志可保存、可查看、可清空
气温趋势图正常生成、展示、嵌入邮件
节气 + 节假日自动显示(含儿童节)
邮件推送成功发送,包含文字 + 图片
简易 / 详细模式切换正常
交互式菜单操作流畅
关闭程序立即停止,无后台占用

八、实验过程中遇到的问题和解决过程
问题 1:天气接口获取失败、索引越界导致程序崩溃
原因:网络异常、城市名称错误、接口返回数据为空。
解决:添加数据长度判断与全局异常捕获,程序不崩溃并给出友好提示。
问题 2:图表无法嵌入邮件,仅本地弹窗显示
原因:未保存图片、未构建 HTML 内嵌图片格式。
解决:图表自动保存为本地文件,邮件使用 CID 方式内嵌图片,QQ 邮箱可直接查看。
问题 3:6 月 1 日儿童节无法识别,显示 “无节假日”
原因:holidays库仅包含法定节假日,不含儿童节。
解决:手动添加日期判断,6 月 1 日固定显示 “儿童节”。
问题 4:图表中文乱码
原因:matplotlib默认不支持中文字体。
解决:设置全局中文字体,适配 Windows 系统。
问题 5:邮件发送失败
原因:昵称格式不被 QQ 邮箱支持。
解决:直接使用邮箱地址作为发件人,修复发送逻辑。
问题 6:日志内容过于简单
解决:重构日志模块,记录完整气象数据、预警、指数、预报信息。
问题 7:预警逻辑单一
解决:新增多条件组合预警,提升实用性与智能性。

九、全课总结
哎一学期的 Python 程序设计选修课就这样结束了,心里其实还有点小小的不舍。
说实话,这门课上课节奏真的有点快,我基础本来就一般,很多知识点老师一带而过,好多专业术语、代码逻辑我基本都跟不上,上课大半时间都是似懂非懂的状态,复杂的那些原理我也压根没听懂,更别说自己独立写复杂程序了。
不过就算跟不上,多多少少还是有收获的。至少我第一次真正接触到了编程,大概搞懂了 Python 最基础的一些东西,比如代码怎么加注释、变量是干嘛的、平时常用的几种数据类型长什么样。也大概知道了输入输出、条件判断、循环这些最基础的程序结构,还有列表、字典这些平时常听到的序列概念,虽然不会灵活运用,但起码不再完全陌生了。
我觉得 Python 本身其实挺有意思的,能做天气爬取、做数据整理、画图表,还能写小工具,现实里好多地方都能用得上。只是课程进度太快,知识点又多,稍微走神就跟不上,后面越听越懵,像正则表达式、爬虫那些稍微难一点的内容,我基本就只能混个眼熟,完全搞不懂底层原理,也写不出来相关代码。
能明显感受到,这门课更适合有一点编程基础的同学,像我这种零基础的,跟着课堂节奏学起来真的有点吃力。很多内容老师讲得偏快,案例也跳得有点快,来不及慢慢琢磨,一节课就过去了,课后自己翻看课件,还是很多地方看不太懂。
说说我自己的一点小想法吧。我真觉得以后这种选修课,可以稍微放慢一点进度,不用一口气塞太多难懂的知识点。多带我们手写一些简单的小代码,从最基础的小程序慢慢练,而不是一下子就跳到爬虫、正则这些难的内容。要是每节课课件和案例代码都提前发下来,课后我们也能自己慢慢回看、慢慢琢磨,不至于课上跟不上,课后也没地方补。
虽然这学期没能把 Python 学精通,复杂的代码和专业知识我也掌握不来,但好歹入门了,也有了自己的码云仓库。起码不再对编程一头雾水,大概知道编程是干嘛的、Python 能做什么,也算不白学这门选修课。

posted @ 2026-05-25 20:33  luv86  阅读(10)  评论(0)    收藏  举报