首页 / 资讯中心 / 文章详情

用Python+微信测试号+天气API,实现每天自动推送早安天气

用Python+微信测试号+天气API,实现每天自动推送早安天气 ★ FEATURED ARTICLE
简介教你搭建微信公众号每日自动推送天气与纪念日通知的完整制作教程面向想用技术为恋爱关系增添仪式感、仅有少量编程基础即可上手的读者。压缩包共5个文件、仅5KB核心包含Python主程序、参数配置文件、GitHub Actions工作流定义与使用说明文档主程序负责拼接天气、星座和纪念日信息并调用公众号接口配置文件支持自定义城市、推送时间与模板工作流则让整个流程在云端每日自动运行无需额外服务器。目前已有7690人学习/下载。教程针对不同需求提供了最新版、简化版、单独星座版、星座天气版以及PHP版等五套方案每套均含成品或源码与消息模板并注明需要借助天行、和风API获取数据。读者既可以下载成品直接测试也能依据源码理解调用逻辑并二次开发是低成本、高心意的一项自动化实践。1. 每天早上七点她微信里自动出现的天气消息是怎么来的微信公众号的消息推送能力用到最温柔的地方大概就是每天早晨把一条包含天气、气温和出行提醒的消息自动发到女朋友的微信上。这种“每天一条、自动触发、只推给一个人”的小工具靠的不是什么大平台服务而是一个微信测试号、一个天气接口外加一台能跑定时任务的机器。它的价值在于稳定和仪式感不需要她记得去查也不需要你每天手动复制粘贴到点自动出现。适合有一定 Python 基础、手里有一台常开电脑或云服务器的人周末花半天就能把第一版跑通。这篇文章会把背后的链路和每个容易翻车的细节拆开讲让你少走冤枉路。2. 推送链路拆解公众号测试号、天气接口与运行环境怎么搭在动手写代码之前先把链路讲明白。整个系统的数据流是定时任务在每天早上七点唤醒脚本脚本先向天气服务商请求当天天气拿到 JSON 数据后做一层字段整理接着向微信接口申请一个 access_token最后把天气数据组装成模板消息的格式发送给指定用户。用户看到的是一条五六行的小卡片里面城市、日期、天气、最高温、最低温、风力都有微信官方把这种消息叫模板消息。如果你去搜索引擎翻这个需求会看到大量写着“微信公众号”的教程。但实际注册公众号时会发现个人能注册的是订阅号而订阅号没有模板消息接口权限。这个矛盾怎么绕开是第一节的重点。2.1 为什么是测试号而不是订阅号模板消息权限的差别第一次做这个项目时我第一反应也是注册一个订阅号。注册完登录后台才发现个人订阅号的接口能力里没有“发送模板消息”这一项。它也有自定义菜单、被动回复但想主动给某个用户发一条带天气内容的消息接口层面走不通。也有人会建议用服务号服务号确实有模板消息但注册服务号需要企业或组织主体还要做认证光认证这一道就把绝大多数个人用户挡在门外。随后我在微信公众平台的开发者工具里找到了“测试号”。这是官方提供给开发者的调试账号不需要企业主体不需要认证个人微信扫码即可开通而且完整模拟了服务号的接口能力能拿到 appid 和 appsecret能添加模板消息能看到粉丝列表里的 openid。唯一的限制是它没有正式服务号那种对外发布的功能但对这种一对一私人推送来说体验上几乎没有差别。开通测试号后要做三件事第一在测试号管理首页复制 appid 和 appsecret第二进入模板消息页面从模板库选一个适合天气场景的模板记下 template_id第三让女朋友用微信扫码关注这个测试号然后在用户列表里找到她对应的 openid。这三项配置值就是后面脚本里最核心的三个常量。提示测试号毕竟不是正式服务号不建议把它替换成对外发布业务的推送渠道但做私人提醒场景非常够用这也是大多数人给女朋友推送天气时选择它的原因。2.2 天气接口免费额度、城市编码与返回字段怎么读天气数据的来源我建议选一个提供标准 API 的天气服务商而不是去抓网页或桌面软件的数据。原因很简单抓网页的解析规则会被对方改版打断一旦页面结构调整脚本第二天就哑火而且这种抓取行为本身也不受欢迎。正规天气服务商都提供开发者接口注册后申请一个免费 API key免费配额每天几百到几千次而你每天只需要运行一次配额完全用不完。接口请求一般长这样请求地址固定查询参数里带 key、城市编码、单位。这里的城市编码不是邮政编码而是服务商自己维护的一套城市 ID比如有的服务商把北京写成 101010100把杭州写成 101210101。如果只推送一个城市直接在配置区把对应 ID 填死就行以后想支持更多城市可以把 ID 存成一个字典按需读取。返回的数据结构各家不完全相同但几乎都是这个形状{ status: ok, result: { date: 2024-01-15, city_name: 杭州, text_day: 多云, temp_high: 12, temp_low: 5, wind_dir: 东北风, wind_scale: 3级 } }这份 JSON 里text_day 是白天天气描述temp_high 和 temp_low 是当天最高温和最低温wind_dir 和 wind_scale 组合起来就是风力描述。字段命名在不同服务商之间差异很大有的叫 weather有的叫 condition所以拿到接口后第一件事是写一个字段映射函数统一成自己内部使用的字典后续代码全部只和你自己的字段名打交道不再直接依赖服务商的命名方式。2.3 Python 运行环境与依赖只用 requests 这一件事脚本运行环境要求非常低Python 3.6 以上即可第三方依赖只有一个 requests 库。本地调试时在终端执行 pip install requests然后写一个 run_weather.py 文件就够了。不需要 flask不需要 django也不需要数据库整个系统就是一个一百行以内的普通脚本。我会把配置信息全部集中在文件顶部的一个常量区包括天气 API key、城市编码、微信 appid、appsecret、template_id、openid。以后要改城市只需要改一行不需要在代码里到处找。把配置独立出来还有一个好处部署到服务器后只需要同步脚本和配置系统环境不用动。如果你还没有服务器这一步可以先跳过先把脚本在本地跑通后面部署时再迁移。3. 天气数据到微信模板消息核心代码怎么写才不容易翻车这一章是整个项目的核心。最终得到的 run_weather.py 会包含三个函数fetch_weather 负责拉天气get_access_token 负责拿微信凭证send_template_message 负责发消息。每一步都不复杂但每一步都有边界条件写错了既不报错也发不出去最难排查的就是这种静默失败。3.1 先写天气拉取函数解析 JSON 时做好字段映射先定义配置区再写 fetch_weather。下面这段代码直接给出来其中 API 地址、key 和城市编码需要替换成你实际申请到的值import requests # 配置区部署前必须逐项替换 APP_ID wx1234567890abcdef # 测试号后台复制 APP_SECRET your_app_secret # 测试号后台复制 TEMPLATE_ID your_template_id # 测试号模板列表复制 FRIEND_OPENID oYourFriendOpenid # 测试号用户列表复制 WEATHER_API_KEY your_weather_api_key # 天气服务商申请 WEATHER_CITY_ID 101010100 # 城市编码查服务商文档 WEATHER_API_URL https://api.your-weather-provider.com/v3/daily def fetch_weather() - dict: params { key: WEATHER_API_KEY, city: WEATHER_CITY_ID, unit: metric, } resp requests.get(WEATHER_API_URL, paramsparams, timeout10) resp.raise_for_status() data resp.json() if data.get(status) ! ok: raise RuntimeError(f天气接口返回异常: {data}) today data[result][daily][0] # 部分服务商是 result.today按文档调整 return { city: today[city_name], date: today[date], text: today[text_day], high: today[temp_high], low: today[temp_low], wind: today[wind_dir] today[wind_scale], }代码逻辑分三步先拼查询参数发起 GET 请求然后判断返回状态status 不是 ok 直接抛异常避免后面拿到残缺数据继续往下走最后从 result 里取第一天的数据组装成统一的字典返回。注意 timeout 参数一定要写这个项目跑在定时任务里如果接口卡住不返回整个脚本会一直挂着到时候排查非常费劲。部分服务商把当天数据放在 daily 数组里也有的直接放在 today 字段下。这行解析逻辑是需要对照服务商文档调整的唯一地方。代码里我写了注释就是提醒你收到 JSON 后先打印一次看清结构再动手。3.2 获取 access_token两小时一次别每次都踩同样的坑微信接口的大多数调用都需要一个 access_token这个 token 的有效期是 7200 秒也就是两小时。获取方式很简单用 appid 和 appsecret 换def get_access_token() - str: url https://api.weixin.qq.com/cgi-bin/token params { grant_type: client_credential, appid: APP_ID, secret: APP_SECRET, } resp requests.get(url, paramsparams, timeout10) data resp.json() if access_token not in data: raise RuntimeError(f获取 access_token 失败: {data}) return data[access_token]微信返回的 JSON 正常会包含 access_token 和 expires_in 两个字段。因为 token 有效期两小时而脚本每天只运行一次完全不需要做缓存每次运行现取一次即可。如果你在调试时一分钟内连续跑几十次微信会短暂限流提示请求过于频繁这时停半小时就好。有一个细节值得提获取 token 时微信并没有强制校验 IP 白名单但发送模板消息接口同样需要这个 token所以这个函数的返回值会被下一个函数直接使用。如果拿到 token 后脚本出现其他异常没有发送下一次运行再取一次就行不用刻意复用。3.3 组装模板消息并发送data 字段要和模板一一对应模板消息的发送接口接收两个关键信息模板 ID 和用户 openid。模板里定义好了一组字段占位符比如 {{city.DATA}}、{{date.DATA}}你在 data 里就要传相应的 key 和值。写代码前先去测试号后台打开模板详情把模板里的字段名抄下来再回来写这段顺序不能反。def send_template_message(token: str, weather: dict) - dict: url https://api.weixin.qq.com/cgi-bin/message/template/send body { touser: FRIEND_OPENID, template_id: TEMPLATE_ID, data: { city: {value: weather[city]}, date: {value: weather[date]}, weather: {value: weather[text]}, high: {value: weather[high]}, low: {value: weather[low]}, wind: {value: weather[wind]}, }, } resp requests.post(url ?access_token token, jsonbody, timeout10) result resp.json() if result.get(errcode) ! 0: raise RuntimeError(f模板消息发送失败: {result}) return resultbody 里的 data 是一个字典每个字段都包一层 {value: ...}这是微信模板消息的标准结构漏掉 value 这一层接口会直接拒绝。token 需要拼在 URL 查询参数里而不是放在请求头或 body这一点很容易写错。发送接口返回的 JSON 如果 errcode 是 0代表成功否则要把整个 result 打印出来错误码能直接告诉你问题在哪。3.4 把三段函数串成 main()一次运行只做一件事三个函数写完后用 main 把它们串起来def main(): print(开始拉取天气) weather fetch_weather() print(天气数据:, weather) token get_access_token() print(获取 token 成功) result send_template_message(token, weather) print(推送成功:, result[errcode]) if __name__ __main__: main()在本地先运行一次看到打印的天气数据和推送成功说明整条链路已经打通。这个 main 里故意加了 print是给日志用的挂上定时任务后每天运行是否成功通过日志一眼就能看出来。后续加纪念日文案、随机提醒也都是往 weather 字典里加一个 key再往 data 里加一个字段整体结构不会变。4. 每天自动执行cron 与云函数两条路线怎么选脚本写好后剩下的问题是谁在早上七点把它跑起来两条主流路线有服务器用 cron没服务器用云函数。两种方案我都部署过下面分别讲清楚你根据自己的条件选一条就行。4.1 云服务器 cron最直接的定时方案如果你手里有一台常开的 Linux 服务器cron 是最简单可靠的方式。把 run_weather.py 放到服务器上然后执行crontab -e在打开的编辑器里加一行0 7 * * * cd /home/example_user/weather_project /usr/bin/python3 run_weather.py logs/weather.log 21把 example_user 和 weather_project 替换成你服务器里的真实用户名和项目路径。这行规则的含义是每天早上 7 点先切换到项目目录再用 python3 执行脚本把标准输出和错误信息都追加到 logs/weather.log 里。和21两个符号缺一不可没有它们时脚本输出会丢到系统邮件里你根本不会去看。这里最大的坑是时区。cron 使用服务器系统时间不少海外云主机的默认时区是 UTC北京时间早上 7 点对应的是 UTC 前一天 23 点。你需要先执行date命令确认当前时间如果显示的是 UTC就把 cron 表达式改成0 23 * * *或者更推荐的做法在 crontab 文件顶部加一行环境变量声明CRON_TZAsia/Shanghai这样下面的任务仍然按北京时间 7 点执行语义清晰即使以后换机器也不会再次踩坑。注意crontab -e修改的是当前用户的任务列表不要在命令前加 sudo否则任务会写到 root 的 crontab 里普通用户身份运行时可能找不到文件路径。4.2 云函数 定时触发器不养服务器也能跑如果不想为一个小脚本专门养服务器可以使用各家云厂商都有的函数计算服务。创建函数时运行环境选 Python 3入口函数命名为 main_handler把 run_weather.py 里的 main 逻辑包一层def main_handler(event, context): from run_weather import main try: main() return {code: 0, msg: ok} except Exception as exc: print(推送失败:, exc) return {code: 1, msg: str(exc)}然后在函数配置里加一个定时触发器填 cron 表达式0 7 * * *大部分云函数平台上还要选时区务必选北京时间不然又会出现第 4.1 节里那个时区问题。云函数按调用次数计费一天一次基本处于免费额度内跑一年也花不了几块钱。这个方案有一个隐含的部署细节run_weather.py 需要作为函数代码上传或者放在同一目录下通过 from run_weather import main 引用同时函数运行环境里需要安装 requests 依赖上传代码时要把依赖一并打包或者使用平台自带的依赖声明文件。平台控制台里通常会自带日志查看功能main_handler 里的 print 会被收集排查问题比服务器还方便。4.3 先手动跑一遍再挂定时检查日志的三种方式无论选哪条路线我的习惯都是先手动运行一次确认逻辑没问题再挂定时。手动运行就是直接在项目目录执行python3 run_weather.py看终端输出的天气数据和推送结果。确认没问题后挂上 cron 的第一天故意把执行时间设成当前时间后两分钟等它自动触发再去看日志。在服务器上看日志有三种方式一是cat logs/weather.log直接查看最近输出二是tail -f logs/weather.log实时跟踪适合观察定时任务触发的瞬间三是crontab -l确认当前用户的定时任务列表里真的有那一行。这三条命令组合使用基本能把“到底跑没跑”查清楚。云函数路线没有本地日志文件对应到平台上的执行日志逻辑是同样的。5. 避坑指南我在推送上线后踩过的 5 个坑这一章记录我自己的真实踩坑过程每条都按现象、原因、解决来写遇到相同报错可以直接对照不用再从头排查。5.1 报错 40001appid 或 secret 复制错了现象脚本运行后get_access_token 抛异常打印出来的 JSON 里 errcode 为 40001提示 invalid credential。原因绝大多数是复制配置时出了问题。appsecret 从测试号后台复制时没有复制完整或者粘贴时带入了一个换行符和空格。测试号后台的 appsecret 是一串较长的十六进制字符手输特别容易错尤其是那些长得像的字符和数字。解决回到测试号后台重新复制 appid 和 appsecret粘贴前先确认首尾没有空格。如果还报错把配置值打印出来对比前四位和后四位基本一眼就能找到问题。这个坑在刚上手时几乎必踩一次踩过之后就会形成习惯所有配置都从后台复制粘贴绝不手打。5.2 模板消息报“data 格式错误”字段名没对齐现象send_template_message 返回非 0 的 errcode微信给出的提示大致是模板消息内容与模板不匹配。原因代码里 data 的 key 和测试号后台模板里定义的字段名不一致。模板里写的是 high代码里传的是 temp_high微信校验时发现模板里没有这个变量直接拒绝。还有一种情况是代码里传了模板里不存在的字段同样会报错。解决打开测试号后台的模板详情页把字段名逐字抄到代码里。模板字段里有的就传没有的就删数量也必须对等。调试时可以先写死一个测试值发送成功后再替换成真实天气数据这样能把“字段名问题”和“天气解析问题”分开排查。5.3 定时任务没触发服务器时区不是北京时间现象头天晚上手动运行一切正常第二天早上七点没有收到推送但下午某个时间又推送了。原因服务器系统时区是 UTCcron 表达式里的 7 点实际对应的是北京时间下午三点而下午三点正好是 UTC 7 点所以推送在下午触发了。如果你只观察早晨没看到消息很难第一时间想到是时区问题。解决先执行date命令确认时区再用第 4.1 节里的两种方式之一处理改 cron 时间为 UTC 等价时刻或者声明 CRON_TZ 为 Asia/Shanghai。云函数方案同理在定时触发器配置里找时区选项选北京时间即可。5.4 天气数据出现空值免费接口限流与降级现象推送正常发出但内容里最高温显示 0或者天气描述、风力字段为空。原因免费天气接口有调用额度调试阶段频繁请求把配额用完接口开始返回降级数据例如温度全部为 0、天气描述为空。脚本拿到这些脏数据后没有校验直接组装进模板消息发了出去结果就是女朋友收到一条明显错误的天气。解决在 fetch_weather 返回后加一层数据完整性校验温度范围不在 -40 到 50 度之间就抛异常天气描述为空也抛异常。发送之前任何一步校验失败本次推送直接终止。宁可当天不发也不要发一条错误天气过去这是这套系统里最重要的一条底线。5.5 提示用户不存在openid 不是微信号现象发送接口返回 40003提示 invalid openid。原因把女朋友的微信号昵称当作 openid 填进去了。openid 是微信给每个用户在某个公众号下分配的 ID看起来像一串乱码不是微信号更不是昵称。两者关系是固定的同一个用户在同一公众号下的 openid 不变但如果换了公众号openid 就变了。解决在测试号管理后台的用户列表里查看那里显示的 id 就是 openid点击复制后填入配置。注意这个值是一长串字母数字复制时别漏掉末尾字符。如果换了一个测试号需要重新获取之前的 openid 在新账号下无效。6. 让推送被期待加一句纪念日倒数和一点随机文案脚本稳定运行之后内容本身变得更重要。一个只有天气数字的消息和一个带纪念日倒数、随机关怀语的消息体验完全不同。这个增强做起来只需要十几行代码。6.1 在模板里多放一个“每日一句”在配置区下面加一个函数计算纪念日剩余天数再随机选一句关怀文案from datetime import date import random def love_tip() - str: today date.today() target date(today.year, 6, 1) # 改成你们的纪念日 delta (target - today).days if delta 0: target target.replace(yeartoday.year 1) delta (target - today).days tips [ 记得吃早饭, 今天也要多喝热水, 晚上早点睡别熬夜, 出门前检查钥匙手机, ] return f距离纪念日还有 {delta} 天{random.choice(tips)}这段代码先算纪念日和今天的差距如果今年的日期已经过了就自动切换到下一年的同一天保证结果永远是非负整数。然后从 tips 列表里随机挑一句拼成一个小短句。返回值加进 weather 字典发送时在 data 里多加一个字段即可模板消息需要先去测试号后台把新字段加上。6.2 失败静默宁可不发也别发错上线一段时间后我给自己立了一条规矩任何异常都不往用户端发送预警消息只记录在日志里。原因很简单女朋友不是你的运维同事她看到一条“推送失败”的提示除了困惑没有别的收获。脚本运行中天气服务商限流、微信接口抖动、网络短暂不可用这些都属于正常情况全部交给异常捕获和日志处理不打扰她。这套项目让我养成了一个小习惯先让链路完整跑通再把体验做温柔最后用日志和数据判断方向对不对。有过一次深夜查日志的经历之后我学会了控制自己的调试欲望功能稳定比频繁改代码更能守住信任。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站