Fetch the repository succeeded.
import requests
from bs4 import BeautifulSoup
import logging
from apscheduler.schedulers.blocking import BlockingScheduler
import matplotlib
import matplotlib.pyplot as plt
import io
import base64
import re
import sys
PUSH_KEY = "SCT282155Tbsscmv2v2zHLna06jIuvwZgv"
PUSH_URL = f"https://sctapi.ftqq.com/{PUSH_KEY}.send"
CITY_CODE = "101010100"
BASE_URL = f"https://www.weather.com.cn/weather/{CITY_CODE}.shtml"
AIR_QUALITY_URL = f"https://www.weather.com.cn/air/{CITY_CODE}.html"
NOTIFICATION_HOUR = 21
NOTIFICATION_MINUTE =21
WIND_THRESHOLD = 6
AQI_THRESHOLD = 150
RAIN_KEYWORDS = ["雨", "雷", "雹", "雪", "雾", "霾"]
LOG_FILE = ".venv/weather_crawler.log"
MATPLOTLIB_FONT = "SimHei"
matplotlib.use("Agg")
plt.rcParams["font.family"] = MATPLOTLIB_FONT
plt.rcParams["axes.unicode_minus"] = False
def setup_logging():
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler = logging.FileHandler(LOG_FILE, encoding="utf-8")
stream_handler = logging.StreamHandler()
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
return logger
def fetch_weather_data(logger):
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"Referer": "https://www.weather.com.cn/"
}
try:
response = requests.get(BASE_URL, headers=headers, timeout=15)
response.encoding = "utf-8"
if response.status_code != 200:
logger.error(f"天气数据请求失败,状态码:{response.status_code}")
return None
soup = BeautifulSoup(response.text, "lxml")
container = soup.find("div", id="7d") or soup.find("div", class_="t")
if not container:
logger.error("未找到天气数据容器")
return None
items = container.find_all("li", limit=3)
return parse_forecast_items(items, logger)
except Exception as e:
logger.error(f"获取天气数据失败:{str(e)}", exc_info=True)
return None
def parse_forecast_items(items, logger):
forecast = []
for idx, item in enumerate(items):
try:
date = item.find("h1").get_text(strip=True)
weather = item.find("p", class_="wea").get_text(strip=True)
temp = item.find("p", class_="tem").get_text(strip=True)
wind = item.find("p", class_="win").get_text(strip=True)
max_temp, min_temp = parse_temperature(temp)
wind_level = parse_wind_level(wind)
forecast.append({
"date": date,
"weather": weather,
"max_temp": max_temp,
"min_temp": min_temp,
"wind_level": wind_level,
"day_index": idx
})
except Exception as e:
logger.error(f"解析第{idx + 1}天数据失败:{str(e)}", exc_info=True)
continue
return forecast if forecast else None
def parse_temperature(temp_str):
try:
temps = temp_str.split("/")
return int(temps[0].replace("℃", "")), int(temps[1].replace("℃", ""))
except:
return 0, 0
def parse_wind_level(wind_str):
match = re.search(r"\d+", wind_str)
return int(match.group()) if match else 0
def fetch_air_quality(logger):
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"Referer": "https://www.weather.com.cn/air/",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9",
"Connection": "keep-alive"
}
try:
logger.info(f"正在请求空气质量数据:{AIR_QUALITY_URL}")
response = requests.get(AIR_QUALITY_URL, headers=headers, timeout=15)
response.encoding = "utf-8"
if response.status_code != 200:
logger.warning(f"空气质量请求失败,状态码:{response.status_code},使用默认值")
return 100
soup = BeautifulSoup(response.text, "lxml")
# 最新页面AQI位于<div class="aqi_num">标签内
aqi_elem = soup.find("div", class_="aqi_num")
return int(aqi_elem.text) if aqi_elem else 100
except Exception as e:
logger.error(f"获取空气质量失败:{str(e)}", exc_info=True)
return 100
def check_running_status(forecast, aqi):
tomorrow = next((d for d in forecast if d["day_index"] == 1), None)
if not tomorrow:
return "⚠️ 未获取到明日天气数据,跑操状态未知"
has_rain = any(keyword in tomorrow["weather"] for keyword in RAIN_KEYWORDS)
has_strong_wind = tomorrow["wind_level"] >= WIND_THRESHOLD
has_pollution = aqi >= AQI_THRESHOLD
if has_rain or has_strong_wind or has_pollution:
reasons = []
if has_rain: reasons.append(f"天气{tomorrow['weather']}")
if has_strong_wind: reasons.append(f"风力{tomorrow['wind_level']}级")
if has_pollution: reasons.append(f"AQI{aqi}污染")
return f"🚫 明日不跑操!原因:{', '.join(reasons)}"
else:
return "✅ 明日正常跑操"
def send_notification(forecast, aqi, logger):
if not forecast or len(forecast) < 2:
logger.warning("有效数据不足,跳过通知")
return False
city_name = "北京"
title = f"{city_name} 明日天气及跑操提醒"
content = f"""
🌞 今日天气({forecast[0]['date']})
天气:{forecast[0]['weather']}
温度:{forecast[0]['min_temp']}~{forecast[0]['max_temp']}℃
风力:{forecast[0]['wind_level']}级
🌡️ 明日天气({forecast[1]['date']})
天气:{forecast[1]['weather']}
温度:{forecast[1]['min_temp']}~{forecast[1]['max_temp']}℃
风力:{forecast[1]['wind_level']}级
空气质量:{"良好" if aqi < AQI_THRESHOLD else f"污染(AQI{aqi})"}
{check_running_status(forecast, aqi)}
""".strip()
payload = {"title": title, "desp": content}
try:
logger.info("正在发送微信通知...")
response = requests.post(PUSH_URL, data=payload, timeout=15)
result = response.json()
if result.get("code") == 0:
logger.info("通知发送成功")
return True
else:
logger.error(f"通知发送失败:{result.get('message')}")
return False
except Exception as e:
logger.error(f"发送通知时出错:{str(e)}", exc_info=True)
return False
def daily_notification_job(logger):
logger.info("===== 天气提醒任务开始执行 =====")
weather_data = fetch_weather_data(logger)
aqi = fetch_air_quality(logger)
if weather_data:
send_notification(weather_data, aqi, logger)
else:
logger.warning("未获取到有效天气数据,任务终止")
logger.info("===== 任务执行完毕 =====\n")
if __name__ == "__main__":
logger = setup_logging()
if len(sys.argv) > 1 and sys.argv[1] == "--test":
mock_forecast = [
{"date": "6月8日", "weather": "晴", "max_temp": 32, "min_temp": 22, "wind_level": 3, "day_index": 0},
{"date": "6月9日", "weather": "雷阵雨", "max_temp": 28, "min_temp": 20, "wind_level": 7, "day_index": 1}
]
send_notification(mock_forecast, aqi=180, logger=logger)
else:
scheduler = BlockingScheduler()
scheduler.add_job(
daily_notification_job,
"cron",
hour=NOTIFICATION_HOUR,
minute=NOTIFICATION_MINUTE,
args=[logger],
name="Daily Weather Notification"
)
logger.info(f"定时任务已设置:每天{NOTIFICATION_HOUR}:{NOTIFICATION_MINUTE}自动发送通知")
logger.info("程序运行中... (按Ctrl+C退出)")
scheduler.start()
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。