摘要:你是否遇到过网站突然宕机,自己却是在用户投诉后才知道的尴尬境地?作为开发者或运维,手动F5刷新页面来监控服务显然不现实。本文将带你使用Python编写一个高可用、实用级的网站健康监控脚本。它不仅能定时检查目标网站的状态码和响应时间,还能在服务异常时第一时间发送邮件告警,并详细记录运行日志。麻雀虽小,五脏俱全,掌握它,让你的服务监控不再“裸奔”。


🚀 引言:为什么需要这个脚本?

在数字化时代,服务的可用性至关重要。无论是个人博客、公司官网还是关键的API服务,一旦出现故障(如 500 错误、连接超时等),我们需要在第一时间获知,而不是等待用户反馈。

虽然市面上有著名的监控服务(如 Uptime Robot, Datadog 等),但对于很多中小型项目或个人开发者来说,它们要么收费,要么配置繁琐。

Python 作为胶水语言,极其适合完成这种自动化运维任务。今天我们将构建的脚本,目标是实现以下核心功能:

  1. 多目标监控:支持同时监控多个 URL。

  2. 深度检查:不仅检查 HTTP 状态码是否为 200,还监测响应时间是否超时。

  3. 健壮性设计:加入完善的异常捕获( Try/Except)和超时设置,防止脚本自身因网络波动而挂掉。

  4. 实时告警:服务异常时,通过 SMTP 发送邮件通知。

  5. 规范日志:使用标准 logging 模块记录一切,便于事后复盘。


🛠️ 准备工作

本项目依赖 Python 标准库以及一个第三方库 requests(用于更优雅地处理 HTTP 请求)。

  1. 环境要求:Python 3.6+

  2. 安装依赖

     
    pip install requests
    
  3. 准备一个发送邮件的邮箱:建议使用 163邮箱、QQ邮箱或 Gmail。注意: 为了安全起见,现代邮箱通常需要开启 SMTP 服务并生成授权码(App Password),而不是直接使用登录密码。请提前去邮箱设置中获取。


💡 核心思路解析

一个高可用的监控脚本,其核心流程是一个无限循环的定时任务:

graph TD
    A[启动脚本] --> B[读取配置(URL列表, 邮箱信息)];
    B --> C{进入主循环};
    C --> D[遍历监控目标 URL];
    D --> E[发起 HTTP GET 请求 (设置超时)];
    E --> F{请求成功且状态码为200?};
    F -- Yes --> G[记录正常日志];
    F -- No --> H[记录错误日志 & 发送告警邮件];
    G --> I[检查下一个 URL];
    H --> I;
    I --> J{所有 URL 检查完毕?};
    J -- Yes --> K[休眠 N 秒 (sleep)];
    J -- No --> D;
    K --> C;

💻 完整代码实战

以下是完整的脚本代码。你可以将其保存为 monitor.py。代码中已包含详细注释。

import requests
import smtplib
import time
import logging
from email.mime.text import MIMEText
from email.utils import formatdate
from requests.exceptions import RequestException, Timeout, ConnectionError

# ================= 配置区域 (请修改这里) =================

# 1. 监控目标列表
TARGET_URLS = [
    "https://www.baidu.com",
    "https://github.com",
    # 在这里添加你自己的网站,例如 "http://your-website.com/api/health"
    # "https://httpbin.org/status/500", # 测试用:故意模拟错误的地址
]

# 2. 监控配置
CHECK_INTERVAL = 60 * 5  # 检查间隔(秒),建议 300 (5分钟)
REQUEST_TIMEOUT = 10     # 请求超时时间(秒),超过此时间视为目标不可用

# 3. 邮件告警配置 (以 163 邮箱为例,其他邮箱请查找对应的 SMTP 设置)
SMTP_SERVER = "smtp.163.com"      # SMTP 服务器地址
SMTP_PORT = 465                   # SMTP SSL 端口 (通常是 465)
SENDER_EMAIL = "your_email@163.com" # 发件人邮箱
# 【重要】这里填写的是邮箱授权码,不是登录密码!
SENDER_PASSWORD = "YOUR_AUTH_CODE_HERE"
RECEIVER_EMAILS = ["receiver1@example.com"] # 收件人列表

# ================= 日志配置 =================
# 配置日志输出格式,同时输出到控制台和文件
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - [%(levelname)s] - %(message)s',
    handlers=[
        logging.FileHandler("monitor.log", encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)


# ================= 核心功能函数 =================

def send_alert_email(subject, message_body):
    """
    发送告警邮件的核心函数
    """
    try:
        msg = MIMEText(message_body, 'plain', 'utf-8')
        msg['Subject'] = f"【网站监控告警】{subject}"
        msg['From'] = SENDER_EMAIL
        msg['To'] = ",".join(RECEIVER_EMAILS)
        msg['Date'] = formatdate(localtime=True)

        # 使用 SSL 加密连接 SMTP 服务器
        with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, timeout=15) as server:
            server.login(SENDER_EMAIL, SENDER_PASSWORD)
            server.sendmail(SENDER_EMAIL, RECEIVER_EMAILS, msg.as_string())
        
        logger.info(f"告警邮件已成功发送给: {RECEIVER_EMAILS}")
        return True
    except smtplib.SMTPAuthenticationError:
        logger.critical("邮件发送失败:认证错误。请检查邮箱账号和授权码是否正确。")
    except Exception as e:
        logger.error(f"邮件发送失败,发生未知错误: {e}")
    return False

def check_site_health(url):
    """
    检查单个站点的健康状况
    返回: (是否健康 bool, 状态描述 str)
    """
    try:
        start_time = time.time()
        # 发起请求,务必设置 timeout,防止脚本卡死
        response = requests.get(url, timeout=REQUEST_TIMEOUT)
        end_time = time.time()
        response_time = round((end_time - start_time) * 1000, 2) # 毫秒

        if response.status_code == 200:
            logger.info(f"[正常] {url} - 状态码: {response.status_code} - 耗时: {response_time}ms")
            return True, f"OK (Time: {response_time}ms)"
        else:
            error_msg = f"异常状态码: {response.status_code}"
            logger.warning(f"[异常] {url} - {error_msg}")
            return False, error_msg

    except Timeout:
        error_msg = f"请求超时 (>{REQUEST_TIMEOUT}s)"
        logger.error(f"[不可用] {url} - {error_msg}")
        return False, error_msg
    except ConnectionError:
        error_msg = "连接失败 (无法解析域名或连接被拒绝)"
        logger.error(f"[不可用] {url} - {error_msg}")
        return False, error_msg
    except RequestException as e:
        # 捕获其他所有 requests 相关的异常
        error_msg = f"发生请求错误: {str(e)}"
        logger.error(f"[错误] {url} - {error_msg}")
        return False, error_msg

# ================= 主循环 =================

def run_monitor_loop():
    """
    主程序入口
    """
    logger.info("=== 网站监控脚本已启动 ===")
    logger.info(f"监控目标数: {len(TARGET_URLS)}, 检查间隔: {CHECK_INTERVAL}秒")

    # 用于记录上一次的告警状态,避免重复发送告警(简单的防抖动)
    # 格式: {url: is_down_last_time_bool}
    alert_history = {url: False for url in TARGET_URLS}

    while True:
        logger.debug("开始新一轮检查...")
        
        for url in TARGET_URLS:
            is_healthy, msg = check_site_health(url)
            
            if not is_healthy:
                # 如果当前不健康,且上一次是健康的(或者刚启动),则发送告警
                if not alert_history[url]:
                    alert_subject = f"目标无法访问: {url}"
                    alert_body = (
                        f"检测时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n"
                        f"故障目标: {url}\n"
                        f"错误信息: {msg}\n\n"
                        f"请相关人员尽快排查。"
                    )
                    # 发送邮件
                    send_alert_email(alert_subject, alert_body)
                    # 更新状态,标记已告警
                    alert_history[url] = True
            else:
                # 如果当前健康,且上一次是不健康的,可以发送一个恢复通知(可选)
                if alert_history[url]:
                     logger.info(f"[{url}] 服务已恢复正常。")
                     # 发送恢复邮件(可根据需要取消注释)
                     # send_alert_email(f"服务恢复: {url}", f"{url} 现已恢复正常访问。")
                     
                # 更新状态,标记为正常
                alert_history[url] = False

        logger.debug(f"本轮检查结束,休眠 {CHECK_INTERVAL} 秒...")
        time.sleep(CHECK_INTERVAL)

if __name__ == "__main__":
    # 建议在服务器使用 nohup python -u monitor.py > /dev/null 2>&1 & 后台运行
    run_monitor_loop()

🌟 代码亮点与高可用设计解析

这段代码虽然不长,但包含了很多生产环境脚本的必备要素:

1. 完善的日志记录 (Logging)

我们没有使用简陋的 print()

logging.basicConfig(..., handlers=[logging.FileHandler("monitor.log"), logging.StreamHandler()])

通过配置 logging 模块,脚本会将运行信息同时输出到控制台(方便调试)和 monitor.log 文件(方便持久化保存和排查历史故障)。每条日志都带有精确的时间戳和日志级别(INFO, WARNING, ERROR)。

2. 健壮的网络请求与超时控制 (Timeout)

这是监控脚本最关键的部分!

requests.get(url, timeout=REQUEST_TIMEOUT)

如果不设置 timeout,一旦目标网站卡死(既不返回数据也不断开连接),你的监控脚本也会随之无限期挂起,失去监控能力。设置超时是高可用脚本的基本素养。

3. 精细化的异常处理 (Try/Except)

我们没有简单地使用一个裸露的 except Exception

except Timeout: ...
except ConnectionError: ...
except RequestException as e: ...

代码专门捕获了 requests 库可能抛出的特定异常,如超时、连接错误等。这样我们就能区分网站是“慢”、是“挂了”还是“域名解析不了”,并在日志和邮件中提供更准确的错误信息。同时,邮件发送模块也单独进行了异常处理,防止因为邮件发不出去而导致整个监控循环崩溃。

4. 简单的告警防抖动机制 (Debouncing)

试想一下,如果一个网站挂了 1 个小时,每 5 分钟检查一次,你不想收到 12 封重复的“网站挂了”的邮件吧?

alert_history = {url: False for url in TARGET_URLS}
# ...
if not is_healthy:
    if not alert_history[url]: # 只有上一次状态是正常时,才发送告警
        send_alert_email(...)
        alert_history[url] = True

通过 alert_history 字典记录每个 URL 上一次的状态。只有当状态从“正常”变为“异常”的那个瞬间,才触发告警。这大大减少了垃圾邮件的轰炸。


🚀 如何运行与扩展

运行指南

  1. 修改代码中的配置区域,填入你的监控 URL 和邮箱 SMTP 信息(切记使用授权码)。

  2. 在终端运行:

    python monitor.py
    
  3. 服务器部署建议:在 Linux 服务器上,为了让脚本在后台稳定运行,并在退出终端后不中断,可以使用 nohup 命令:

    # -u 表示禁用输出缓存,让日志实时写入文件
    nohup python -u monitor.py > /dev/null 2>&1 &
    

    运行后,可以通过 tail -f monitor.log 查看实时运行日志。

扩展思路

这个脚本是一个坚实的基础,你可以继续扩展它:

  • 增加通知渠道:除了邮件,可以接入钉钉机器人、企业微信 Webhook 或 Telegram Bot 发送告警,即时性更强。

  • 内容校验:目前的脚本只检查状态码。你可以增加逻辑,检查返回的 HTML 页面中是否包含特定的关键词(例如检查页面是否有 "Welcome"),以防止页面虽然返回 200 但内容却是空白的情况。

  • 使用专业调度库:对于更复杂的定时任务,可以使用 APScheduler 库代替简单的 while True + sleep 循环。

  • 配置文件分离:将敏感的邮箱配置信息从代码中剥离,存放到环境变量或单独的 .env/config.ini 文件中,提高安全性。


📝 总结

本文介绍了一个 Python 实用脚本的完整开发过程。我们不仅实现了基础的网站监控功能,更重要的是融入了日志规范、异常处理、超时控制、告警防抖等“高可用”的设计思维。

希望这个实战案例能让你体会到 Python 在自动化运维领域的魅力。别再让你的服务“裸奔”了,赶紧动手部署一个属于你自己的监控探针吧!

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注一波!你的支持是我持续创作的动力! 👍

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐