给每日文章自动化加 watchdog:cron + 状态轮询 + 微信告警,再也不漏发
今天下午 3 点老板问我”今天发了几篇”,我去 ai.hkras.com 后台一拉——cat28 发了,cat4 啥都没有。

10:00 的自动化跑挂了。等到下午才发现,断更一整天。
这事我得治。根本原因不是”自动化写挂了”,是”挂了没人知道”。自动化总有概率失败(API 抽风、磁盘满、token 过期、配额耗尽…),但等到读者 / 老板发现就晚了。
今天我就把这个洞补上——给文章自动化加一个 watchdog。
一、需求拆解:watchdog 要干啥
我想要的很简单:
- 每小时查一次:今天该发的 cat4(10:00)、cat28(11:00)到底发了没
- 到了发布点 + 30 分钟还没发:触发告警
- 告警走微信:直接发到我手机,不用我手动查后台
听起来 50 行 Python 的事,但真做起来踩了 5 个坑,下面一个一个说。

二、核心代码
先上完整脚本,再讲坑。这个脚本我直接放在服务器 /opt/watchdog/check_posts.py,每小时 cron 跑一次:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""文章自动化 watchdog — 检查今日 cat4/cat28 是否已发布"""
import os
import sys
import json
import requests
from datetime import datetime, timedelta
import pytz
# —— 配置 ——
WP_URL = os.getenv("WP_URL", "https://ai.hkras.com")
WP_USER = os.getenv("WP_USER", "hewenqiang")
WP_PASS = os.getenv("WP_PASS") # 走环境变量,不写死
WX_PROXY = os.getenv("WX_PROXY", "https://ai.hkras.com/wx-proxy.php")
WX_TOKEN = os.getenv("WX_TOKEN") # 脱敏
# 预期发布时间表(BJT)
SCHEDULE = {
4: {"hour": 10, "minute": 0, "name": "技术笔记"},
28: {"hour": 11, "minute": 0, "name": "AI战略转型"},
}
BJT = pytz.timezone("Asia/Shanghai")
GRACE_MIN = 30 # 发布点后宽限 30 分钟
def fetch_today_posts(cat_id: int):
"""拉今天已发布的这个分类的最新一篇"""
now = datetime.now(BJT)
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
try:
r = requests.get(
f"{WP_URL}/wp-json/wp/v2/posts",
params={
"categories": cat_id,
"after": start.isoformat(),
"per_page": 1,
"orderby": "date",
"order": "desc",
},
auth=(WP_USER, WP_PASS),
timeout=10,
)
r.raise_for_status()
return r.json()
except Exception as e:
return {"_error": str(e)}
def send_wechat(text: str):
"""走 wx-proxy.php 发送微信通知"""
try:
r = requests.post(
WX_PROXY,
json={
"action": "send_text",
"proxy_token": WX_TOKEN,
"to_user": "hewenqiang",
"content": text,
},
timeout=10,
)
r.raise_for_status()
return r.json()
except Exception as e:
# 兜底:写本地日志
with open("/opt/watchdog/wx_failed.log", "a") as f:
f.write(f"{datetime.now(BJT).isoformat()}\t{text}\t{e}\n")
return {"_error": str(e)}
def main():
now = datetime.now(BJT)
print(f"[{now.isoformat()}] watchdog 启动")
for cat_id, sched in SCHEDULE.items():
# 发布点时间戳
publish_at = now.replace(
hour=sched["hour"], minute=sched["minute"], second=0, microsecond=0
)
# 还没到发布点 → 跳过
if now < publish_at:
print(f"⏳ cat{cat_id} 还没到发布点 {sched['hour']:02d}:{sched['minute']:02d}")
continue
# 还没过宽限期 → 跳过
if now < publish_at + timedelta(minutes=GRACE_MIN):
print(
f"⏳ cat{cat_id} 在宽限期 {GRACE_MIN}min 内,再观察"
)
continue
# 真正检查:今天这个分类有没有已发布的文章
posts = fetch_today_posts(cat_id)
if isinstance(posts, dict) and posts.get("_error"):
send_wechat(
f"🚨 watchdog 异常\n"
f"cat{cat_id} ({sched['name']}) 查询失败\n"
f"err: {posts['_error']}"
)
continue
if not posts:
# 没发 → 告警
send_wechat(
f"🚨 文章漏发告警\n"
f"分类: cat{cat_id} ({sched['name']})\n"
f"预期: {sched['hour']:02d}:{sched['minute']:02d}\n"
f"当前: {now.strftime('%H:%M')}\n"
f"URL: {WP_URL}/wp-admin/edit.php?cat={cat_id}"
)
print(f"❌ cat{cat_id} 今日未发布,已告警")
else:
title = posts[0]["title"]["rendered"]
print(f"✅ cat{cat_id} 已发布: {title[:30]}")
if __name__ == "__main__":
main()
cron 这边每小小时跑一次:
# 注意:服务器是 UTC,下面 0-23 是 UTC 时间
# 北京时间 0-23 = UTC 16-15(次日)
# 我直接用绝对时间偏移:北京时间每小时 → UTC 减 8 小时
0 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 * * * cd /opt/watchdog && /usr/bin/python3 check_posts.py >> /opt/watchdog/run.log 2>&1
跑了一周,没再漏发过。

三、5 个踩坑经验(每个都让我多花了半小时)
坑 1:服务器时区是 UTC,cron 不指定 TZ 就完蛋
我一开始写 0 10 * * * 想着北京 10:00 跑,结果发现服务器时区是 UTC——这个时间跑的是北京 18:00。watchdog 全天都在”等发布点”,真正的 10:00 反而没人查。
正解:要么在 cron 前面加 TZ=Asia/Shanghai(crontab 头部 CRON_TZ=Asia/Shanghai),要么把时间转成 UTC 写死。我选了后者,简单粗暴。
坑 2:WordPress REST API 过滤 categories 用 ID,不是 slug
我一开始写 ?categories=tech-notes,查回 0 篇,懵了半天。WP REST API 的 categories 参数只接受数字 ID——cat4 是 4,不是 "tech-notes"。
slugs 看 wp-admin 后台 → 文章 → 分类,鼠标悬停就能看到 tag_ID。也可以直接 GET /wp-json/wp/v2/categories?slug=tech-notes 查。
坑 3:after 参数必须带时区
?after=2026-08-19T00:00:00 这种 naive datetime WP 会按 UTC 解析——北京 0:00 发的文章会被漏掉,因为它在 WP 看来是”前一天的 16:00″。
正解:带 timezone 的 ISO 8601,例如 2026-08-19T00:00:00+08:00。
start = now.replace(hour=0, minute=0, second=0, microsecond=0)
# .isoformat() 会带上 +08:00(如果 BJT 是带 tzinfo 的)
坑 4:watchdog 自己也可能挂
第一次部署完我以为万事大吉。结果有一天我自己服务的 PHP-FPM 挂了,watchdog 也没跑成,没人发现。
正解是双层:
– 服务器内部 systemd 监控 watchdog 进程(Restart=on-failure)
– 云厂商的云监控再监控一次服务器本身(CPU / 内存 / 进程数)
而且 watchdog 的告警通道(wx-proxy.php)也要定期演练——我每周手动 curl 一次,确认 token 还能用。
坑 5:微信测试号每分钟限 20 条,别让 watchdog 太勤
我一开始图省事写”每 5 分钟跑一次”,结果某次 API 抽风,watchdog 连发 20 条微信轰炸我手机。
正解:
– 每小时跑一次足够(10:30 之前 cat4 没人查也没事,发文本来就在 10:00)
– 告警里加一个去重逻辑——同一类问题 1 小时内只发一次
– 真正紧急的事不要走微信测试号,走 Server 酱 / 阿里云告警 / 短信

四、最后说两句
自动化这东西,写出来只完成了一半,能”感知失败”才算闭环。
我今天的教训:
– 10:00 跑挂了 → 没人知道 → 下午才发现
– 加了 watchdog → 下次再挂,10:30 微信就响了
代码就上面那些,核心是 cron + WP REST API + wx-proxy.php 三件套。如果你也在跑类似的定时发文 / 定时同步 pipeline,别等出问题再加 watchdog,今天就加。
不然你下一次发现漏发,可能就是读者比你先到。
(全文完)