☰
Python定时推送天气给女友:微信公众号模板消息实战教程
2026/10/21 5:28:49 网站建设 项目流程

简介:这份资源面向具备少量编程基础、希望为伴侣定制每日问候的开发者,围绕微信公众号定时推送天气与纪念日提醒这一场景,提供可落地的实现方案。资源包共5个文件,以py主程序、yml自动化工作流配置、txt配置与说明、md文档为主,压缩包约5KB,体积轻巧,便于直接部署到GitHub Actions实现每日线上自动运行。内容涵盖最新完整版、简化版、星座单独版、星座加天气版以及php版本等多套成品与消息模板,需自行申请天行、和风等天气接口的API密钥。目前已有7695人学习下载,读者可据此快速搭建属于自己的推送服务,理解定时任务、接口调用与消息模板配置的完整链路,并参考多版本差异按需裁剪功能,省去从零摸索的时间。

1. 从一条“今天降温记得加衣”说起:公众号天气推送到底怎么跑通

很多人第一次动念头做天气推送,不是因为想学开发,而是因为某天早上收到一句“今天降温,记得加衣”比闹钟还管用。微信公众号每日消息推送天气给女朋友制作教程,本质上就是一套“定时抓天气 → 拼成文案 → 调公众号接口发出去”的最小闭环。它不复杂,但涉及三个容易翻车的环节:天气数据从哪来、模板消息怎么发、定时任务怎么稳。适合会一点 Python、想给身边人做点小工具的人,也适合想练手服务号接口的开发者。下面按我实际拆过的顺序讲,能直接抄。

2. 选型与准备:天气接口、公众号类型和服务器怎么定

2.1 先确认你的公众号有没有发模板消息的资格

微信公众号分订阅号和服务号,个人主体一般只能注册订阅号,而订阅号没有模板消息接口权限。这是第一个卡点,很多人代码写完了才发现调不通。常见做法是:如果只是发给一个人,用服务号(企业主体)最顺;如果只有订阅号,可以退而求其次用“客服消息”,但客服消息要求用户 48 小时内有过互动,不适合每天定时推。所以选型阶段先登录公众平台,在“功能 → 模板消息”里看有没有入口,没有就别往下写代码了,换方案。

模板消息本身需要先申请一个模板,模板里用{{keyword.DATA}}占位。天气推送一般申请两个字段:城市天气概况、温馨提示。审核通过后你会拿到一个template_id,这个后面要写进配置。

2.2 天气数据源:免费接口够不够用

天气数据我用的是和风天气的免费版,注册后拿到key,接口返回 JSON,字段清晰。免费版每天有调用次数限制,个人推送一天一次完全够。请求地址形如:

# 实时天气接口,location 填城市 ID 或经纬度 https://devapi.qweather.com/v7/weather/now?location=101010100&key=你的KEY

返回里now.temp是温度,now.text是天气现象。注意免费版部分字段(比如生活指数)要单独申请,别一股脑全调,容易超限。如果你不想注册,也可以用 OpenWeatherMap,但国内访问偶尔慢,自己权衡。

2.3 服务器和运行环境

定时任务需要一个能常驻的地方。最省事的是云函数(比如某云厂商的定时触发器),不用自己维护服务器;其次是买一台最低配的云服务器,装 Python 3.8+ 和requests、apscheduler两个库。我一般用云服务器 +crontab,因为调试方便,日志能直接看。目录结构建议这样:

weather-push/ ├── config.py # 存 key、template_id、用户 openid ├── weather.py # 抓天气、拼文案 ├── push.py # 调公众号接口 └── main.py # 入口,串起来

把敏感信息放config.py,别硬编码在业务代码里,后面换 key 不用翻全文件。

3. 核心实现:抓天气、拼模板、调接口三步走

3.1 抓天气并组装成模板需要的字段

先写weather.py,负责请求和解析。关键是把接口返回的字段映射成模板占位符,别在推送时才拼字符串,容易乱。

import requests def get_weather(city_id, key): # 实时天气接口,超时设 5 秒,避免卡死定时任务 url = f"https://devapi.qweather.com/v7/weather/now?location={city_id}&key={key}" resp = requests.get(url, timeout=5) data = resp.json() # 接口返回 code 为 "200" 才算成功,其他都是异常 if data.get("code") != "200": raise RuntimeError(f"天气接口返回异常: {data.get('code')}") now = data["now"] return { "temp": now["temp"], # 温度,字符串 "text": now["text"], # 天气现象,如“晴” "feels": now["feelsLike"] # 体感温度 }

逻辑说明:timeout=5是防止接口挂起导致整个任务卡住;code判断不能省,免费接口偶尔会返回限流码。参数city_id用和风天气的城市列表查,比如北京是101010100,别用中文城市名,容易匹配错。

3.2 拼模板文案:把冷冰冰的数字变成人话

模板消息的data字段是键值对,每个值要符合模板定义的格式。我一般把天气概况和提示分开:

def build_template_data(weather): # 根据温度给一句提示,阈值可以自己调 temp = int(weather["temp"]) if temp <= 5: tip = "今天很冷,出门记得穿厚外套" elif temp <= 15: tip = "有点凉,带件外套别感冒" else: tip = "温度还行,但别贪凉" return { "weather": {"value": f"{weather['text']} {weather['temp']}℃"}, "tip": {"value": tip} }

这里weather和tip必须和你在公众号后台申请的模板字段名一致,大小写都不能错。value是固定写法,别直接写字符串。温度阈值我试过 5/15 两档,够用;想更细可以加湿度、风力,但模板字段要提前申请。

3.3 调公众号接口发送:access_token 是绕不开的坑

发送前要先拿access_token,它有效期 7200 秒,且每天获取次数有限,不能每次推送都重新拿。常见做法是缓存到本地文件或 Redis,过期再刷新。

import requests, json, time, os def get_access_token(appid, secret): cache_file = "token_cache.json" # 先读缓存,没过期直接用 if os.path.exists(cache_file): with open(cache_file) as f: cache = json.load(f) if cache["expire_at"] > time.time(): return cache["token"] # 缓存失效,重新请求 url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={secret}" resp = requests.get(url, timeout=5).json() if "access_token" not in resp: raise RuntimeError(f"获取 token 失败: {resp}") token = resp["access_token"] # 提前 300 秒过期,留出网络延迟余量 with open(cache_file, "w") as f: json.dump({"token": token, "expire_at": time.time() + resp["expires_in"] - 300}, f) return token def send_template(openid, template_id, data, token): url = f"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token={token}" payload = { "touser": openid, "template_id": template_id, "data": data } resp = requests.post(url, json=payload, timeout=5).json() # errcode 为 0 才算发送成功 if resp.get("errcode") != 0: raise RuntimeError(f"发送失败: {resp}") return resp

参数说明:touser是接收者的 openid,不是微信号,需要用户先关注公众号并授权获取;template_id是模板申请后拿到的 ID;data就是上一步拼好的字典。errcode判断必须做,常见错误 40001 是 token 失效,40037 是模板 ID 不对。

3.4 用定时任务串起来

最后main.py把三步串起来,再用crontab每天固定时间跑:

from weather import get_weather from push import get_access_token, send_template from config import CITY_ID, KEY, APPID, SECRET, TEMPLATE_ID, OPENID def main(): weather = get_weather(CITY_ID, KEY) data = build_template_data(weather) token = get_access_token(APPID, SECRET) send_template(OPENID, TEMPLATE_ID, data, token) if __name__ == "__main__": main()

crontab配置:

# 每天早上 7 点执行,日志写到 push.log 0 7 * * * /usr/bin/python3 /home/user/weather-push/main.py >> /home/user/weather-push/push.log 2>&1

日志重定向别省,出问题全靠它。时间用服务器时区,如果服务器是 UTC,记得换算成北京时间。

4. 避坑与排查:那些让我半夜爬起来改代码的瞬间

4.1 现象:接口返回 40001,token 明明刚拿的

原因:access_token被其他程序刷新了,或者你本地缓存和服务器缓存不一致。公众号的 token 是全局的,同一时间只能有一个有效。解决:统一用一处缓存,别多个脚本各拿各的;如果用了云函数,把 token 存到云数据库而不是本地文件。

4.2 现象:模板消息发出去,用户收不到

原因:用户取关了,或者 openid 不对。openid 是公众号下的唯一标识,换公众号就变了。解决:发送前先调user/info接口查一下关注状态,取关了就别发,省得浪费调用次数。

4.3 现象:天气接口偶尔超时,任务直接挂

原因:免费接口有波动,requests没设重试。解决:加一层重试,用tenacity库或者手写循环,重试 2 次,间隔 1 秒。别无限重试,会把定时任务拖死。

4.4 现象:crontab 不执行,手动跑却正常

原因:crontab 的环境变量和登录 shell 不一样,python3路径可能找不到。解决:在 crontab 里写绝对路径,或者在脚本开头source一下环境。我一般直接在 crontab 里写/usr/bin/python3,省事。

4.5 现象:推送时间不准,偶尔晚几分钟

原因:服务器负载或者 crontab 调度延迟。解决:对时间要求高就改用apscheduler常驻进程,精度到秒;但常驻进程要处理异常退出,各有利弊。

5. 进阶技巧:让推送更稳、更省、更像“人发的”

跑通之后,我一般会做三件事。第一,加一个“失败告警”:如果发送失败,给自己发一封邮件或者一条 Server 酱消息,别等女朋友问“今天怎么没发”才发现。第二,把天气接口的返回缓存 10 分钟,防止同一天多次调试触发限流。第三,文案别太机械,可以按星期几换句式,比如周五加一句“周末愉快”,成本极低但体验差很多。

验证方法很简单:手动跑一次main.py,看日志里errcode是不是 0,再看手机有没有收到。如果收到但字段错位,多半是模板字段名和data的 key 没对上,回公众号后台核对。

检查项正常表现异常处理
access_token缓存文件存在且未过期删除缓存重新获取
天气接口 code返回 "200"检查 key 和城市 ID
发送 errcode返回 0按错误码查文档
crontab 日志有执行记录检查路径和权限

从那以后我每次上线定时任务前,都强制先手动跑三遍,确认 token 缓存、接口重试、日志输出都正常,再交给 crontab。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询