← 返回博客列表

Python实时盯盘与预警

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

2026年10月15日 08:19 · 麦蕊智数 MyData

前 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

完整接口文档请查阅官网:https://www.mairuiapi.com/sdk

行情接口详细参数说明请查阅官网 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="<你的证书>")  # 演示证书,请替换成你自己的正式证书
    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=<你的证书>  # 演示证书,请替换成你自己的正式证书
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=<你的证书>  # 演示证书,请替换成你自己的正式证书

绝对不能把真实证书写进 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 篇文章,全部代码真实自验通过。一份证书、一套脚本,盘中到盘后全链路自动化。


十、系列文章

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


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

标签: Python量化
QQ 客服 3826425416 咨询时请提供证书号或订单号,便于快速处理
咨询