
简介这份资源面向具备少量编程基础、希望为伴侣定制微信每日推送的开发者围绕公众号定时发送天气与纪念日提醒这一场景提供可落地的实现方案。资源包共5个文件以py主程序、yml自动化工作流配置、txt配置与说明、md文档为主压缩包仅5KB轻量易读适合直接部署到GitHub Actions实现每日自动线上运行。内容涵盖最新完整版、简化版、星座版、星座加天气版以及PHP版本等多套成品与消息模板并附有配置说明读者可据此替换天行、和风等API密钥快速搭建属于自己的推送服务。目前已有7695人学习下载教程结合作者亲身经历讲解从消息模板到自动运行链路均有对应文件支撑便于按需选用与二次修改。1. 从零搭一套天气推送为什么我劝你先别急着写代码去年冬天某位朋友想给对象每天推一条天气提醒觉得“调个接口发条消息”半小时就能搞定。结果他折腾了整整一个周末公众号类型选错、模板消息用不了、服务器定时任务没跑起来、接口返回40003不知道错在哪。最后消息没发出去对象还以为他忘了这回事。这套东西听起来简单实际是一条完整的链路公众号后台配置、服务器环境、定时任务、天气接口调用、消息模板拼装。任何一环出问题表现都是“没收到消息”排查起来像在黑匣子里摸。它适合两类人一是想练手完整后端小项目的开发者二是想给身边人做点实用小工具的人。这篇笔记按我实际部署的顺序把环境、版本、参数、坑点一次讲清照着走能跑通。2. 环境准备与版本选型Python、服务器和公众号类型2.1 运行环境清单与版本对照这套推送系统对硬件要求极低一台 1 核 1G 的云服务器足够。真正容易翻车的是软件版本和公众号类型。我一般会先把下面这张表确认一遍再动手省得装到一半发现方向错了。组件推荐版本说明操作系统Ubuntu 20.04 / 22.04 LTS包管理稳定社区资料多Python3.8 ~ 3.113.12 部分依赖轮子还不全新手避开pip随 Python 自带建议升级到最新requests2.28 以上调天气接口和微信接口都用它APScheduler3.10.x做定时任务比系统 cron 更好调试公众号类型服务号已认证订阅号没有模板消息权限这里最关键的一行是公众号类型。很多人注册时随手选了订阅号写到发消息那一步才发现根本没有模板消息接口权限。服务号需要企业或组织主体认证个人主体拿不到。如果你只是想自己练手可以用测试号替代测试号有完整的模板消息调试权限具体在公众号后台的开发者工具里能找到入口。提示测试号的接口权限和正式服务号基本一致适合本地开发和验证逻辑正式上线再换正式号。2.2 服务器初始化与 Python 环境安装拿到服务器后先做基础更新和 Python 环境准备。我习惯用系统自带的 Python 3再用 venv 隔离依赖避免污染全局环境。# 更新系统包索引并升级已安装的包 sudo apt update sudo apt upgrade -y # 安装 Python3、pip 和虚拟环境模块 sudo apt install -y python3 python3-pip python3-venv # 确认版本Ubuntu 22.04 默认是 Python 3.10 python3 --version # 在项目目录下创建虚拟环境 mkdir -p ~/weather-push cd ~/weather-push python3 -m venv venv # 激活虚拟环境 source venv/bin/activate # 升级 pip 并安装依赖 pip install --upgrade pip pip install requests apschedulerpython3 -m venv venv会在当前目录生成一个独立的 Python 环境后面所有依赖都装在里面删掉 venv 目录就等于清理干净。source venv/bin/activate激活后命令行提示符前面会出现(venv)说明后续 pip 安装都作用于这个隔离环境。这一步看着基础但我见过不少人直接sudo pip install把系统 Python 搞乱后面 apt 都报错。2.3 公众号后台需要提前拿到的三个参数在写代码之前先把公众号后台的几个关键信息记下来后面配置要用AppID公众号的唯一标识在「设置与开发 → 基本配置」里。AppSecret调用接口的密钥同一页面生成只显示一次务必保存。模板 ID在「模板消息 → 我的模板」里新建一个天气模板后获得。模板内容可以自己定义我一般用这样的结构{{date.DATA}} 天气{{weather.DATA}} 温度{{temp.DATA}} 建议{{tip.DATA}}。每个{{xxx.DATA}}对应一个字段代码里按字段名传值即可。模板建好后审核通常几分钟到几小时趁这个时间可以把服务器环境和代码写完。3. 核心代码落地天气接口调用与消息推送实现3.1 天气接口的选择与请求封装天气数据源我用的是公开的免费接口返回 JSON 格式包含当天天气、温度区间、风力等字段。不同接口字段名不一样下面以常见的结构为例实际替换成你选用的接口地址即可。import requests def get_weather(city_code): 根据城市代码获取天气数据 url https://api.example.com/weather params { city: city_code, # 城市编码如 101010100 代表北京 key: YOUR_API_KEY, # 接口密钥替换成自己的 type: daily # 获取每日预报 } try: resp requests.get(url, paramsparams, timeout10) resp.raise_for_status() # 非 200 状态码直接抛异常 data resp.json() return data except requests.RequestException as e: print(f天气接口请求失败: {e}) return Nonetimeout10是必须加的否则接口卡住时整个定时任务会一直挂着。raise_for_status()让 4xx、5xx 直接进异常分支不会拿着错误响应继续解析。返回None时上层做跳过处理避免把空数据推到微信接口导致报错。城市编码各平台不同常见做法是查接口文档里的城市列表把常用城市存成字典。3.2 微信模板消息的拼装与发送拿到天气数据后按模板字段拼装消息体再调用微信的模板消息接口。这里有两个容易错的地方access_token 需要缓存不能每次发消息都重新获取字段名必须和后台模板完全一致。import time import requests # 简单的内存缓存生产环境建议用文件或 Redis _token_cache {token: None, expire_at: 0} def get_access_token(appid, secret): 获取 access_token带缓存避免频繁请求 now time.time() if _token_cache[token] and now _token_cache[expire_at]: return _token_cache[token] url https://api.weixin.qq.com/cgi-bin/token params { grant_type: client_credential, appid: appid, secret: secret } resp requests.get(url, paramsparams, timeout10).json() token resp.get(access_token) if not token: raise RuntimeError(f获取 token 失败: {resp}) # 微信 token 有效期 7200 秒提前 300 秒刷新 _token_cache[token] token _token_cache[expire_at] now 6900 return token def send_weather_msg(openid, template_id, weather_data, token): 发送模板消息给指定用户 url fhttps://api.weixin.qq.com/cgi-bin/message/template/send?access_token{token} body { touser: openid, template_id: template_id, data: { date: {value: weather_data[date]}, weather: {value: weather_data[weather]}, temp: {value: weather_data[temp]}, tip: {value: weather_data[tip]} } } resp requests.post(url, jsonbody, timeout10).json() if resp.get(errcode) ! 0: print(f消息发送失败: {resp}) return resp_token_cache用字典做进程内缓存expire_at设为 6900 秒而不是 7200 秒是留出刷新余量避免边界时间拿到已失效的 token。touser填的是接收者的 openid不是微信号获取方式需要用户先关注公众号并授权测试阶段可以直接在后台看自己的 openid。data里的键名必须和模板里的{{xxx.DATA}}一一对应大小写敏感写错会返回40037。3.3 定时任务配置与主流程串联定时任务用 APScheduler 的 BlockingScheduler它会在主线程阻塞运行适合常驻脚本。比系统 cron 的好处是日志集中、调试方便出问题不用去翻/var/log/syslog。from apscheduler.schedulers.blocking import BlockingScheduler APPID your_appid SECRET your_secret TEMPLATE_ID your_template_id OPENID target_openid CITY 101010100 def daily_job(): 每天执行一次的推送任务 weather get_weather(CITY) if not weather: print(天气数据为空跳过本次推送) return token get_access_token(APPID, SECRET) send_weather_msg(OPENID, TEMPLATE_ID, weather, token) print(推送完成) if __name__ __main__: scheduler BlockingScheduler() # 每天早 7 点执行 scheduler.add_job(daily_job, cron, hour7, minute0) print(定时任务已启动等待执行...) scheduler.start()scheduler.add_job的cron模式支持hour、minute、day_of_week等参数按需调整推送时间。BlockingScheduler启动后会一直运行服务器上建议配合nohup或 systemd 让它后台常驻。我一般先用hour*, minute*每分钟跑一次验证链路确认能收到消息后再改成每天固定时间。4. 避坑与排查消息发不出去时先查这五处4.1 报错 40003openid 无效或用户未关注现象是接口返回{errcode:40003,errmsg:invalid openid}。原因通常是 openid 填错或者接收者已经取关公众号。openid 是公众号维度的换一个公众号就变了不能混用。解决办法是在公众号后台的用户列表里重新确认 openid或者让接收者重新关注后再取一次。4.2 报错 40037模板字段不匹配现象是返回invalid template_id或字段校验失败。原因是模板 ID 写错或者data里的键名和后台模板的{{xxx.DATA}}不一致。我踩过一次是模板里写的是{{temp.DATA}}代码里传了temperature接口直接拒绝。解决方法是把后台模板内容复制出来逐个字段核对键名注意大小写。4.3 定时任务不执行时区或进程退出现象是脚本手动跑能发消息挂到服务器上到点没反应。原因有两个一是服务器时区是 UTC你按北京时间设的 7 点实际是 UTC 23 点二是用nohup启动后进程因为未捕获异常退出了。解决办法是先用timedatectl确认时区必要时sudo timedatectl set-timezone Asia/Shanghai再给主流程加 try/except 兜底避免单次异常导致整个调度器挂掉。4.4 access_token 频繁刷新被限流现象是偶尔报45009或 token 获取失败。原因是每次发消息都重新请求 token触发了微信的频率限制。解决办法就是 3.2 节里的缓存逻辑把 token 存下来复用提前几分钟刷新即可。如果多进程运行内存缓存不共享常见做法是写到文件或 Redis 里统一读取。4.5 天气接口返回空或超时现象是推送内容为空或者脚本卡住不动。原因是接口超时未处理或者城市编码错误导致返回空数据。解决办法是请求加timeout返回空时跳过本次推送而不是硬发。另外免费接口通常有每日调用次数限制调试阶段别把定时设成每分钟一次容易把额度跑完。5. 验证跑通从手动触发到稳定推送的检查清单代码写完不等于跑通我一般按下面这个顺序验证每一步确认后再进下一步避免问题叠在一起不好定位。验证步骤操作预期结果天气接口单独运行 get_weather打印出包含日期、天气、温度的字典token 获取单独调用 get_access_token返回一长串 token 字符串消息发送手动调用 send_weather_msg微信收到消息接口返回 errcode 0定时触发设成每分钟一次运行每分钟收到一条确认后改回每天长期运行nohup 后台启动隔天检查到点收到消息日志无异常手动触发时我习惯在daily_job里加一行print把天气数据和 token 前几位打出来确认数据流是通的。等每分钟测试稳定后再把时间改成hour7, minute0。后台运行用nohup python main.py push.log 21 日志重定向到文件第二天直接tail -f push.log看有没有报错。还有一个细节微信模板消息对同一用户的推送有频率限制测试阶段别短时间内反复发否则可能被临时限制。我一般验证时用自己的号确认无误后再换成对方的 openid。从那以后我每次部署这类定时推送都强制先跑一遍每分钟触发的链路验证确认消息能稳定到达再改回正式时间。这个习惯帮我省了好几次“以为配好了结果第二天没收到”的尴尬。希望帮到你。本文还有配套的精品资源点击获取