简介:面向需要自动向微信好友推送指定城市天气的Python学习者,这份压缩包提供了一套完整的定时任务脚本方案,覆盖天气数据抓取、城市代码匹配、定时调度与微信消息发送等环节,适合有一定Python基础、希望实践爬虫和自动化通知的开发者参考。压缩包共7个文件,由6个py脚本与1个城市代码文本文件构成,整体仅7KB,代码量精简且模块划分清晰,便于快速阅读和二次修改。已有1450人学习下载,说明该案例对相似需求具有较高的参考价值。通过阅读代码,可以掌握使用requests请求天气接口并解析返回信息的思路,理解Timer定时器如何驱动周期任务,以及如何将微信好友发送逻辑独立封装;同时,城市代码文本文件展示了城市与代码的映射方式,为实现多城市天气监控、定时关怀提醒等功能提供了可直接复用的脚本模块与工程组织方法。
1. 每天定时发一条天气给关心的人,这事值不值
你在外地工作,手机里存着老家和几个重要朋友的微信,每天想问候又不知道说什么。天气就是那个最自然的开场白。与其早上醒来翻天气再复制粘贴,不如写一个 Python 定时任务,每天七点把指定城市的天气抓下来,算好穿衣建议,直接推到群里的微信好友。这个脚本的全部价值就一句话:把“想关心但没空表达”变成“每天自动送到”。适合有一定 Python 基础、想落地一个真实小工具的开发者,也适合第一次接触定时任务和 webhook 推送的人。整个项目只依赖 requests 和 APScheduler 两个库,不碰手机协议,不用复杂框架。
2. 天气数据从哪来:选对数据源,后面少踩一半坑
2.1 为什么先从公共天气接口入手,而不是直接上商业 API
很多第一次做天气脚本的人,第一反应是注册一个商业天气平台的账号,拿 API Key。这确实省事,但有几个现实问题:免费额度有限,个人开发用着用着就超限;部分平台要求实名甚至绑卡;还有不少平台把“实时天气”和“生活指数”拆成不同接口,每个都要单独调。更麻烦的是,一旦你把 key 写死在脚本里,半年后 key 过期,定时任务就悄悄断掉了。
我一般会先看目标城市所在的公共天气服务网页。这类网页本身是给人看的,每天更新,城市代码稳定,不限制个人访问频率。只要解析其中的结构化区块,就能拿到温度、湿度、风力和天气现象。缺点是有反爬风险,但我们的频率是每天一次,远低于正常访问量,风险很小。另一个常见做法是直接调用高德或和风天气的公开 JSON 接口,只要不需要实时分钟级数据,免费档就够用。
选择数据源时有一个判断标准:你的脚本多久跑一次。每天一次属于低频,不需要商业 SLA;反而更需要“代码不发散、字段稳定、不登录也能拿”。公共天气网页恰好满足这三点。如果将来数据源改版,你只需要改一个解析函数,不影响推送链路。
2.2 城市代码:先看懂 URL 里的编码规律再写抓取
几乎所有天气网页都有一个特点:每个城市对应一串数字编码,编码出现在 URL 里。比如某个页面结构是/city/101010100.html,最后的九位数字就是城市代码。这个代码不是随机生成的,通常遵循“省级-市级-区县”的分段规则。我们没必要去研究编码算法,只要能把“城市名”映射到“城市代码”就行。
我的做法是把城市代码放在一个独立的city.json文件里,结构是{"城市名": "101010100"}。这样改动一个城市不需要改脚本,只需要修改配置。脚本启动时读取这个文件,通过命令行参数选择目标城市。放在配置文件里的原因很简单:定时任务里的命令越短越好,如果哪天要换个城市推送,不去动 Python 代码,只改 json 文件,风险最小。
获取城市代码的路径一般是:先打开对应的天气网页,手动查一次目标城市,看浏览器地址栏最后的数字串;或者爬取网站的省份城市列表页,但那是另一个复杂度。对个人脚本来说,手动查一次花两分钟,比写一个城市列表抓虫器值得。
2.3 用 requests 加 BeautifulSoup 抓取页面核心字段
确定了页面结构后,抓取逻辑就很清晰。下面是核心代码,它抓取页面上几个关键区块:当前温度、天气现象、湿度和风向。为了让代码能直接跑,我用了BASE_URL和CITY_CODE作为占位,你使用时替换成实际服务的地址和城市代码。
import requests from bs4 import BeautifulSoup BASE_URL = "https://weather.example.com/{city_code}.html" # 替换为实际天气页面 CITY_CODE = "101010100" # 替换为目标城市代码 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", "Referer": BASE_URL.format(city_code=CITY_CODE) } def fetch_weather(city_code: str) -> dict: url = BASE_URL.format(city_code=city_code) resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() resp.encoding = "utf-8" soup = BeautifulSoup(resp.text, "html.parser") # 提取当前温度,页面里通常有一个 class 包含 now 和 temp temp_tag = soup.select_one(".now .temp") weather_tag = soup.select_one(".now .weather") humidity_tag = soup.select_one(".now .humidity") wind_tag = soup.select_one(".now .wind") data = { "temp": temp_tag.text.strip() if temp_tag else "N/A", "weather": weather_tag.text.strip() if weather_tag else "N/A", "humidity": humidity_tag.text.strip() if humidity_tag else "N/A", "wind": wind_tag.text.strip() if wind_tag else "N/A", } return data if __name__ == "__main__": print(fetch_weather(CITY_CODE))这段代码的关键点有两个。第一,headers里必须带User-Agent和Referer,缺少任何一个,某些站点会直接返回 403 或跳转到验证页。第二,timeout=10是必须的,天气接口偶尔会慢,如果没有超时控制,定时任务会卡在请求上,后面推送全部排队。
2.4 字段解析:温度、天气、风向、湿度一起拿
上面的代码只拿“当前”数据,但给好友看的消息最好还包含最低温和最高温。很多天气页面在另一个区块里放着一天的预报,比如“白天最高温 18 度,夜间最低温 9 度”。如果只抓当前温度,夏天可能显示 29 度,朋友却不知道晚上降温要加外套。
我在实际脚本里会再解析一组today数据,把最高温和最低温抓出来,拼在推送消息里。字段选择上有一个建议:宁可少拼一个字段,不要为了凑齐所有数据去解析复杂表格。表格解析在页面改版时最容易崩,而且一个字段崩了可能导致整个脚本报错。稳定的做法是抓两个区块:当前天气和今日温度区间,其他生活指数等字段能抓就抓,抓不到就用默认值。
抓取字段后应该先打印一次,确认数据是想要的内容。如果你发现某个字段是空字符串,不要急着调正则,先打开页面看元素结构变了没。页面结构每变一次,解析选择器就要跟着调整,这是整个项目里维护频率最高的地方。
3. 把天气塞进微信:企业微信群机器人是当前最稳的路
3.1 群机器人 webhook:一分钟拿到的推送入口
要给微信好友发消息,最忌讳的是去用个人微信自动化方案。那些基于网页版微信的协议库,常年封号,而且现在很多账号已经无法登录网页版。真正稳定且官方支持的是企业微信群机器人:拉一个群,把好友都拉进来,再添加一个群机器人,它会给你一个 webhook 地址。你用脚本往这个地址 POST 一段 JSON,就能把消息发到群里。
有人会问,这不是发到群里不是私聊吗?实际使用里,为了给“关心的好友”推送,最轻量的方式就是建一个只有这几个人的群,群名叫“天气小分队”,每天早上机器人发一条。如果非要私聊,可以用企业微信应用消息接口,但那需要企业认证和应用配置,复杂度高很多。对个人开发者和家庭场景,群机器人已经够用,这也是社区里最稳定的做法。
创建机器人的步骤大概是:在企业微信群里打开群设置,找到“群机器人”,点击“添加”,给它起个名字,生成 webhook。把 webhook 地址复制下来,保存到你的配置文件或环境变量里。这个地址就像一把钥匙,谁拿到谁就能往里发消息,所以千万不要提交到公开仓库里。
3.2 用 markdown 拼一条干净天气卡片
企业微信群机器人支持文本和 markdown 两种格式。markdown 消息在微信里会渲染成带样式的小卡片,比纯文本好看很多,也适合放温度数据。需要注意,群机器人的 markdown 语法是有限子集,不支持复杂 HTML,但支持加粗、字体颜色、引用和换行。
import requests import json WEBHOOK_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=replace-me" def send_weather(webhook: str, city: str, data: dict) -> bool: msg = ( f"## {city}今日天气\n" f"> 当前:{data['temp']}℃ {data['weather']}\n" f"> 湿度:{data['humidity']}\n" f"> 风力:{data['wind']}\n" f"> 今日:{data['temp_low']}℃ ~ {data['temp_high']}℃\n" f"<font color=\"info\">记得照顾好自己</font>" ) payload = { "msgtype": "markdown", "markdown": {"content": msg} } resp = requests.post(webhook, json=payload, timeout=5) return resp.json().get("errcode") == 0 if __name__ == "__main__": weather = {"temp": "23", "weather": "多云", "humidity": "55%", "wind": "东南风3级", "temp_low": "18", "temp_high": "27"} send_weather(WEBHOOK_URL, "A城", weather)send_weather的返回值依赖于企业微信返回的errcode,只有为 0 才算成功。这里我把超时设置为 5 秒,因为推送失败可以重试,但不要让定时任务一直挂在一个失败请求上。消息里的<font color="info">是企业微信 markdown 的专属写法,不要换成标准 markdown 的颜色语法,否则不会渲染。
3.3 想私发给单个好友:企业微信应用消息的替代方案
如果你真的需要让每个好友各自收到私聊消息,而不是群消息,可以用企业微信的“客户联系”或“应用消息”。但代价比群机器人高不少:你需要注册一个企业微信组织,创建自建应用,获取corpid、agentid和secret,再通过 API 获取 access_token,最后调用消息发送接口。整个流程对新手来说至少要折腾半天。
从投入产出比看,我不建议在第一个版本就做私聊推送。原因很简单:群机器人十分钟上线,私聊方案要先解决认证、用户关系、消息模板三件事。等群机器人跑了两个月,确认稳定了,再谈私有化通知。如果你只是给爸妈和三五好友推天气,群消息完全够用,他们反而能在群里聊起来。
如果将来真要改造,架构上只需要把send_weather的发送逻辑拿出来,换一个发送函数即可,前面的天气抓取代码完全不用动。这就是在写代码时把“抓取”和“推送”拆成两个函数的好处。
4. 定时任务:从手动跑到每天自动跑
4.1 先用 APScheduler 在脚本内部定时
早期调试阶段,我不会直接挂到系统计划任务,而是先在脚本里用 APScheduler 跑一次。它支持按固定时间触发,比如每天早上07:30。好处是同一台机器上,即使系统计划任务配置出错,脚本内部仍然能自动触发,适合在个人电脑上跑。
from apscheduler.schedulers.blocking import BlockingScheduler def job(): weather = fetch_weather(CITY_CODE) send_weather(WEBHOOK_URL, "A城", weather) print("推送完成") scheduler = BlockingScheduler(timezone="Asia/Shanghai") scheduler.add_job(job, trigger="cron", hour=7, minute=30, id="weather_job") scheduler.start()timezone参数必须显式指定,否则 APScheduler 会使用系统时区,跨平台部署时会因为时区不同导致时间差八小时。add_job的id用来标识任务,重复添加同 id 的任务会报错,这在脚本被意外重复运行时很有用。这条代码运行后会阻塞主进程,所以适合在后台挂机运行。
4.2 部署到 Windows 计划任务
APScheduler 虽然内部能定时,但有一个致命弱点:脚本必须一直保持运行。如果电脑重启,或者 Python 进程被关掉,任务就没了。所以正式部署还是建议交给系统。Windows 上最常用的是“任务计划程序”,配置起来并不复杂。
先写一个run_weather.py作为入口,它不做任何其他事情,只调用主任务函数并退出。然后在任务计划程序里新建任务,触发器设为每天 07:30,操作里“程序或脚本”填 Python 的完整路径,“添加参数”填run_weather.py的完整路径,“起始于”填脚本所在目录。这里最容易出错的是“起始于”字段,如果为空,脚本里用相对路径读取city.json会失败。
我把任务配置成一个“即使上次运行失败也重复每 30 分钟运行一次”的规则,防止某次抓取超时后整天的推送丢失。Windows 计划任务的“设置”选项卡里有“如果任务失败,按以下频率重新启动”的选项,设置成 30 分钟一次,最多三次。这样你就不需要自己在代码里写重试循环。
4.3 部署到 Linux crontab
如果脚本跑在云服务器或树莓派上,用 crontab 更直接。先确保run_weather.py可执行,然后在 crontab 里写一行:
30 7 * * * cd /path/to/project && /usr/bin/python3 run_weather.py >> weather.log 2>&1这行的意思是每天 7:30 执行一次,输出和错误都追加到weather.log。这里我建议用绝对路径,包括cd的路径和 Python 解释器的路径,因为 cron 环境里 PATH 很精简,找不到python3很常见。>> weather.log 2>&1是为了保留日志,后面排查问题全靠它。
cron 的时区跟随系统,如果你的服务器是 UTC 时区,7:30 就会变成国内时间 15:30。需要先执行date查看系统时区,如果不是国内时区,建议在代码里把 APScheduler 的 timezone 固定为Asia/Shanghai,或者直接改系统时区。我用的是后者,因为 cron 对时区不敏感,改系统时区更省心。
5. 避坑指南:定时抓天气发微信的五个常见坑
5.1 推送里显示的永远是昨天的温度
现象:定时任务正常触发,群里也收到了消息,但温度明显和当天实时对不上。
原因:天气页面在某些时段只有缓存数据,或者你解析的是“昨日温度”区块。还有可能是你在凌晨 0 点跑,页面数据还没更新。
解决:第一,把抓取时间调到早上 7 点后,此时页面的今日数据已经更新;第二,在解析时打印原始数据,确认你选中的元素的class或id到底属于哪个区块;第三,增加一个校验逻辑,如果温度字段为空或等于昨天的值,本次不推送并把日志打出来。
5.2 定时任务到点不执行,脚本手动跑完全正常
现象:在命令行里运行python run_weather.py没有任何问题,但计划任务到时间不触发。
原因:Windows 计划任务里 Python 路径或工作目录配置错误;cron 里脚本权限不够,或者 Python 解释器使用了不同的环境,缺少依赖包。
解决:先用系统的运行日志看任务是否被启动。在登录交互式运行改成“不管用户是否登录都要运行”的同时,把“启动于”目录填绝对路径。Linux 上先用crontab -l确认任务已写入,再在脚本第一行打印一个时间戳,观察日志里是否有输出。
5.3 webhook 请求频繁被限制
现象:有一天突然连续收到多条相同天气消息,后来机器人不再发送,提示errCode: 45009。
原因:你的脚本可能因异常启动了多个实例,或者你测试时手动执行了多次脚本。企业微信群机器人有频率限制,一分钟内最多发消息次数有限。
解决:在定时任务里加单实例锁。最简单的方式是脚本启动时尝试创建一个锁文件,如果文件已存在则直接退出。同时给 webhook 请求加一个重试上限,不要无限重发。日常调试用另一个机器人,生产机器人只留给定时任务。
5.4 城市代码失效导致推送失败
现象:抓取函数提醒raise_for_status报 404,或者返回的页面结构里解析不出任何字段。
原因:天气网站调整了城市代码规则,或者目标城市被合并到其他行政区划。
解决:不要依赖代码里的固定值,把城市代码放进city.json,并在抓取成功后校验温度字段是否存在于数据中。如果连续三天抓取失败,让脚本发一条预警消息到自己的备用机器人,提醒城市代码需要维护。这就是把错误当成数据来通知,而不是只在日志里记录。
5.5 推送消息里的 emoji 在 PC 微信上显示成方块
现象:手机上看到晴天符号正常,在电脑版微信上却显示为一个小方块。
原因:PC 微信对部分 Unicode emoji 字体支持不全,尤其是一些较新的天气符号。
解决:天气消息里不用 emoji 图标,改用纯文字如“晴”“多云”“小雨”。如果一定要图形,只使用微信内置的 markdown 引用线和字体颜色来区分重点。这也是为什么我的模板里只用了<font color>而没有加任何 emoji 的原因。
6. 进阶:在天气推送里加入穿衣建议和出行提醒
6.1 根据温度区间生成建议文案
推送消息里如果只有一串数字,看多了会麻木。我在实际使用中会加一条温度建议:低于 5 度提示“厚羽绒服”,5 到 15 度提示“风衣或夹克”,15 到 25 度提示“长袖或卫衣”,25 度以上提示“短袖或裙子”。这个逻辑用一个简单的函数就能完成,数据来自当天最高温。
6.2 把密钥放进环境变量而不是写死在代码
webhook 地址和城市代码属于敏感信息。我后来把WEBHOOK_URL改为从环境变量读取,脚本里只做读取,不写默认值。这样即使脚本被人看到,对方也无法直接拿到推送入口。建议你也在.env文件里管理这些变量,并用python-dotenv加载。
6.3 从“能发”到“稳定发”,一次完整的自测流程
我给自己定的验收标准是连续跑七天不人工干预。第一周每天上午检查日志和群里消息,确认推送时间、内容、样式都正常。如果某天漏发,不是急着改重试,而是先看是哪一环断了:抓取、组装还是推送。把这一环修复后,回放一次当天的数据,确认修复有效。这个习惯帮我避免了很多“改了脚本但不知道自己有没有修好”的尴尬。
最后我想说,这个天气小工具最容易翻车的不是代码,而是“跑两天就再也不想维护了”。我自己的经验是,把它当成一个长期运行的小服务来对待,该留日志就留日志,该拆函数就拆函数。只要数据源和 webhook 都稳定,它就能安安静静地替你关心人。希望这个方案能帮到你,让你的问候比别人早到一步。
本文还有配套的精品资源,点击获取