生产环境必须用APScheduler而非schedule库,因其支持守护进程、持久化和异常自动恢复;推荐BackgroundScheduler模式,配置coalesce=True和max_instances=1防堆积,并配合while循环或WSGI服务器运行。

用 APScheduler 做定时调度,别碰 schedule 库
直接上结论:生产环境必须用 APScheduler,而不是轻量级的 schedule。后者没有守护进程、不支持持久化、异常崩溃后不会自动恢复,爬虫挂了你根本不知道。
推荐使用 APScheduler 的 BackgroundScheduler 模式(非阻塞),配合 BlockingScheduler 仅用于调试。启动时加 coalesce=True 和 max_instances=1 防止任务堆积或并发冲突:
from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler({
'apscheduler.job_defaults.coalesce': True,
'apscheduler.job_defaults.max_instances': 1
})
scheduler.add_job(crawl_task, 'interval', minutes=30)
scheduler.start()
注意:BackgroundScheduler 必须搭配 while True: time.sleep(3600) 或用 waitress/gunicorn 托管,否则主进程一结束,调度就停了。
微信推送用企业微信「应用消息」,不是个人号或公众号
别折腾 itchat 或 wechaty——前者已停更且登录不稳定,后者依赖微信协议逆向,随时失效。企业微信应用消息是唯一稳定、免扫码、可批量推送、支持 Markdown 的方案。
立即学习“Python免费学习笔记(深入)”;
关键步骤只有三步:
- 在企业微信后台创建「自建应用」,拿到
corp_id、secret、agent_id - 调用
https://qyapi.weixin.qq.com/cgi-bin/gettoken换取access_token(缓存 2 小时,别每次请求都重取) - POST 到
https://qyapi.weixin.qq.com/cgi-bin/message/send,toparty填部门 ID,touser填成员账号(如zhangsan),msgtype推荐用text或markdown
示例错误推送片段:
def send_wechat_alert(msg):
token = get_access_token() # 自行实现缓存逻辑
data = {
"touser": "zhangsan",
"msgtype": "markdown",
"agentid": 100001,
"markdown": {"content": f"⚠️ 爬虫异常\n> 时间:{datetime.now()}\n> 错误:{msg}"}
}
requests.post(f"https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={token}", json=data)
异常捕获必须包裹整个任务函数,且区分「预期异常」和「崩溃异常」
很多人只在 requests.get() 外层 try,结果 DNS 失败、SSL 握手失败、解析器报错(如 lxml.etree.ParserError)全被漏掉,任务静默退出。
正确做法:在 crawl_task 函数最外层加 try/except Exception,但要排除 KeyboardInterrupt 和 SystemExit;对网络类异常(如 requests.exceptions.RequestException、socket.timeout)做降级重试(最多 2 次),其余一律触发微信告警:
def crawl_task():
for attempt in range(3):
try:
resp = requests.get(url, timeout=15)
parse_and_save(resp.text)
break # 成功则跳出重试
except (requests.exceptions.RequestException, socket.timeout) as e:
if attempt == 2:
send_wechat_alert(f"网络请求连续失败:{e}")
time.sleep(2 ** attempt) # 指数退避
except Exception as e:
send_wechat_alert(f"未预期异常:{type(e).__name__}: {e}")
break
注意:APScheduler 默认不传播异常到主线程,所以必须在任务内部处理并主动通知。
日志不能只写文件,得把 ERROR 级别实时发到微信
光靠 logging.FileHandler 不够——服务器磁盘满了、日志轮转出错、或者你根本没连上服务器,告警就断了。必须让 ERROR 日志同时触发微信推送。
用 logging.Handler 子类自定义一个微信日志处理器:
class WeChatHandler(logging.Handler):
def emit(self, record):
if record.levelno >= logging.ERROR:
msg = self.format(record)
send_wechat_alert(msg[:2000]) # 企业微信单条上限 2000 字
<p>logger = logging.getLogger("crawler")
logger.addHandler(WeChatHandler())
logger.setLevel(logging.INFO)
这样,哪怕你在代码里写了 logger.error("XPath 匹配为空"),也会立刻出现在微信里。比手动 send_wechat_alert 更可靠,也避免遗漏。
真正容易被忽略的是:企业微信 access_token 有调用频率限制(2000次/天),别在日志 handler 里不做限流就疯狂发;还有,send_wechat_alert 自身必须有超时和异常兜底,否则它自己挂了会导致整个爬虫线程卡死。



















