mail_domains_check.py

  • 通过脚本巡检即将到期的域名,并发送邮件到相关人

  • 只需要修改***就可以添加发送人和接收人

  • 巡检内容地址为:/data/domains_list,格式为域名+空格备注(liwork.cn 博客网站)

  • 效果示意:

  • 执行命令:

python3 mail_domains_check.py

代码内容

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import subprocess
import smtplib
import ssl
import re
from datetime import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText

# ------------------ 配置参数 ------------------
SMTP_HOST = "***"
SMTP_PORT = ***
SMTP_USER = "***"
SMTP_PASSWORD = "***"
RECIPIENTS = [
    "***",
    "***"
]
DOMAIN_LIST_FILE = "/data/domains_list"  # 域名列表文件
OUTPUT_FILE = "/data/domains_lists"
# ---------------------------------------------

def get_domain_expiry(domain):
    """获取域名过期时间"""
    try:
        # 针对.cn域名使用特定WHOIS服务器
        if domain.endswith('.cn'):
            cmd = f"timeout 30 whois -h whois.cnnic.cn {domain}"
        else:
            cmd = f"timeout 30 whois {domain}"
        
        result = subprocess.run(
            cmd,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            universal_newlines=True,
            encoding='utf-8'
        )
        
        if result.returncode != 0:
            raise Exception(f"WHOIS错误: {result.stderr.strip()}")
        
        # 解析过期日期
        whois_output = result.stdout.lower()
        patterns = [
            r'expiry date\s*:\s*(\d{4}-\d{2}-\d{2})',
            r'registry expiry date\s*:\s*(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})',
            r'expiration time\s*:\s*(\d{4}-\d{2}-\d{2})',
            r'registrar registration expiration date\s*:\s*(\d{4}-\d{2}-\d{2})'
        ]
        
        expiry_date = None
        for pattern in patterns:
            match = re.search(pattern, whois_output)
            if match:
                expiry_date = match.group(1).split()[0]  # 取日期部分
                break
                
        if not expiry_date:
            raise Exception("未找到过期日期字段")
            
        return datetime.strptime(expiry_date, '%Y-%m-%d')
            
    except Exception as e:
        print(f"[ERROR] {domain} 检测失败: {str(e)}")
        return None

def check_domains():
    """主检查函数"""
    if not os.path.exists(DOMAIN_LIST_FILE):
        print(f"[FATAL] 配置文件 {DOMAIN_LIST_FILE} 不存在")
        sys.exit(1)
    
    warning_domains = []
    
    with open(DOMAIN_LIST_FILE, 'r', encoding='utf-8') as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            
            parts = line.split(maxsplit=1)
            domain = parts[0]
            note = parts[1] if len(parts) > 1 else ''
            
            print(f"\n{'='*80}")
            print(f"正在检测: {domain}")
            
            expiry_date = get_domain_expiry(domain)
            if not expiry_date:
                print("查询失败,跳过该域名")
                continue
                
            now = datetime.now()
            days_left = (expiry_date - now).days
            
            print(f"剩余天数: {days_left}")
            
            if days_left < 30:  # 过期阈值设为30天
                print("[WARNING] 域名即将过期")
                with open(OUTPUT_FILE, 'a', encoding='utf-8') as f:
                    f.write(f"{domain}\n")
                
                warning_domains.append({
                    'domain': domain,
                    'expire': expiry_date.strftime('%Y-%m-%d'),
                    'note': note
                })
    
    return warning_domains

def send_email(warnings):
    """发送邮件(完全保留原始样式)"""
    if not warnings:
        print("无域名过期预警")
        return
    
    current_date = datetime.now().strftime("%Y-%m-%d")
    
    msg = MIMEMultipart('alternative')
    msg['Subject'] = f'[{current_date}] 域名过期预警 - 需人工处理'
    msg['From'] = f"Liwork自动巡检 <{SMTP_USER}>"
    msg['To'] = ", ".join(RECIPIENTS)
    
    # 生成表格内容
    table_rows = []
    for item in warnings:
        table_rows.append(f"""
        <tr style="border-bottom: 1px solid #eee;">
            <td style="padding: 12px 0; color: #2c3e50; width: 40%;">{item['domain']}</td>
            <td style="padding: 12px 0; color: #e74c3c; width: 30%;">{item['expire']}</td>
            <td style="padding: 12px 0; color: #95a5a6; width: 30%;">{item['note']}</td>
        </tr>
        """)
    
    # 完整保留原始邮件模板
    html_content = f"""
<html>
<head>
<meta charset="UTF-8">
<style>
    body {{
        font-family: Helvetica, Arial, sans-serif;
        margin: 0;
        padding: 30px;
        background: #fafafa;
    }}
    .header {{
        background: #2c3e50;
        color: white;
        padding: 25px 30px;
        border-radius: 8px;
        margin-bottom: 25px;
    }}
    .alert-section {{
        background: #ffe5e5;
        color: #c0392b;
        padding: 15px;
        border-radius: 6px;
        margin-bottom: 25px;
        border-left: 4px solid #c0392b;
    }}
    .report-table {{
        width: 100%;
        background: white;
        border-radius: 8px;
        box-shadow: 0 2px 4px rgba(0,0,0,0.05);
        border-collapse: collapse;
        margin-bottom: 30px;
    }}
    .report-table th {{
        background: #f8f9fb;
        padding: 16px 0;
        color: #7f8c8d;
        font-weight: 500;
        text-align: left;
        border-bottom: 2px solid #eee;
    }}
    .report-table td {{
        padding: 14px 0;
    }}
    .footer {{
        color: #95a5a6;
        font-size: 0.9em;
        padding-left: 10px;
        line-height: 1.5;
    }}
</style>
</head>
<body>
    <div class="header">
        <h1 style="margin:0; font-weight:300;">域名过期巡检报告</h1>
        <h3 style="margin:10px 0 0; font-weight:400;">检测日期:{current_date}</h3>
    </div>

    <div class="alert-section">
        发现 {len(warnings)} 个域名即将过期(剩余天数 &lt; 30)
    </div>

    <table class="report-table">
        <thead>
            <tr>
                <th style="padding-left: 30px;">域名</th>
                <th>过期时间</th>
                <th style="padding-right: 30px;">备注信息</th>
            </tr>
        </thead>
        <tbody>
            {''.join(table_rows)}
        </tbody>
    </table>

    <div class="footer">
        <p>技术支持:zhouli@liwork.cn</p>
        <p style="margin-top:8px;">此邮件由系统自动生成,请勿直接回复</p>
    </div>
</body>
</html>
    """
    
    msg.attach(MIMEText(html_content, 'html', 'utf-8'))
    
    try:
        context = ssl.create_default_context()
        with smtplib.SMTP_SSL(
            host=SMTP_HOST,
            port=SMTP_PORT,
            context=context,
            timeout=30
        ) as server:
            server.login(SMTP_USER, SMTP_PASSWORD)
            server.sendmail(SMTP_USER, RECIPIENTS, msg.as_string())
        print("告警邮件已发送至:", RECIPIENTS)
    except Exception as e:
        print(f"[ERROR] 邮件发送失败: {str(e)}")

if __name__ == "__main__":
    # 初始化输出文件
    with open(OUTPUT_FILE, 'w', encoding='utf-8') as f:
        f.write("")
    
    print("=== 域名过期巡检开始 ===")
    warnings = check_domains()
    
    if warnings:
        print(f"\n发现 {len(warnings)} 个域名即将过期")
        send_email(warnings)
    else:
        print("\n所有域名状态正常")
    
    print("=== 检查完成 ===")