第三章:Python自动化脚本——运维的瑞士军刀

说实话,在量化运维这个行当里摸爬滚打这么多年,我越来越觉得Python就是运维人员的“第二双手”。你想想看,每天要盯着几十台服务器,检查磁盘、看进程、捞日志……要是全靠手动操作,那不得累死?

我个人习惯是,但凡重复操作超过三次,就写个脚本把它自动化掉。这一章,我就把平时用得最多的几个Python运维场景,掰开揉碎了讲给你听。

3.1 文件与目录操作:运维的基本功

运维工作里,文件操作是最频繁的。日志切割、配置文件备份、数据归档……这些活儿,Python的osshutil模块都能搞定。

3.1.1 路径拼接与判断

我见过不少新手喜欢用字符串拼接路径,比如"/data/logs/" + filename。嗯,这在Windows上会出大问题。正确的做法是用os.path.join

import os

# 自动处理不同操作系统的路径分隔符
log_dir = "/data/logs"
today = "2025-03-21"
full_path = os.path.join(log_dir, today, "trade.log")
print(full_path)  # /data/logs/2025-03-21/trade.log

# 判断文件是否存在
if os.path.exists(full_path):
    print("日志文件存在,开始处理...")
else:
    print("文件不存在,请检查日期")
我的经验:在量化交易系统里,日志文件经常按日期分目录存放。用os.path.join可以避免因为路径分隔符问题导致的“文件找不到”错误。我曾经因为这个问题,排查了整整一个下午。

3.1.2 批量重命名与移动

有一次,交易所改了数据文件的命名规则,从trade_20250321.csv变成了20250321_trade.csv。几百个文件,手动改?不存在的。写个脚本几秒钟搞定:

import os
import glob

data_dir = "/data/market_data"
# 匹配所有trade开头的csv文件
for old_name in glob.glob(os.path.join(data_dir, "trade_*.csv")):
    # 提取日期部分
    basename = os.path.basename(old_name)
    date_part = basename.split("_")[1].split(".")[0]
    new_name = os.path.join(data_dir, f"{date_part}_trade.csv")
    os.rename(old_name, new_name)
    print(f"已重命名: {basename} -> {os.path.basename(new_name)}")

3.1.3 目录遍历与文件筛选

量化系统跑久了,日志目录里会堆积大量文件。我们需要定期清理超过30天的旧日志。用os.walk可以递归遍历所有子目录:

import os
import time

def clean_old_logs(log_root, days=30):
    now = time.time()
    cutoff = now - days * 86400
    cleaned_count = 0
    
    for dirpath, dirnames, filenames in os.walk(log_root):
        for filename in filenames:
            if filename.endswith(".log"):
                full_path = os.path.join(dirpath, filename)
                # 获取文件最后修改时间
                mtime = os.path.getmtime(full_path)
                if mtime < cutoff:
                    os.remove(full_path)
                    cleaned_count += 1
                    print(f"已删除过期日志: {full_path}")
    
    return cleaned_count

# 执行清理
count = clean_old_logs("/data/logs", days=30)
print(f"本次共清理 {count} 个日志文件")
注意:删除文件前一定要确认路径正确。我建议先打印出要删除的文件列表,人工确认后再执行删除操作。别问我怎么知道的……有一次我写错了路径,差点把数据库备份给删了。

3.2 系统信息采集:给服务器做“体检”

量化交易对系统性能极其敏感。CPU飙高、内存不足、磁盘写满……任何一个指标出问题,都可能导致交易延迟甚至亏损。所以,系统信息采集是运维自动化的核心环节。

3.2.1 使用psutil采集系统指标

psutil是我最常用的系统监控库,没有之一。它跨平台、接口统一,用起来非常顺手:

import psutil
import datetime

def collect_system_info():
    info = {}
    
    # CPU信息
    info['cpu_percent'] = psutil.cpu_percent(interval=1)
    info['cpu_count'] = psutil.cpu_count()
    info['cpu_load_avg'] = psutil.getloadavg()
    
    # 内存信息
    mem = psutil.virtual_memory()
    info['memory_total'] = mem.total / (1024**3)  # 转GB
    info['memory_used'] = mem.used / (1024**3)
    info['memory_percent'] = mem.percent
    
    # 磁盘信息
    disk = psutil.disk_usage('/')
    info['disk_total'] = disk.total / (1024**3)
    info['disk_used'] = disk.used / (1024**3)
    info['disk_percent'] = disk.percent
    
    # 网络信息
    net = psutil.net_io_counters()
    info['net_bytes_sent'] = net.bytes_sent
    info['net_bytes_recv'] = net.bytes_recv
    
    # 时间戳
    info['timestamp'] = datetime.datetime.now().isoformat()
    
    return info

# 采集一次系统信息
sys_info = collect_system_info()
for key, value in sys_info.items():
    print(f"{key}: {value}")

3.2.2 进程管理:监控交易程序

量化交易程序必须7x24小时运行。如果进程挂了,我们需要第一时间发现并重启。用psutil可以轻松实现进程监控:

import psutil

def check_trading_process(process_name="quant_trader"):
    """检查交易进程是否在运行"""
    for proc in psutil.process_iter(['pid', 'name', 'create_time']):
        try:
            if process_name in proc.info['name']:
                return {
                    'status': 'running',
                    'pid': proc.info['pid'],
                    'uptime': datetime.datetime.now() - datetime.datetime.fromtimestamp(
                        proc.info['create_time']
                    )
                }
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            pass
    return {'status': 'stopped'}

# 检查交易程序
result = check_trading_process()
if result['status'] == 'running':
    print(f"交易程序运行正常,PID: {result['pid']},已运行: {result['uptime']}")
else:
    print("警告:交易程序未运行!")
    # 这里可以触发重启逻辑
核心思路:系统信息采集不是一次性工作,而是需要持续监控。我通常每5分钟采集一次数据,写入时序数据库,再配合告警规则。这样一旦指标异常,就能立刻收到通知。

3.3 异常处理与日志:让脚本更健壮

运维脚本最怕什么?怕半夜跑着跑着突然挂了,连个错误信息都没留下。所以,异常处理和日志记录是脚本的“安全带”和“黑匣子”。

3.3.1 异常处理:别让脚本静默死亡

很多新手写脚本,遇到异常就直接崩溃。其实,合理的异常处理可以让脚本在出错时优雅地降级:

import os
import sys

def safe_read_config(config_path):
    """安全读取配置文件"""
    try:
        with open(config_path, 'r') as f:
            return f.read()
    except FileNotFoundError:
        print(f"配置文件 {config_path} 不存在,使用默认配置")
        return "default_config"
    except PermissionError:
        print(f"没有权限读取 {config_path},请检查文件权限")
        sys.exit(1)
    except Exception as e:
        print(f"读取配置文件时发生未知错误: {e}")
        # 记录到日志文件
        with open('/var/log/quant_ops.log', 'a') as log:
            log.write(f"[ERROR] {datetime.datetime.now()}: {e}\n")
        return None

3.3.2 日志记录:用logging模块代替print

我早期写脚本也喜欢用print,但后来发现这玩意儿在生产环境根本不够用。日志需要分级、需要输出到文件、需要自动轮转……logging模块才是正道:

import logging
import logging.handlers

def setup_logger(log_file="/var/log/quant_ops.log"):
    """配置日志系统"""
    logger = logging.getLogger('quant_ops')
    logger.setLevel(logging.DEBUG)
    
    # 文件处理器:每天轮转,保留30天
    file_handler = logging.handlers.TimedRotatingFileHandler(
        log_file, when='midnight', backupCount=30
    )
    file_handler.setLevel(logging.INFO)
    
    # 控制台处理器:开发时用
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.DEBUG)
    
    # 日志格式
    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)
    
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    return logger

# 使用日志
logger = setup_logger()
logger.info("系统信息采集任务开始")
try:
    info = collect_system_info()
    logger.info(f"采集完成,CPU使用率: {info['cpu_percent']}%")
except Exception as e:
    logger.error(f"采集失败: {e}", exc_info=True)
避坑指南:我曾经在日志轮转配置上吃过亏。如果backupCount设置得太小,历史日志会被覆盖;如果太大,磁盘会被撑爆。对于量化系统,我建议保留30天的日志,每天轮转一次。这样既能追溯问题,又不会占用太多磁盘空间。

3.4 知识体系总览

说了这么多,我们来梳理一下本章的核心逻辑。下面这张图,是我自己总结的Python运维自动化知识框架:

Python运维自动化知识体系 Python运维脚本 文件与目录操作 路径拼接 批量重命名 目录遍历 系统信息采集 CPU/内存 磁盘/网络 进程监控 异常处理与日志 try/except logging模块 日志轮转 目标:构建稳定、可观测的量化运维自动化体系

说白了,这三个模块就是运维自动化的三根支柱。文件操作管的是“数据怎么存”,系统采集管的是“机器跑得怎么样”,异常日志管的是“出错了怎么办”。三者缺一不可。

我个人习惯是,每写一个运维脚本,先把日志系统搭好,再把关键操作包在try/except里。这样即使脚本半夜跑崩了,第二天早上我也能通过日志快速定位问题。嗯,这个习惯帮我避免了好几次生产事故。


专注资料整理