Python 微信公众号天气推送:从环境搭建到避坑实战

发布时间:2026/10/21 7:29:46
Python 微信公众号天气推送:从环境搭建到避坑实战 简介这份资源面向想为伴侣制作微信公众号每日天气推送的编程初学者与爱好者解决定时自动发送天气、纪念日提醒的落地问题。包内共5个文件以py主程序、yml工作流配置、txt配置与消息模板、md说明文档为主压缩包仅5KB轻量易部署配合GitHub Actions即可实现线上每日自动运行。资源提供最新版、简化版、星座版、星座加天气版及PHP版等多套成品与源码需自行申请天行、和风API覆盖从基础推送到个性化定制的不同需求。已有7695人学习下载说明方案经过较多用户验证。读者可从中获得可直接运行的脚本、消息模板、工作流配置与排错思路快速搭建属于自己的天气推送服务适合有一点点编程基础、希望用心为对方制造惊喜的人参考实践。1. 从零搭建天气推送服务为什么你本地跑通、线上却收不到消息很多人第一次做微信公众号天气推送都卡在同一个地方本地python main.py跑得飞起日志里明明白白打印出“发送成功”可手机上的订阅号消息列表安静得像凌晨三点的机房。这不是玄学是微信公众平台的服务器配置、IP 白名单、模板消息权限、access_token 缓存这几道关卡里至少有一道没对齐。这套环境本身不复杂一台 1 核 2G 的云服务器、Python 3.8 以上、一个认证过的服务号或测试号再加一个定时任务就能跑起来。但装不好轻则每天推送延迟几小时重则直接触发频率限制把接口封掉。这篇笔记面向的是想给身边人做个小工具、但被微信生态各种“隐藏条件”折腾过的开发者我会把版本选择、依赖安装、配置项含义、以及翻车后怎么救按我实际踩过的顺序写清楚。2. 环境准备与版本选型Python、Redis 和微信测试号的取舍2.1 为什么我坚持用 Python 3.8 而不是 3.12微信官方并没有 Python SDK社区里用得最多的是wechatpy和直接裸写requests调接口。我试过在 Python 3.12 上装wechatpycryptography这个依赖会去编译rust绑定1 核服务器直接卡死二十分钟然后 OOM。稳妥的做法是锁 Python 3.8 到 3.10 这个区间wechatpy的 wheel 包齐全pip install秒级完成。如果你服务器上已经有 3.11也不用降级直接裸写requests调cgi-bin/token和cgi-bin/message/template/send两个接口就行少一层封装反而少一堆版本兼容的坑。# 我一般用 pyenv 管理版本避免动系统自带的 python3 curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash # 装完后把下面三行写进 ~/.bashrc export PATH$HOME/.pyenv/bin:$PATH eval $(pyenv init -) eval $(pyenv virtualenv-init -) source ~/.bashrc pyenv install 3.9.18 pyenv global 3.9.18 python -V # 确认输出 Python 3.9.18这段命令的逻辑是先装 pyenv 本体再把它的初始化脚本挂到 shell 启动流程里最后装一个 3.9.18 并设为全局默认。参数上3.9.18是 3.9 系列最后一个带完整安全更新的版本比 3.8 新又避开了 3.10 之后某些 C 扩展的编译问题。如果你不想折腾 pyenv用系统自带的python3.9加venv也完全够用关键是别用 3.12。2.2 依赖清单与 Redis 的角色核心依赖就三个requests发 HTTP 请求、redis做 access_token 和每日推送状态的缓存、apscheduler做定时任务。有人问能不能不用 Redis用文件存 token 行不行行但微信的 access_token 有效期 7200 秒且获取次数有限制多进程或重启后文件读写容易撞车。Redis 的SETEX原子操作能保证同一时刻只有一个进程去刷新 token这是最省心的做法。# 创建虚拟环境并安装依赖 python -m venv venv source venv/bin/activate pip install requests2.31.0 redis5.0.1 apscheduler3.10.4 # 把依赖冻结下来方便迁移 pip freeze requirements.txtrequests锁 2.31.0 是因为再新的版本对urllib3有要求某些老系统自带的 openssl 版本会报SSLError。redis5.x 的 API 和 4.x 基本兼容但连接池的默认参数有微调如果你从旧项目迁移过来注意decode_responsesTrue这个参数要显式加上否则取出来的 token 是 bytes拼到 URL 里会变成bxxx这种鬼东西。Redis 的安装我一般直接用系统包管理器不编译源码# Ubuntu/Debian sudo apt update sudo apt install redis-server -y sudo systemctl enable redis-server sudo systemctl start redis-server redis-cli ping # 返回 PONG 即正常装完后改一下/etc/redis/redis.conf里的bind 127.0.0.1和requirepass只允许本机访问并加个密码。天气推送服务本身不对外暴露 Redis这一步是防止服务器被扫端口。2.3 微信测试号 vs 服务号先跑通再认证正式服务号需要企业资质和 300 元认证费个人开发者折腾不起。微信公众平台有个“测试号”入口用个人微信号就能申请拥有除支付外几乎全部接口权限包括模板消息。我强烈建议先用测试号把整条链路跑通确认天气 API 能取到数据、模板消息能发到手机再考虑要不要升级服务号。申请测试号后你会拿到appID和appsecret这两个值填到后面的配置文件里。测试号的用户列表里会显示你的微信号那个openid就是推送目标。注意测试号的 access_token 获取频率限制比正式号更严所以 Redis 缓存这一步在测试阶段就要做对否则调几次就报45009错误。3. 配置文件与核心代码把 appID、模板 ID 和天气 API 串起来3.1 配置文件的结构与参数含义我不喜欢把敏感信息硬编码在代码里用一个config.ini加configparser读取迁移时只改这一个文件。结构如下[wechat] appid wx1234567890abcdef appsecret 0987654321fedcba0987654321fedcba template_id AbCdEfGhIjKlMnOpQrStUvWxYz1234567890 openid oXyZ1234567890abcdefGHIJKL [weather] api_key your_weather_api_key city 310000 [redis] host 127.0.0.1 port 6379 password your_redis_password db 0appid和appsecret在测试号页面的“接口配置信息”上方。template_id需要你在测试号的“模板消息接口”里先新增一个模板标题随便写“每日天气”内容里填变量占位符比如{{date.DATA}} {{city.DATA}} {{weather.DATA}} {{temp.DATA}} {{tips.DATA}}保存后微信会生成一个模板 ID那串长字符就是。openid在“用户列表”里是一串以o开头的 28 位字符串。天气 API 我用的是某天气数据服务商的免费接口city参数填城市编码310000代表某直辖市具体编码去服务商文档里查。注意appsecret一旦泄露别人可以冒充你的公众号发消息。配置文件权限设成600且不要提交到公开仓库。3.2 获取 access_token 并缓存到 Redisaccess_token 是调用所有微信接口的“通行证”必须放在 URL 参数里。它的坑在于每天获取次数有限且新 token 生成后旧 token 有 5 分钟重叠期。我一般用 Redis 的SETEX存 7000 秒比 7200 少 200 秒留出刷新缓冲。import requests import redis import configparser config configparser.ConfigParser() config.read(config.ini) r redis.Redis( hostconfig[redis][host], portint(config[redis][port]), passwordconfig[redis][password], dbint(config[redis][db]), decode_responsesTrue # 关键让返回值是 str 而不是 bytes ) def get_access_token(): token r.get(wx_access_token) if token: return token url https://api.weixin.qq.com/cgi-bin/token params { grant_type: client_credential, appid: config[wechat][appid], secret: config[wechat][appsecret] } resp requests.get(url, paramsparams, timeout10) data resp.json() if access_token not in data: raise Exception(f获取 token 失败: {data}) token data[access_token] # 7000 秒后自动过期留 200 秒缓冲 r.setex(wx_access_token, 7000, token) return token逻辑说明先从 Redis 读读不到再调微信接口。grant_type固定为client_credentialappid和secret从配置文件取。timeout10是防止微信接口偶尔抽风导致进程挂死。如果返回的 JSON 里没有access_token直接把错误信息抛出来通常是40164IP 不在白名单或40013appid 无效。setex的 7000 秒是经验值你设 7100 也行但别贴着 7200 设网络延迟会导致边界失效。3.3 调用天气 API 并组装模板消息天气 API 返回的 JSON 结构各家不同我以常见的{“data”: {“forecast”: [...]}}为例。取当天数据拼成模板消息要求的data字段。import json from datetime import datetime def get_weather(): url https://api.weather.com/v1/forecast params { key: config[weather][api_key], city: config[weather][city], date: datetime.now().strftime(%Y-%m-%d) } resp requests.get(url, paramsparams, timeout10) data resp.json() today data[data][forecast][0] return { date: datetime.now().strftime(%Y年%m月%d日), city: today[city], weather: today[condition], temp: f{today[tempMin]}~{today[tempMax]}℃, tips: today[tips] } def send_weather(): token get_access_token() weather get_weather() url fhttps://api.weixin.qq.com/cgi-bin/message/template/send?access_token{token} body { touser: config[wechat][openid], template_id: config[wechat][template_id], data: { date: {value: weather[date]}, city: {value: weather[city]}, weather: {value: weather[weather]}, temp: {value: weather[temp]}, tips: {value: weather[tips]} } } resp requests.post(url, datajson.dumps(body), timeout10) result resp.json() if result.get(errcode) ! 0: raise Exception(f发送失败: {result}) return resulttouser就是你的 openidtemplate_id是模板消息 ID。data里的每个 key 必须和你在模板里定义的变量名完全一致大小写敏感。json.dumps默认会把中文转成\uXXXX微信接口能识别不用加ensure_asciiFalse。发送成功后errcode为 0否则根据错误码排查40037是 template_id 无效43004是 openid 没关注公众号45009是接口调用超限。3.4 用 APScheduler 做每日定时定时任务我不用系统 crontab因为 crontab 的环境变量和虚拟环境路径经常对不上调试起来像开黑匣子。APScheduler 跑在同一个进程里日志和异常都能直接看到。from apscheduler.schedulers.blocking import BlockingScheduler if __name__ __main__: scheduler BlockingScheduler(timezoneAsia/Shanghai) # 每天早上 7 点 30 分推送 scheduler.add_job(send_weather, cron, hour7, minute30) try: scheduler.start() except (KeyboardInterrupt, SystemExit): passtimezone必须显式指定Asia/Shanghai否则服务器如果是 UTC 时间推送会变成下午 3 点半。cron表达式的hour和minute是 24 小时制。如果你想让周末不推送加day_of_weekmon-fri参数。这个进程用nohup或systemd守护起来挂了自动重启。4. 避坑与排查IP 白名单、频率限制和模板消息被拒4.1 现象本地能发服务器报 40164原因微信公众平台要求调用接口的服务器 IP 在“IP 白名单”里测试号在“接口配置信息”下方有个“IP 白名单”链接正式号在“基本配置”里。你本地开发机的 IP 加进去了但云服务器的公网 IP 没加。解决在服务器上curl ifconfig.me拿到公网 IP填进白名单。注意如果服务器走 NAT 网关出口 IP 可能和ifconfig.me显示的不一样以微信后台报错信息里提示的 IP 为准。改完后等 1 分钟再试微信侧有缓存。4.2 现象推送成功但手机收不到原因模板消息的data里某个变量值超过长度限制比如tips字段超过 200 字符或者模板内容里用了未定义的变量名。微信接口返回errcode: 0但实际丢弃了消息这种情况最坑。解决把send_weather的返回结果完整打印出来确认errcode为 0 且msgid存在。然后去测试号的“模板消息接口”里点“模板列表”看模板内容是否和你代码里的 key 一一对应。我一般会在模板里只放 5 个以内的变量每个值截断到 50 字符以内。4.3 现象运行几天后突然报 45009原因access_token 获取频率超限。微信规定每个 appid 每天获取 token 的次数有限如果你代码里每次发送都重新获取或者 Redis 挂了导致缓存失效就会触发。解决确认get_access_token里先读 Redis 的逻辑生效。可以在 Redis 里TTL wx_access_token看剩余时间。如果 Redis 密码错了r.get会抛异常而不是返回 None记得在代码里捕获redis.exceptions.ConnectionError并打日志。另外测试号的 token 获取限制比正式号严正式号每天 2000 次测试号可能只有 200 次所以缓存策略在测试阶段就要做对。4.4 现象定时任务在服务器重启后不执行原因用nohup python main.py 启动的进程服务器重启后不会自动拉起。或者systemd服务文件里WorkingDirectory没设对导致读不到config.ini。解决写一个systemdunit 文件放到/etc/systemd/system/weather.service[Unit] DescriptionWeather Push Service Afternetwork.target redis.service [Service] Typesimple Useryour_user WorkingDirectory/home/your_user/weather-push ExecStart/home/your_user/weather-push/venv/bin/python main.py Restartalways RestartSec10 [Install] WantedBymulti-user.targetWorkingDirectory必须是项目根目录否则config.ini的相对路径会找不到。Restartalways保证进程崩溃后 10 秒自动重启。改完后systemctl daemon-reload systemctl enable weather systemctl start weather。4.5 现象天气 API 返回的城市不对原因城市编码填错了或者 API 服务商对某些编码做了重定向。比如你填了310000以为是某直辖市实际返回的是邻近城市的数据。解决先用curl直接调天气 API把完整 JSON 打出来看city字段。不要依赖代码里的解析逻辑原始响应最诚实。如果编码确实不对去服务商官网的“城市列表”页面查正确的编码通常是一个 6 位数字或 9 位字母数字组合。5. 装完怎么验证跑通从手动触发到观察三天日志5.1 手动触发一次确认全链路无异常服务启动后别干等第二天早上 7 点半。我一般会临时把scheduler.add_job的hour和minute改成当前时间加两分钟或者直接写一个if __name__ __main__里先调一次send_weather()再启动 scheduler。观察控制台输出python main.py # 期望输出类似 # {errcode: 0, errmsg: ok, msgid: 1234567890}如果errcode非 0根据上一章的错误码表排查。如果errcode为 0 但手机没收到去微信测试号的“模板消息接口”页面看“发送记录”那里会显示每条消息的投递状态。这一步能区分是微信侧丢弃还是你代码根本没发出去。5.2 检查 Redis 里的 token 和推送标记手动触发成功后连上 Redis 确认缓存写入redis-cli -a your_redis_password TTL wx_access_token (integer) 6983 GET wx_access_token 1234567890abcdef...TTL返回一个小于 7000 的正整数说明setex生效。如果返回-2说明 key 不存在检查get_access_token里的r.setex是否执行到。如果返回-1说明你用了r.set而不是r.setextoken 永不过期下次微信侧失效后你的服务会一直用旧 token 报40001错误。5.3 观察三天日志确认定时和异常恢复把服务挂到systemd后用journalctl -u weather -f实时看日志。重点观察三个时间点推送时间是否准确误差在 1 分钟内、token 刷新时是否有报错、天气 API 超时后是否自动重试。我一般会跑三天第一天看功能第二天看稳定性第三天看有没有内存泄漏ps aux | grep main.py看 RSS 是否持续增长。如果第三天发现 RSS 从 30M 涨到 200M大概率是requests的 session 没复用或者 Redis 连接池泄漏。解决办法是在get_access_token和send_weather里用requests.Session()全局复用一个 session 对象而不是每次requests.get。这个坑我在两个项目里都踩过后来养成习惯只要涉及循环调用外部接口一律用 session。5.4 一个验证模板消息是否被折叠的小技巧微信模板消息在聊天列表里会折叠成一条“服务通知”点进去才能看到详情。如果你不确定对方是否收到可以让对方在微信里搜索“服务通知”看有没有当天的记录。另外测试号发的消息会带一个“测试”水印正式号没有。如果你用测试号跑通了换正式号时只需要改appid、appsecret和template_id代码一行不用动。从那以后我每次部署这类定时推送服务都会强制走一遍“手动触发 → Redis 检查 → 三天日志观察”的流程不再凭感觉认为“代码没问题就是没问题”。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询