资讯动态

用Python+公众号模板消息实现每日天气自动推送

发布时间:2026/10/15 16:09:21 来源:尧图企业网站定制
简介微信公众号每日自动推送天气与纪念日通知的制作教程专门面向想用技术为女朋友制造惊喜、有一点编程基础的学习者目标是实现无需手动干预的每日定时消息提醒。包体很精简共5个文件、大小仅5KB涵盖Python主程序、配置项、GitHub Actions工作流与说明文档py承担天气与纪念日信息组装yml负责每日自动运行txt与md提供API参数和部署指引。目前已有7692人学习浏览属于热度较高的小项目资源。内容包括成品源码、消息模板和完整的线上运行配置拿到后替换天行/和风的API密钥、填好祝福语即可部署作者亲测可行并分享了申请API、配置定时任务与排错的经验照着操作就能为女朋友做出专属的天气提醒公众号。相比普通礼物这类用心的代码小功能更容易打动人心适合节日、纪念日或日常浪漫时刻使用。1. 项目定位用微信公众号每天给女朋友推天气做一件能跑起来的浪漫小事早晨八点手机弹出一条微信通知发件人不是同事也不是快递而是自己写的一个公众号内容是今天的天气、温度和一句“记得带伞”。很多人第一眼看到这类教程觉得难度在“调天气接口”真正动手才发现卡人的永远是消息通道公众号消息权限没理清、access_token 缓存没写好、定时任务在服务器上悄悄失效代码在本地跑得再欢线上还是发不出去。这套制作教程把天气获取、文案拼装、微信推送、定时执行拆成四个独立模块每一步都给出可运行代码和验证手段目标是让一条天气通知从接口数据变成每天准时出现在手机上的消息。适合想给女朋友做自动化推送的开发者也适合第一次摸公众号接口的人。2. 技术方案与整体架构一条天气数据从云接口到手机微信的完整链路先把这项目当一条数据管道看。每天触发一次天气数据从外部接口拉回来程序把结构化数据加工成一句人话再通过微信接口推给指定用户。整条链路上没有复杂的业务逻辑但每一环都依赖一个外部系统任何一个环节出问题这一天的推送就没了。所以架构设计追求的不是花哨而是“每个环节可以独立验证失败了知道该看哪里”。2.1 技术选型Python、Requests、APScheduler 配合公众号模板消息选型不用纠结这种轻量定时任务用 Python 写成本最低。requests 负责所有 HTTP 调用APScheduler 负责进程内定时调度配置用 YAML 外置。消息通道我选公众号模板消息接口而不是客服消息接口原因很实际客服消息有 48 小时会话限制用户不和公众号说话这条通道根本发不出去模板消息属于应用通知类消息用户关注了公众号、拿到对方 openid就能主动下发。这正好匹配“每天一条天气通知”的场景。天气数据源我习惯用某地图开放平台的天气接口返回结构稳定区分白天和夜间预报拼文案很顺手。真实项目里当然也可以换其他天气服务商差别只在请求参数和返回字段名。下面这张选型表后面换成别的接口时也留着参照。| 模块 | 选择 | 理由 | 注意点 | | Python 3.8 | 轻量任务 | 生态成熟写小任务不折腾 | 服务器上别装太新版本避免第三方库兼容问题 | | requests | HTTP 调用 | 同步请求写起来最简单 | 必须设 timeout不设可能长挂 | | APScheduler | 进程内调度 | 改时间不用碰 crontab | 进程退出调度就没了需要守护 | | 公众号模板消息 | 消息通道 | 主动推送不受会话限制 | 模板 ID 和字段 key 必须精确匹配 | | YAML | 配置外置 | 城市、账号、模板 ID 不写死在代码里 | 密钥不要提交进代码仓库 |这里有个容易被忽略的细节requests 的超时设置。不少教程里直接 requests.get(url) 不带 timeout某天天气接口响应卡住定时任务整个堵在那里消息晚了一两个小时。所以后面所有外部请求我都会加 timeout宁可这次失败等下次重试也不能让任务挂死。2.2 目录结构把天气、文案、微信三个关注点拆开代码量不大但为了二次开发方便建议最小结构就按下面这样拆weather-push/ |-- config.yaml # 城市编码、公众号 appid、secret、模板 ID、openid |-- requirements.txt # requests、pyyaml、apscheduler |-- weather.py # 天气接口拉取与解析 |-- message.py # 文案拼接 |-- wechat.py # access_token 管理与模板消息发送 |-- push.py # 主流程拉数据、拼文案、发消息 |-- scheduler.py # 定时调度入口 |-- run_cron.sh # 给 crontab 调用的包装脚本 └-- logs/ # 运行日志目录weather.py 只做一件事给一个城市编码返回解析后的天气结果。message.py 把天气结果转成最终下发的文本。wechat.py 封装微信的两个接口一个是拿 token一个是发模板消息。push.py 是所有模块串起来的主入口。scheduler.py 是“进程内定时”方案如果你最终用系统 crontab这个文件可以不部署。拆这么细的原因不是故作优雅而是每个外部接口都可能抽风。天气数据出问题只需要看 weather.py微信报错只需要看 wechat.py文案想改碰 message.py 就够了。真到排查问题那一天你会感谢当初少写了几个“大而全”的函数。2.3 一次推送从触发到送达的完整生命周期整条链路按时间顺序可以拆成六步定时器触发进入 push 入口读 config.yaml拿到城市编码、天气 key、公众号参数weather.py 请求天气接口解析出当天白天和夜间的天气、温度、风message.py 把数据拼成一段生活化文案同时拆成模板消息需要的数据结构wechat.py 先取 access_token带缓存再调用模板消息接口发送微信服务器完成下发用户微信里出现这条天气通知。第一次运行不要直接挂定时器应该先手动执行 push.py 一次确认第 3、4、5 步各自返回正常。这六个步骤里最容易被忽略的是第 5 步的 token 缓存很多人第一次跑通了第二天就没消息了原因就是 token 被重复刷新导致旧的失效。后面的避坑章节会专门说这个问题。3. 核心代码拆解天气查询、文案拼接、模板消息一个环节一个环节写这一章按模块来写顺着调用顺序走。三个模块之间没有隐藏依赖每个都能单独抽出来测这一点对排错非常重要。3.1 天气数据获取接口参数、返回解析与异常降级先看 weather.py。以某地图开放平台天气接口为例核心参数是三个城市编码、接口 key、是否返回全天预报。代码里强制设置了 timeout并对 status 做了判断import requests def fetch_weather(city_code: str, key: str) - dict: url https://restapi.example.com/v3/weather/weatherInfo params { city: city_code, key: key, extensions: all, } resp requests.get(url, paramsparams, timeout8) data resp.json() if data.get(status) ! 1: raise RuntimeError(fweather api error: {data.get(info)}) forecasts data.get(forecasts) or [] if not forecasts: raise RuntimeError(weather api return empty forecasts) return forecasts[0]这段代码做了两个防御动作。第一个是 status 判断接口返回 status 为“1”才表示成功否则 info 字段会带出错原因直接抛异常比带病往下走好排查。第二个是 forecasts 空判断城市编码传错时接口经常返回成功状态但结果为空这种“假成功”如果不在源头拦住后面拼出来的文案就是空的消息照样能发出去但内容没法看。第一次接入时我强烈建议把返回的完整 JSON 存一份到日志里因为字段名是 daytemp 还是 daytemp_float不同接口差异很大存下来看一遍比反复读文档快得多。等项目跑稳了再把这个调试日志降到 debug 级别。3.2 文案拼接把数据变成一句带情绪的话拿到了天气数据接下来是 message.py。这里要把气象数据翻译成人话同时为模板消息准备好数据字段。我的习惯是先拼成纯文本再在接口层拆字段这样后面改文案只动一个函数def build_message(forecast: dict, city_name: str) - dict: today forecast[casts][0] report_time forecast.get(reporttime, )[:10] text ( f早安今天{report_time}。\n f{city_name}白天{today[dayweather]} f{today[daytemp_float]}℃。\n f夜间{today[nightweather]} f{today[nighttemp_float]}℃。\n f风向{today[daywind]}风力{today[daypower]}级。\n f出门看天带伞不带伞自己判断 ) return { city: city_name, time: report_time, text: text, }注意模板消息的字段不能随便给。微信模板消息里每个变量都必须在公众平台选用的模板里存在比如模板里写了 {{city.DATA}}、{{time.DATA}}、{{text.DATA}}那 data 的 key 只能是 city、time、text多一个少一个都报错。所以 message.py 返回的是一个和模板一一对应的 dict后面发送时直接套上去。如果想让文案每天不重样可以把天气情况映射成一组形容词比如“晴天”对应“适合散步”“阵雨”对应“记得带伞”这种映射表放在文件底部运行时根据 today[dayweather] 去查表。这是成本最低的个性化改造。3.3 微信模板消息发送access_token 的缓存与接口调用wechat.py 是整个项目最容易翻车的地方因为 access_token 有 7200 秒有效期并且有效期内重复获取会导致之前的 token 失效。最经典的翻车现场是多次重启或两个任务各拿了一次 token后拿到的把先拿到的干掉某次发送突然报 40001。我的做法是进程内加一个简单缓存token 没过期就继续用并且提前 5 分钟视为过期避免在临界点踩到接口还没同步的坑import time import requests _token_cache {token: None, expire_at: 0} def get_access_token(appid: str, secret: str) - str: now time.time() if _token_cache[token] and _token_cache[expire_at] - now 300: return _token_cache[token] resp requests.get( https://api.weixin.qq.com/cgi-bin/token, params{ grant_type: client_credential, appid: appid, secret: secret, }, timeout5, ).json() if access_token not in resp: raise RuntimeError(fget access_token failed: {resp}) _token_cache[token] resp[access_token] _token_cache[expire_at] now int(resp.get(expires_in, 7200)) return resp[access_token]缓存这个动作看起来不起眼实际上决定了公众号会不会在某天突然被限流。微信对获取 token 的接口有频率限制反复刷新很容易触发轻则接口报错重则短暂封禁。我预留了 300 秒的余量也就是 token 实际还剩不到 5 分钟时就重新获取不会卡在最后几秒发送时发现 token 失效。发送模板消息是一个 POST 请求搞定def send_template_message(token: str, template_id: str, openid: str, data: dict) - dict: resp requests.post( fhttps://api.weixin.qq.com/cgi-bin/message/template/send?access_token{token}, json{ touser: openid, template_id: template_id, data: data, }, timeout8, ).json() if resp.get(errcode) ! 0: raise RuntimeError(fsend template message failed: {resp}) return resp返回值里 errcode 为 0 才是发送成功其他错误码需要对照微信官方文档。注意 data 里的值必须是字符串别顺手传成数字微信接口对类型很挑。主流程 push.py 就是把两个模块串起来def main(): cfg load_config(config.yaml) forecast fetch_weather(cfg[city_code], cfg[weather_key]) message_data build_message(forecast, cfg[city_name]) token get_access_token(cfg[appid], cfg[app_secret]) send_template_message(token, cfg[template_id], cfg[openid], message_data)这里没有加任何重试逻辑。定时任务本身允许失败后第二天再补如果加了重试反而要小心同一分钟发重复消息。我见过有人把整个 main 包在 while 循环里重试结果女朋友在 1 分钟内收到两条一模一样的天气比没收到还尴尬。4. 部署运行与定时调度从本地手动触发到服务器每天自动执行代码写完接下来是把任务变成“每天早上自动跑一次”。建议分三步走先把公众号参数准备好再本地手动触发一次最后上服务器挂调度。不跳过前两步直接上服务器是血泪经验换来的习惯。4.1 公众号接入参数从哪里拿appid、secret、模板 ID、openid先准备配置文件 config.yamlweather_key: 你的天气接口key city_code: 某市的城市编码 city_name: 某市 appid: 公众号appid app_secret: 公众号secret template_id: 模板消息模板ID openid: 接收消息的用户openidappid 和 secret 在公众号后台的基本配置里找第一次获取 secret 时平台会要求管理员扫码确认。template_id 在后台“模板消息”功能里选用你想要的模板选好后会给出模板 ID。openid 获取有两种方式公众号已经上线的话可以在用户管理里找到关注用户详情里有对方的 openid如果是开发阶段可以用开发者工具模拟或通过网页授权换取。个人开发者场景下最快的是从用户管理页面把 openid 复制进配置。这里有个关键提醒选用的模板内容决定了后续 data 字段的 key。比如模板里定义了“城市”“日期”“内容”三个变量那 data 里只能用 city、date、content 这三个 key。所以第 3 章里我把 message.py 返回的 dict 设计成和模板一一对应就是为这个。4.2 本地手动触发一次先确认链路通畅再谈自动化不要第一时间去配定时器先把主流程跑通pip install -r requirements.txt python push.py如果配置没问题脚本没有任何输出说明发送成功。如果报错就按错误出处往上追报错在 weather.py说明天气接口参数有问题报错在 get_access_token说明 appid、secret 不对或 IP 白名单没加报错在 send_template_message把返回 JSON 原样贴出来和错误码对照最常见的 40003 是 openid 错误41030 是模板 ID 或模板字段错误。第一次跑通之后我习惯手动连跑三遍。第一遍验证接口没问题第二遍验证 access_token 缓存是否正常复用第三遍确认连续发送不会被微信限流。三遍都稳定返回 errcode 0才说明链路基本可靠。4.3 上服务器crontab 优先APScheduler 作为备选方案部署有两条路线。一条是把 scheduler.py 带上去用 APScheduler 在代码里定时执行。好处是时间规则写在 Python 里改起来方便坏处是进程必须一直活着进程挂了调度就没了还得再套一个守护进程。另一条是干脆不用进程内调度只留 push.py在系统 crontab 里加一行让系统每天早上固定时间调用一次。我个人推荐后者cron 是操作系统级保障比自建守护进程靠谱而且这个任务本身是“一次执行完就退出”的类型天然幂等不需要常驻。run_cron.sh 长这样#!/bin/bash cd /opt/weather-push /usr/bin/python3 push.py logs/cron.log 21crontab 里这样挂0 8 * * * TZAsia/Shanghai /opt/weather-push/run_cron.sh这里有两个细节容易踩。第一是脚本里必须 cd 到项目目录不然代码里的相对路径 config.yaml 找不到第二是 crontab 默认可能用系统时区如果不显式指定 TZAsia/Shanghai你以为的早上 8 点实际可能是下午 4 点早安推送变成了晚安问候。日志重定向到 cron.log 也不是摆设每天早上看一眼这个文件确认今天有没有正常发送这是整套系统唯一的可视化窗口。注意服务器时区如果已经设置成北京时间crontab 里的 TZ 可以省略。但与其依赖服务器环境不如在 cron 表达式里写死时区这样换机器不换行为。5. 避坑指南与常见问题排查消息发不出去通常卡在这五个地方这一章把实际跑项目过程中遇到的高频问题整理成清单。每条按现象、原因、解决方式写遇到报错建议先来对照一遍再去看完整错误信息。5.1 发送报 41030模板 ID 没问题模板字段却不对现象send_template_message 返回 errcode 41030查文档说模板不存在但你在后台明确复制了这个模板 ID。原因多数情况不是模板 ID 写错而是模板 ID 属于另一个公众号或者用了测试号的模板在正式号上发。还有一种可能data 的 key 和模板变量不一致微信也会把这个错误归类到模板不匹配。解决确认当前 appid 对应的公众号后台确实选用了这个模板然后把 data 的 key 和模板变量逐个对照多一个少一个都会被拒绝。我有段时间为了省事把三个字段合并成一个 text 字段结果微信直接报 41030分开字段后立刻正常。5.2 access_token 突然失效重启后恢复正常看着像玄学现象连续几天发送正常某天开始偶尔报 40001过一会儿又自己好重启进程后必然好一段时间。原因access_token 在 7200 秒内重复获取会让旧 token 失效。如果同时开了多个定时任务或者手动调试了一次又让定时任务立刻跑第二次拿的 token 会把第一次的作废定时任务里那个缓存 token 就成了死 token。解决先从代码上保证 token 缓存复用进程级缓存足够。如果服务器上部署了多个副本就得把 token 放到 Redis 或文件里共享。更省事的办法是把调度统一到同一个入口避免“手动执行一遍 定时任务再执行一遍”的并发场景。5.3 天气接口返回成功但 forecasts 是空的白推了一天现象status 是“1”看起来成功但 forecasts 数组为空推送文案变成“None℃”这类字符串。原因这个假成功通常由城市编码错误引起。直接把城市名“某市”传给接口接口不报错但找不到匹配数据返回空数组。解决确认 city_code 用的是行政区划编码而不是城市名。去查一下目标城市对应的 6 位编码填进 config.yaml 再跑一次。编码对了forecasts 自然有数据。5.4 定时任务时间不准说好的 8 点变成了下午现象crontab 里写的 0 8 * * *但日志显示每天下午才执行。原因服务器系统时区是 UTCcrontab 按系统时间解析8 点指的是 UTC 8 点换算成北京时间就是 16 点。解决在 crontab 行里显式加 TZAsia/Shanghai或者用 timedatectl set-timezone Asia/Shanghai 把服务器时区改成北京时间。改完记得用 date 命令确认别光改文件不验证。5.5 同一分钟收到两条一样的推送现象对方连续两天在同一分钟收到两条相同的天气消息。原因部署阶段埋的雷。有人在进程里跑了 APScheduler又同时在 crontab 里加了同一行两边在同一时间各触发一次。也有人给 push.py 加了失败重试逻辑第一次发送超时但实际发出去了重试补发又送了一条。解决调度入口只保留一个选了 crontab 就把 scheduler.py 收起来。重试逻辑必须做成只在明确收到网络错误时才重试并且加一个基于文件锁或 Redis 的去重判断十几行代码就能避免这种社死现场。6. 二次开发方向从固定城市推送变成会记住纪念日的天气助手项目跑稳之后想让它更懂人可以从三个方向改造。这些都不需要动主体架构只是往一个模块里加逻辑这也是当初把代码拆成 weather、message、wechat 三个文件的原因。第一个方向把配置外置得更彻底。现在城市、openid、模板 ID 都在 config.yaml但如果想用同一套代码推给两个人、两个城市就把 config.yaml 改成用户列表让 push.py 循环处理for user in cfg[users]: forecast fetch_weather(user[city_code], cfg[weather_key]) message_data build_message(forecast, user[city_name]) send_template_message(token, user[template_id], user[openid], message_data)这样一份代码多城市多人推送只需要在 YAML 里追加用户块。我会把每个用户的 city_name、openid、模板 ID 独立成块方便女朋友、家人、自己各自维护。第二个方向加纪念日和倒计时。在 message.py 里维护一个日期表每次推送前计算距离目标日期还有多少天from datetime import datetime anniversary datetime(2025, 6, 1).date() days_left (anniversary - datetime.now().date()).days if days_left 0: celebration 纪念日已经过去了来日方长 elif days_left 0: celebration 就是今天别忘了准备惊喜 else: celebration f距离纪念日还有 {days_left} 天把 celebration 拼进 text模板消息里就能显示出倒计时。这个改动只加一个新函数不碰天气模块和微信模块。第三个方向接入大模型接口生成早安语。天气数据依然是结构化的但文案不再用固定模板而是把温度、天气、风力作为参数交给大模型生成一句口语化的话。这里重点要做好 fallback模型调用可能超时、可能生成奇怪内容一旦失败就回落到本地模板文案保证消息永不缺席。这也是我把项目送进生产环境后才明白的原则——花活可以上但底线逻辑必须是最稳的那条。这三个方向做完项目就不再是单纯天气推送而是一个把数据、文案、消息通道三件事都摸清楚的小系统。从那以后我每写一个依赖外部接口的定时任务都会强制保留 push.py 这种“手动触发一次”的入口先把链路跑通再挂调度省掉了不知道多少次半夜查日志的麻烦。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑