☰
用PHP实现每日自动推送微信天气:模板消息与定时任务实战
2026/10/15 16:22:55 网站建设 项目流程

简介:围绕微信公众号每日定时推送场景,这份资源整理了完整的制作教程与可运行代码,面向想给伴侣或亲友推送天气、纪念日提醒、且具备少量编程基础的读者。压缩包内共5个文件,以 Python 主程序为核心,配合文本配置文件、Markdown 说明文档和 GitHub Actions 工作流文件,覆盖了 API 接入、消息模板、定时触发与依赖管理各环节,可帮助理解从天气/星座数据获取、消息模板拼接到云端定时自动运行的全链路实现。资源包仅5KB,体积虽小但方案完整、可操作性强,除最新完整版外,还附带简化版、星座版、星座+天气版及PHP版等多个变体,方便按不同需求直接选用。已有7692人学习下载,可见这类实用通知场景需求不小,直接参考成品代码与消息模板即可快速搭建属于自己的每日推送服务,省去从零调试的时间。

1. 这个教程在解决什么:每天一封天气早安,送进微信里

早上睁开眼第一件事不是看微信,而是打开天气App看一眼今天要不要带伞——这个动作你一定不陌生。如果你还想顺便给在意的人问声好,那每天重复这套动作其实挺繁琐的:先查天气,再想几句话,再复制粘贴发出去。时间一长,这事大概率就坚持不下来了。

这个项目做的事情,就是把整个流程自动化:通过 PHP 脚本定时去天气接口拉取当日天气数据,再调用微信公众号的消息推送接口,在每天早晨把「早安 + 今日天气 + 出行提醒」打包成一条模板消息,直接推到对方的微信里。你只需要做一次配置,之后每天固定时间自动发送,甚至你本人睡过头了,问候也不会缺席。

说白了,这是一个「接口对接 + 定时任务」的典型实战项目。它不复杂,但把微信公众号开发中最常用的三个环节——获取访问凭证、调用模板消息接口、外部数据源接入——完整走了一遍。适合的人群也很明确:有一点 PHP 基础、想把微信开发真正落地一次的人,以及那些很想表达关心但总是坚持不下来的朋友。接下来我就把从零到跑通的全过程拆开讲清楚,你可以照着做,也可以把它改造成自己的版本。

2. 核心链路:微信推送是怎么打通天气数据的

2.1 整体架构:两个外部接口 + 一个定时任务

整个系统包含三个参与者:你的服务器(跑脚本的地方)、天气数据源、微信公众平台。三者之间没有任何页面交互,全部通过 HTTP 请求完成数据交换。

你的服务器 (每天9点执行 PHP 脚本) ├── 请求天气接口:获取城市当日天气(温度、天气现象、风力) ├── 请求微信接口:获取 access_token(凭证) └── 组装模板消息 → 请求微信发送接口 └── 微信服务器 → 推送到目标用户微信

这里有一个常见误区:微信的模板消息不是「你的服务器直接发给用户微信」,而是你的服务器先告诉微信服务器「我要发一条这样的消息」,再由微信服务器推送给用户。所以你的服务器必须能访问公网,或者至少能访问微信接口域名,否则整个链路根本走不通。

2.2 两个关键接口的选择逻辑

天气数据源我选了支持城市模糊搜索、返回 JSON 格式、免费额度够个人使用的接口,这类接口在实名认证后每天有足够的免费调用次数。一次推送只调一次天气接口,完全没有成本压力。

微信侧需要两个接口配合:获取 access_token 的接口和模板消息发送接口。前者需要你在公众号后台配置 IP 白名单,后者需要提前在后台开通「模板消息」功能并选用一个模板。这两个接口是微信开发的老面孔,文档里参数写得清楚,但实际调用时容易在参数名上翻车,后面避坑那章我会细说。

2.3 PHP 脚本在整条链路里的位置

PHP 在这里不承担任何 UI 职责,它就是一个纯 CLI 脚本:按顺序调接口、拼字符串、发请求。我选择 PHP 的原因是教程面向的读者大多是做网站开发出身的,PHP 的环境几乎不用额外装,一个curl扩展就能跑起来。你要换成 Python 写,逻辑完全一样,只是语法层面的翻译。

脚本的核心流程固定为五步:

  1. 读取配置(城市、用户 openid、模板 ID、密钥)
  2. 请求天气接口,拿到 JSON
  3. 解析 JSON,提取天气现象、温度区间、风力
  4. 组装模板消息的数据结构
  5. 调用微信发送接口,看返回码判断是否成功

这五步里最容易出问题的是第 4 步——模板消息对参数结构要求极严格,多一层嵌套或少一个字段都会报错,而且报错信息比较晦涩。我在第 3 章会把这部分的完整代码贴出来逐段讲。

3. 代码逐段拆解:从天气数据到微信消息的完整实现

3.1 配置与初始化:把该变的东西全部拎出来

我先定义了一个配置区,把账号、城市、模板这些每次都可能调整的东西集中管理,不让散落在代码各处。

<?php // push_daily_weather.php // 用法:php push_daily_weather.php // ========== 配置区:按需修改 ========== $config = [ 'app_id' => '你的AppID', // 公众号后台基本配置里获取 'app_secret' => '你的AppSecret', // 公众号后台基本配置里获取 'template_id'=> '你的模板ID', // 模板消息功能中选用模板后可见 'user_openid'=> '接收人的openid', // 用户关注公众号后在聊天记录里可获取 'city' => '杭州', // 城市名称,天气接口支持模糊匹配 'region' => '浙江-杭州', // 用于模板消息中的展示文本 'tip' => '今天也要元气满满哦', // 自定义问候语,每天可改 ]; // 1. 获取微信公众号 access_token function getAccessToken($appId, $appSecret) { $url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $appId . '&secret=' . $appSecret; $result = json_decode(fileGet($url), true); // 如果返回结果里有 access_token 字段,说明获取成功 if (isset($result['access_token'])) { return $result['access_token']; } // 失败时把错误信息打到日志里,便于排查 file_put_contents('./error.log', date('Y-m-d H:i:s') . ' token获取失败: ' . json_encode($result) . PHP_EOL, FILE_APPEND); return null; }

这段代码里做了两件重要的事:一是把配置单独拎出来,二是封装了一个带日志记录的 token 获取函数。grant_type=client_credential是微信固定的参数,不需要改动;app_id和app_secret必须从公众号后台「基本配置」里复制,注意不是小程序的那对密钥,很多人在这一步复制错了导致一直报 40001 错误。fileGet是一个统一封装 HTTP GET 请求的函数,稍后和第 4 步的发送函数放在一起,避免代码里到处重复 curl。

3.2 获取天气数据:解析 JSON 并转换成友好文本

天气接口返回的是标准 JSON,里面字段很多,但我真正需要的只有三个:天气现象、最高/最低温度、风力等级。解析时要注意健壮性,接口偶尔会返回没有「result」字段的错误 JSON,这时要给用户提示而不是让脚本直接报错。

// 2. 获取指定城市的今日天气 function getWeather($city) { // 这里以某天气接口为例,具体 URL 以实际申请的接口文档为准 $url = 'https://api.weather.example.com/v3/weather/now?city=' . urlencode($city) . '&key=你的天气接口密钥'; $result = json_decode(fileGet($url), true); // 接口正常时返回码是 200,结构一般是 result.realtime if (isset($result['result']['realtime'])) { $weather = $result['result']['realtime']; // 部分接口会返回温度区间,没有就用当前温度加"℃" $temp = isset($weather['temperature']) ? $weather['temperature'] . '℃' : '未知'; $info = $weather['info']; // 天气现象描述:晴、多云、小雨等 $direct = $weather['direct']; // 风向 $power = $weather['power']; // 风力,如 3级 // 拼成一句适合直接放进模板消息的话 return "今日天气:{$info},气温 {$temp},{$direct} {$power}。"; } file_put_contents('./error.log', date('Y-m-d H:i:s') . ' 天气接口返回异常: ' . json_encode($result) . PHP_EOL, FILE_APPEND); return '今日天气:暂无数据,出门前记得看一眼窗外。'; }

这段的逻辑很直接:调接口、判空、取字段、拼字符串。需要注意的是urlencode($city),城市名是中文,不转义的话某些接口会拒绝请求。返回值设计成了一段可以直接嵌进模板消息的自然语言文本,而不是把 JSON 原样塞进去——因为最终这条消息是要推送到人微信里的,人读的是句子,不是数据结构。

3.3 组装模板消息并发送:微信接口的参数结构是重点

模板消息的请求体结构是微信定的,不能乱改。你需要先建一个「模板消息数据」数组,第一层是touser、template_id、data,data里的每个字段名必须与你选用的模板关键词一一对应,字段值是{ value, color }的结构。

// 3. 组装并发送模板消息 function sendTemplateMessage($accessToken, $openid, $templateId, $weatherText, $tip) { // 这里模板关键词是 tutorial 模板自带的:天气情况、温馨提示 // 实际字段名要去公众号后台->模板消息->你的模板里查看 $data = [ 'touser' => $openid, 'template_id' => $templateId, 'url' => '', // 点击消息跳转的链接,留空则不跳转 'data' => [ 'weather' => [ 'value' => $weatherText, 'color' => '#173177' ], 'tip' => [ 'value' => $tip, 'color' => '#000000' ] ] ]; $url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' . $accessToken; $result = json_decode(fileGet($url, json_encode($data, JSON_UNESCAPED_UNICODE)), true); // 微信返回码 0 表示发送成功,非 0 表示失败 if (isset($result['errcode']) && $result['errcode'] == 0) { return true; } file_put_contents('./error.log', date('Y-m-d H:i:s') . ' 模板消息发送失败: ' . json_encode($result) . PHP_EOL, FILE_APPEND); return false; }

这里有两个必须强调的细节。第一,data里的字段名(上面代码里的weather和tip)不是随便写的,必须和你后台选用的模板「关键词」名称完全对应。比如模板里第一个关键词叫「天气情况」,那字段名就要写成weather或其他你在模板里自定义的标识,填错了会报 47003 模板参数错误。第二,json_encode第二个参数传了JSON_UNESCAPED_UNICODE,这是为了让中文在请求体里保持可读状态,避免\u6e29这种转义序列引发某些接口解析异常。

3.4 统一入口:把三个函数串起来

最后写一个入口逻辑,判断 access_token 获取成功后才继续执行天气获取和发送,任何一个环节失败都终止脚本并留下日志。

// 4. 主流程 $accessToken = getAccessToken($config['app_id'], $config['app_secret']); if (!$accessToken) { exit('脚本终止:access_token 获取失败,请检查日志' . PHP_EOL); } $weatherText = getWeather($config['city']); $result = sendTemplateMessage( $accessToken, $config['user_openid'], $config['template_id'], $weatherText, $config['tip'] ); echo $result ? '发送成功' . PHP_EOL : '发送失败,详见 error.log' . PHP_EOL;

整个项目就是一个 PHP 文件,不到 80 行,不依赖任何框架,不连数据库,没有页面。它的全部职责就是「每天跑一次,发出一条消息」。你在命令行执行php push_daily_weather.php能看到输出结果,第一次跑通之后,剩下的就是让它每天自动执行。

4. 部署与定时触发:让脚本在每天早晨自动执行

4.1 部署位置的选择:你的服务器需要能访问外网

这个脚本的部署位置没有太多限制,只要能执行 PHP 且能访问外网就行。常见的做法是扔在一台便宜的云服务器上,或者你自己的一台常开的电脑上。需要注意的一点是,微信公众号接口要求调用方 IP 在白名单内,而白名单是在公众号后台配置的——如果你家里宽带的公网 IP 不固定,那每次 IP 变了都要去后台改一次白名单,会比较折腾。所以我在实际部署时更倾向于选择云服务器,IP 稳定,白名单配一次就行。

4.2 配置 crontab:定时任务的核心操作

定时调度我用的是 Linux 自带的 crontab,配置简单,没有任何额外依赖。在命令行输入crontab -e,在文件末尾加一行,保存退出,定时任务就生效了。

# 每天早上8点30分执行一次天气推送 30 8 * * * /usr/bin/php /www/wwwroot/weather-push/push_daily_weather.php >> /www/wwwroot/weather-push/run.log 2>&1

这行配置的含义是:分钟字段填 30,小时字段填 8,三个星号分别表示「每天、每月、每周」都执行。>> run.log 2>&1的意思是把脚本的输出和错误信息都追加到日志文件里,这样脚本有没有跑、跑成功了还是失败了,都有据可查。我建议每个用 crontab 的人都要养成看日志的习惯,定时任务最大的特点是它的失败是「安静失败」——没有弹窗,没有报错,只是一条本该出现的消息没出现,所以日志就成了唯一的排查入口。

4.3 另一种方案:无服务器定时触发

如果你没有云服务器,或者不想维护一台常开的机器,微信云开发控制台里也有定时触发的能力,本质上是把同样的 PHP 脚本包一层云函数入口,用云开发的定时触发器来调用。这种方案的好处是不用管服务器,坏处是调试起来比本地麻烦。我个人更推荐第一次跑通时先在本地用命令行执行,确认整个链路没问题了再上定时或不定期机制,否则你会在「脚本本身报错」和「定时配置错误」之间反复横跳,很难定位问题。

4.4 上线前的自检清单

我在部署完脚本后,会按下面这个顺序做一遍自检,确认万无一失再让它自动跑:

  1. 命令行手动执行三次,每次都收到消息才算通过
  2. 检查 error.log 里有没有 token 获取失败的记录
  3. 修改系统时间或手动执行一次,确认消息内容里没有乱码或缺失字段
  4. 观察两天,确认推送时间正确、内容无异常

这套流程走完,基本可以放心把脚本交给定时任务。如果中途哪一步出了问题,大概率是下面这些原因之一。

5. 避坑与常见问题:接口报错和消息发不出去的排查记录

5.1 排查流程:先分清楚是哪一环断了

整个链路只有三个环节:获取 token、获取天气、发送消息。任何一个环节出错,现象都表现为「微信上没有收到消息」。我第一次做的时候犯了个错——上来就检查发送接口,折腾半天,结果发现是 token 没拿到,后续步骤全部白搭。后来我总结出一个固定的排查顺序:

检查 error.log → 看是 token 失败还是发送失败 ├── token 失败 → 查 appid/secret 是否正确、IP 白名单是否有当前服务器 IP └── 发送失败 → 查返回错误码 ├── 40003 → openid 不正确 ├── 47003 → 模板参数不匹配 └── 45047 → 发送频率超限

这个顺序比直接去看微信错误码文档要高效得多,因为前三步里每一步失败都会留下日志,日志是判断问题方向的第一手材料。

5.2 常见问题记录:现象、原因与解决

问题一:返回码 40001「invalid credential」却找不到原因。这个报错说的是 access_token 无效或已过期。现象很迷惑:手动测试时明明发送成功了,定时任务却报 40001。原因是微信公众号官方要求 access_token 有效期两小时,但同一时刻只能有一个有效的 token。如果你在测试时频繁调用获取 token 的接口,后一次获取会让前一个 token 失效。解决方法是给脚本加一层 token 缓存,把 token 存到文件里,过期了再去拿新的,而不是每次执行都重新获取。

问题二:模板消息发送返回 47003 参数不匹配。这几乎是我见过最多的报错,现象是返回码 47003 且带一句「argument invalid」。原因分两种:要么字段名和模板关键词对不上,要么字段值格式不符合模板设置的类型。解决方法是先去公众号后台的模板消息页面,把你选的模板点开,看它每个关键词的类型要求(比如「日期」类型要求值格式是2019年10月1日),再回头比对代码里的data结构。不要靠猜,后台页面写得明明白白。

问题三:定时任务执行了但消息没收到。这个坑是 crontab 环境变量导致的。现象是命令行手动执行成功,crontab 里却静默失败。原因是 crontab 执行时使用的 PATH 很精简,php命令可能不在默认路径里。解决方法是像我第 4 章里写的那样,在 crontab 里写全路径/usr/bin/php,或者更稳妥地在脚本开头加一段#!/usr/bin/php并用完整路径执行。

问题四:天气接口免费额度迅速耗尽。如果你是按我 3.2 节的写法,每次执行都直接调实时天气接口,那一个用户一天只调一次,完全够用。但如果你在调试阶段每测一次就调一次,免费额度消耗得也很快。解决方法是调试阶段把天气接口返回的 JSON 保存成文件,先用本地缓存的 JSON 做开发,等逻辑跑通了再换回真实接口。

这四条是我在实际部署里真实踩过的坑,每条都至少浪费过半小时以上。如果你按本教程复现,建议把这一章截图存下来,等你在部署时遇到第一个困难,你会回来感谢这段记录的。

6. 二次开发思路:从天气问候变成你的专属小工具

跑通之后,你手里其实已经握着一个「能定时往微信发消息」的完整通道。通道比天气本身更有价值,因为你可以把任何数据源塞进这条通道。

6.1 改造一:加入节假日与纪念日提醒

这是最自然的延伸。在天气接口旁边再调一个节假日接口,或者干脆自己在配置文件里写死几个重要日期,在sendTemplateMessage之前做一个日期判断,当天命中纪念日就在 template 里多塞一个special字段,文本写「今天是相识第 1000 天」之类的。

// 在 getWeather 之后追加一段 $special = ''; $days = floor((time() - strtotime('2020-06-01')) / 86400); if ($days > 0 && $days % 100 == 0) { $special = "🎉 今天是纪念日 {$days} 天"; }

这种改造的工作量极小,但对收消息的人来说,感觉完全不同。

6.2 改造二:把被动推送改成按需查询

模板消息是单向推送,你没法让用户发一条「天气」就收到回复。如果你想做成这种交互,需要换一种接口思路:接入公众号的自动回复能力,用户发关键词,公众号回复当天天气。核心是把天气逻辑抽出来放到一个独立函数里,然后对接消息接收接口。这一步会把项目从「发送」升级到「交互」,复杂度会明显上升,但这也是公众号开发里更接近生产环境的方向。

6.3 改造三:多城市支持

如果你周围有几个朋友都需要天气推送,Configuration 里的city可以改成一个数组,循环调用getWeather,再循环发送。唯一要注意的是模板消息对同一用户每分钟发送频率有限制,多个用户轮流发不会有问题,但单个用户高频推送会被限流。把循环体保持在一个用户发一条的水平上,就不会触到红线。

我从这个项目里学到的最大一条习惯是:凡是交给定时任务之前的东西,先手动跑三遍;凡是涉及外部接口的脚本,日志必须留够信息。这两个习惯救过我不少次,希望你也能用上。希望帮到你。

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

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

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

立即咨询