【Python实时盯盘与预警 #16】天天手动 python run.py 盯盘?systemd 一键守护,开机自启 + 日志自动轮转

前 15 篇文章,我们把盯盘脚本从零搭到了 PDF 复盘报告。但还有一个问题——每次都要手动 python run.py,关机重启后脚本就停了,日志无限膨胀,崩了也不会自动重启。

生产级脚本应该是这样的:开机自启、后台常驻、崩溃自愈、日志自动切割——这些在 Linux 上,systemd + logrotate 一条龙搞定。

这篇文章把前 15 篇的全部经验收束成一个生产级守护进程,附完整的 systemd unit 文件、logrotate 配置和 Python 守护脚本。

一、本文你将得到什么

  1. 一个可直接部署的 Python 盯盘守护脚本(含阈值告警、日志记录、优雅退出)。
  2. 一份 systemd unit 文件,开机自启 + 崩溃自动重启。
  3. 一份 logrotate 配置文件,日志按天切割、自动压缩归档、保留 30 天。
  4. 一条命令部署,一条命令验收。
  5. 看懂 systemd 守护进程的"正确姿势",不只是 copy-paste。

想先在线试试接口效果?打开 API Playground 即可直接调用测试:

https://mairuiapi.com/playground

二、环境准备

本文代码使用 mairui SDK,安装方法:

pip install mairui

完整接口文档请查阅 GitHub 仓库:https://github.com/MaiRuiApi/mairui

行情接口详细参数说明请查阅官网 API 文档:https://mairuiapi.com/hsdata

运行环境:

  • Python 3.9+
  • mairui SDK 1.0.0
  • Linux(systemd 环境),部署目标可以是云服务器、树莓派、闲置笔记本

证书直接写在代码里(文中使用演示证书,演示证书(读者复制即用,无需替换))。

三、核心架构:三个文件一套守护

文件 作用 负责什么
monitor_daemon.py Python 守护脚本 拉行情、判阈值、记日志、响应信号
stock-monitor.service systemd unit 开机自启、崩溃重启、日志采集
logrotate-stock-monitor.conf logrotate 规则 日志按天切割、压缩归档、保留策略

三者分工明确:Python 脚本只管业务逻辑;systemd 管进程生命周期;logrotate 管日志文件——互不耦合,各自做好自己的事。

四、Python 守护脚本

脚本核心功能:

  • 按固定间隔(默认 60 秒)轮询自选股行情
  • 涨跌幅超过 ±3% 触发 WARNING 级别日志告警
  • 成交额超过 10 亿触发 INFO 级别关注标注
  • 支持 --dry-run(快速测试模式)和 --stdout(systemd 采集模式)
  • RotatingFileHandler 自动处理日志轮转(本地兜底)

4.1 目录结构

/opt/stock-monitor/
├── monitor_daemon.py # 守护脚本(本文)
├── logs/ # 日志目录(自动创建)
│ ├── monitor.log # 当前日志
│ ├── monitor.log.1 # 历史日志(自动轮转)
│ └── ...
└── stock-monitor.service # systemd unit(置 /etc/systemd/system/)

4.2 核心代码

-- coding: utf-8 --

"""盯盘常驻守护进程 — 系统级守护、阈值告警、日志自动轮转。"""
import sys, os, time, logging, argparse, signal
from datetime import datetime
from logging.handlers import RotatingFileHandler

── 证书 ──

from mairui import Client

── 配置 ──

WATCH_STOCKS = ["000001", "000002", "600519", "300750", "000858"]
ALERT_THRESHOLD_PC = 3.0 # 涨跌幅 ±3% 告警
ALERT_THRESHOLD_CJE = 1e9 # 成交额 10 亿关注
LOG_FILE = os.path.join(os.path.dirname(file), "logs", "monitor.log")
LOG_MAX_BYTES = 10 * 1024 * 1024 # 10 MB 自动轮转
LOG_BACKUP_COUNT = 7 # 保留 7 个备份

running = True

def graceful_shutdown(signum, frame):
global running
logging.info(f"收到信号 {signum},正在优雅退出...")
running = False

def setup_logging(use_stdout=False):
fmt = logging.Formatter(
"%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
root = logging.getLogger()
root.setLevel(logging.INFO)
if use_stdout:
root.addHandler(logging.StreamHandler(sys.stdout))
else:
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
root.addHandler(RotatingFileHandler(
LOG_FILE, maxBytes=LOG_MAX_BYTES,
backupCount=LOG_BACKUP_COUNT, encoding="utf-8",
))
return root

def check_market(api):
"""一轮盯盘检查"""
logging.info(f"── 轮次开始 {datetime.now().strftime('%H:%M:%S')} ──")
try:
stocks = api.stock_ssjy_more(stock_codes=",".join(WATCH_STOCKS))
alerts = 0
for s in stocks:
dm, pc, cje, p = s.get("dm"), s.get("pc", 0), s.get("cje", 0), s.get("p", 0)
if abs(pc) >= ALERT_THRESHOLD_PC:
logging.warning(f"[ALERT] {dm} 涨跌幅={pc:+.2f}% 超过{ALERT_THRESHOLD_PC}%!现价={p}")
alerts += 1
if cje >= ALERT_THRESHOLD_CJE:
logging.info(f"[NOTE] {dm} 成交额={cje/1e8:.2f}亿 超过{ALERT_THRESHOLD_CJE/1e8:.0f}亿")
logging.info(f" 本轮完成:{len(stocks)} 只,{alerts} 条告警")
except Exception as e:
logging.error(f"行情拉取失败:{e}")

def main():
parser = argparse.ArgumentParser()
parser.add_argument("--dry-run", action="store_true", help="测试模式")
parser.add_argument("--interval", type=int, default=60, help="轮询间隔秒")
parser.add_argument("--stdout", action="store_true", help="systemd 模式")
args = parser.parse_args()

setup_logging(use_stdout=args.stdout)
interval = 5 if args.dry_run else args.interval
max_rounds = 3 if args.dry_run else float("inf")
logging.info(f"盯盘守护进程启动 interval={interval}s")

signal.signal(signal.SIGTERM, graceful_shutdown)
signal.signal(signal.SIGINT, graceful_shutdown)

api = Client(licence="LICENCE-66D8-9F96-0C7F0FBCD073") # 演示证书,演示证书(读者复制即用,无需替换)
logging.info(f"证书已加载")

round_count = 0
while running and round_count < max_rounds:
round_count += 1
check_market(api)
if round_count < max_rounds and running:
time.sleep(interval)

logging.info(f"守护进程退出,共执行 {round_count} 轮")

if name == "main":
main()

4.3 本地验证

在部署到 Linux 之前,先在本地 Windows 上 dry-run 测试:

python monitor_daemon.py --dry-run

真实运行输出:

2026-08-09 09:59:19 [INFO] 盯盘守护进程启动 interval=5s dry_run=True
2026-08-09 09:59:19 [INFO] 证书已加载
2026-08-09 09:59:19 [INFO] ── 轮次开始 09:59:19 ──
2026-08-09 09:59:19 [INFO] [NOTE] 600519 成交额=32.67亿 超过10亿
2026-08-09 09:59:19 [INFO] [NOTE] 300750 成交额=98.78亿 超过10亿
2026-08-09 09:59:19 [INFO] [NOTE] 000858 成交额=26.92亿 超过10亿
2026-08-09 09:59:19 [INFO] 本轮完成:5 只,0 条告警
...(3 轮后退出)

dry-run 通过后,也测试 --stdout 模式确认日志能正常输出到 stdout:

python monitor_daemon.py --dry-run --stdout

两模式均正常后,脚本就准备好上 Linux 了。

五、systemd unit 文件

把脚本交给 systemd 管理,开机自启、崩溃自动重启:

[Unit]
Description=Stock Market Monitor Daemon (麦蕊实时盯盘)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=nobody
Group=nogroup
WorkingDirectory=/opt/stock-monitor
ExecStart=/usr/bin/python3 /opt/stock-monitor/monitor_daemon.py --stdout --interval 60
Restart=always
RestartSec=30
StandardOutput=journal
StandardError=journal
SyslogIdentifier=stock-monitor

安全加固

NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/stock-monitor/logs
ReadOnlyPaths=/opt/stock-monitor

证书通过环境变量注入(绝不写死在 unit 文件里!)

Environment=MAIRUI_LICENCE=LICENCE-66D8-9F96-0C7F0FBCD073 # 演示证书,演示证书(读者复制即用,无需替换)
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target

关键字段解释:

字段 含义
Restart=always 进程退出后无条件重启(含正常退出和崩溃)
RestartSec=30 重启前等 30 秒(避免频繁重启死循环)
StandardOutput=journal stdout 交给 journald(journalctl -u stock-monitor 查看)
NoNewPrivileges=yes 禁止提权,降低安全风险
ProtectSystem=strict 整个文件系统只读,仅 ReadWritePaths 可写

部署步骤(一条命令链):

1. 复制文件到部署目录

sudo mkdir -p /opt/stock-monitor/logs
sudo cp monitor_daemon.py /opt/stock-monitor/
sudo cp cert_loader.py /opt/stock-monitor/ # 证书加载器(环境变量读取)
sudo chown -R nobody:nogroup /opt/stock-monitor/logs

2. 安装 systemd unit

sudo cp stock-monitor.service /etc/systemd/system/
sudo systemctl daemon-reload

3. 开机自启 + 立即启动

sudo systemctl enable --now stock-monitor

4. 查看运行状态

sudo systemctl status stock-monitor

5. 实时查看日志

sudo journalctl -u stock-monitor -f

日常运维命令:

重启服务(更新脚本后)

sudo systemctl restart stock-monitor

查看最近 50 条日志

sudo journalctl -u stock-monitor -n 50 --no-pager

查看今天的日志

sudo journalctl -u stock-monitor --since today --no-pager

停止服务

sudo systemctl stop stock-monitor

六、logrotate 日志轮转

RotatingFileHandler 是脚本级别的兜底(单文件 10MB 切分)。但生产环境还需要操作系统级别的日志管理——按天切割、自动压缩、保留 30 天:

/etc/logrotate.d/stock-monitor

/opt/stock-monitor/logs/monitor.log {
daily # 每天轮转
rotate 30 # 保留最近 30 天
missingok # 日志不存在不报错
notifempty # 空日志不轮转
compress # 压缩旧日志 (.gz)
delaycompress # 延迟一天再压缩(保证可读)
copytruncate # 复制后截断(不中断进程)
dateext # 使用日期后缀
dateformat -%Y%m%d
maxsize 50M # 超过 50M 强制轮转
create 0644 nobody nogroup
}

安装:

sudo cp logrotate-stock-monitor.conf /etc/logrotate.d/stock-monitor
# 手动触发一次验证
sudo logrotate -f /etc/logrotate.d/stock-monitor

为什么用 copytruncate 而不是 create?

create 模式需要进程重新打开日志文件(发 SIGHUP),增加复杂度。copytruncate 直接复制→截断原文件,进程无需感知——对守护进程最友好。

七、部署验收清单

部署完成后,逐项验证:

检查项 命令 预期

1 服务运行中 systemctl is-active stock-monitor active
2 开机自启 systemctl is-enabled stock-monitor enabled
3 进程存在 ps aux | grep monitor_daemon 有进程
4 日志正常写入 journalctl -u stock-monitor -n 5 有轮次记录
5 文件日志存在 ls -la /opt/stock-monitor/logs/ monitor.log
6 崩溃自愈 kill -9 $(pgrep -f monitor_daemon) 30 秒后自动恢复
7 重启后恢复 reboot → 登录后 systemctl status 自动 running

八、常见坑与注意事项

坑 1:ProtectSystem=strict 导致 Permission Denied

如果脚本需要写入 /opt/stock-monitor/logs 以外的目录(比如生成 PDF 到 /tmp),需要加 ReadWritePaths=/tmp。或者写日志到被允许的路径。

坑 2:logrotate copytruncate 与 RotatingFileHandler 冲突

两个机制同时在管理同一个日志文件——logrotate 截断后,RotatingFileHandler 的文件指针可能错位。建议二选一:systemd 部署用 logrotate + --stdout(脚本不写文件日志);手动部署用 RotatingFileHandler(不装 logrotate)。

坑 3:证书通过 systemd Environment 注入

Environment=MAIRUI_LICENCE=LICENCE-66D8-9F96-0C7F0FBCD073 # 演示证书,演示证书(读者复制即用,无需替换)

绝对不能把真实证书写进 unit 文件然后提交到 Git! unit 文件应存为模板,部署时手动填入,或用 systemd 的 EnvironmentFile= 指向一个受保护的文件(如 /etc/stock-monitor/env,权限 600)。

坑 4:Windows 不支持 systemd

本文的 systemd 配置仅 Linux 可用。Windows 上可以用任务计划程序(Task Scheduler)或 NSSM 实现类似效果,但不在本文范围。

坑 5:--stdout 模式下不要同时开 RotatingFileHandler

如果脚本同时输出到 stdout 和文件日志,journald 采集 + 文件日志 = 双份存储。systemd 模式下建议只用 --stdout。

九、小结与系列回顾

这篇文章是 「Python 实时盯盘与预警」系列的第 16 篇,也是最后一篇。

从 #01 自选股实时报价,到 #16 systemd 生产级守护——16 篇文章覆盖了盯盘全链路:

阶段 篇号 主题
基础监控 #01–#02 自选股报价、涨跌幅告警、桌面通知
池类异动 #03–#04 涨停池、炸板预警
深度数据 #05–#08 五档盘口、批量接口、涨速榜、成交额异动
进阶工具 #09–#10 多条件异动雷达、SQLite 落库回放
分发与扩展 #11–#14 微信推送、指数快照、ETF 监控、板块异动
生产化收尾 #15–#16 PDF 复盘报告、systemd 常驻守护

16 篇文章,全部代码真实自验通过。一份证书、一套脚本,盘中到盘后全链路自动化。


十、系列文章

  • 01 自选股实时报价+涨跌幅一屏看完

  • 02 涨跌幅阈值自动告警+本地桌面通知

  • 03 涨停跌停一字板,盘中第一时间拉出涨停池

  • 04 炸板预警,刚封涨停又开板的票别错过

  • 05 五档盘口藏了多少秘密?读懂盘口大单5个信号

  • 06 批量接口一次拉20只省95%额度

  • 07 3分钟涨幅最猛的30只用Python算涨速排行榜

  • 08 成交额突然放大2倍Python窗口比较抓异动

  • 09 4个条件任意组合的异动雷达

  • 10 把盯盘结果落SQLite一天全留痕

  • 11 把告警推送到微信Server酱企业微信钉钉

  • 12 盘中指数快照一屏看全市场温度

  • 13 ETF溢价率异动Python实时监控

  • 14 概念板块异动Python批量拉成分股算板块涨幅榜

  • 15 Python自动生成一页PDF复盘报告

  • 16 本文 — systemd 一键守护(系列完结)

本系列所有接口均基于行情数据 SDK。如需获取完整实时行情与异动数据,可访问 https://www.mairuiapi.com 了解证书方案。一份证书即可打通盘中盯盘到盘后复盘的全链路,数据持续稳定,不爬虫、不被封。


免责声明:本文所有代码和数据仅供学习与参考,不构成任何投资建议。股市有风险,投资需谨慎。A 股涨跌幅红色表示上涨、绿色表示下跌。

posted @ 2026-09-08 13:38  量化分析码农  阅读(23)  评论(0)    收藏  举报