1
This commit is contained in:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
||||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 敏感配置文件
|
||||||
|
config.ini
|
||||||
|
|
||||||
|
# Python虚拟环境
|
||||||
|
venv/
|
||||||
|
*.pyc
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
# 运行时生成的文件
|
||||||
|
.last_grade_*.txt
|
||||||
|
.last_grade_*.html
|
||||||
|
.last_courses.txt
|
||||||
|
.debug_response.html
|
||||||
|
monitor.log
|
||||||
|
|
||||||
|
# 备份文件
|
||||||
|
*.backup
|
||||||
|
*_old.*
|
||||||
|
|
||||||
|
# 系统文件
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
408
Debian服务器部署指南.md
Normal file
408
Debian服务器部署指南.md
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
# 成绩监控系统 - Debian服务器部署指南
|
||||||
|
|
||||||
|
## 📦 项目文件说明
|
||||||
|
|
||||||
|
### 必需文件(需要上传到服务器)
|
||||||
|
|
||||||
|
| 文件名 | 用途 | 何时使用 |
|
||||||
|
|--------|------|----------|
|
||||||
|
| `monitor.py` | **主程序** | 运行监控的核心文件 |
|
||||||
|
| `config.ini` | **配置文件** | 包含账号密码、邮箱配置(⚠️ 敏感文件) |
|
||||||
|
| `requirements.txt` | **Python依赖列表** | 安装Python包时使用 |
|
||||||
|
| `setup_python.sh` | **环境安装脚本** | 首次部署时执行,安装所有依赖 |
|
||||||
|
| `grade-monitor.service` | **systemd服务配置** | 设置开机自启和后台运行 |
|
||||||
|
|
||||||
|
### 可选文件(参考文档)
|
||||||
|
|
||||||
|
| 文件名 | 用途 |
|
||||||
|
|--------|------|
|
||||||
|
| `config(模板).ini` | 配置文件模板,新用户参考 |
|
||||||
|
| `readme.md` | 项目说明文档 |
|
||||||
|
| `部署.md` | 详细部署步骤 |
|
||||||
|
| `常见问题解决.md` | 常见问题和解决方案 |
|
||||||
|
|
||||||
|
### 不需要的文件(不要上传)
|
||||||
|
|
||||||
|
- `venv/` - 虚拟环境(服务器上重新创建)
|
||||||
|
- `.git/` - Git仓库(可选)
|
||||||
|
- `.last_*` - 运行时生成的缓存文件
|
||||||
|
- `monitor.log` - 运行时生成的日志文件
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Debian服务器部署步骤
|
||||||
|
|
||||||
|
### 第一步:准备文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 在本地打包必需文件
|
||||||
|
cd /mnt/e/50425/Documents/Github/GPA_Monitoring
|
||||||
|
|
||||||
|
# 创建压缩包(只包含必需文件)
|
||||||
|
tar -czf gpa_monitor.tar.gz \
|
||||||
|
monitor.py \
|
||||||
|
config.ini \
|
||||||
|
requirements.txt \
|
||||||
|
setup_python.sh \
|
||||||
|
grade-monitor.service \
|
||||||
|
readme.md
|
||||||
|
|
||||||
|
# 查看压缩包内容
|
||||||
|
tar -tzf gpa_monitor.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第二步:上传到服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 方法1:使用 scp
|
||||||
|
scp gpa_monitor.tar.gz 用户名@服务器IP:/home/用户名/
|
||||||
|
|
||||||
|
# 方法2:使用 rsync(推荐)
|
||||||
|
rsync -avz gpa_monitor.tar.gz 用户名@服务器IP:/home/用户名/
|
||||||
|
|
||||||
|
# 方法3:使用 sftp
|
||||||
|
sftp 用户名@服务器IP
|
||||||
|
put gpa_monitor.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第三步:在服务器上解压并安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 登录到Debian服务器
|
||||||
|
ssh 用户名@服务器IP
|
||||||
|
|
||||||
|
# 解压文件
|
||||||
|
cd ~
|
||||||
|
tar -xzf gpa_monitor.tar.gz
|
||||||
|
cd gpa_monitor # 或者你解压到的目录
|
||||||
|
|
||||||
|
# 给脚本添加执行权限
|
||||||
|
chmod +x setup_python.sh
|
||||||
|
|
||||||
|
# 运行安装脚本(会自动安装Python、创建虚拟环境、安装依赖)
|
||||||
|
./setup_python.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第四步:检查配置文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 编辑配置文件(如果需要修改)
|
||||||
|
nano config.ini
|
||||||
|
|
||||||
|
# 确认配置正确:
|
||||||
|
# - USERNAME 和 PASSWORD(学号和密码)
|
||||||
|
# - 邮箱配置(SENDER_EMAIL、SENDER_PASSWORD、RECEIVER_EMAIL)
|
||||||
|
# - CHECK_INTERVAL(建议120秒以上)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第五步:测试运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 激活虚拟环境
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# 测试运行(获取一次成绩,不进行监控)
|
||||||
|
python3 monitor.py --test
|
||||||
|
|
||||||
|
# 如果测试成功,运行正式监控
|
||||||
|
python3 monitor.py
|
||||||
|
# 按 Ctrl+C 停止
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 设置后台运行(三选一)
|
||||||
|
|
||||||
|
### 方案A:使用 tmux(推荐,简单易用)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 安装 tmux(如果没有)
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install tmux
|
||||||
|
|
||||||
|
# 2. 创建会话并运行
|
||||||
|
tmux new -s grade_monitor
|
||||||
|
source venv/bin/activate
|
||||||
|
python3 monitor.py
|
||||||
|
|
||||||
|
# 3. 离开会话(程序继续运行)
|
||||||
|
# 按 Ctrl+B,然后按 D
|
||||||
|
|
||||||
|
# 4. 重新连接查看
|
||||||
|
tmux attach -t grade_monitor
|
||||||
|
|
||||||
|
# 5. 查看所有会话
|
||||||
|
tmux ls
|
||||||
|
```
|
||||||
|
|
||||||
|
**tmux 使用时机:**
|
||||||
|
- ✅ 测试阶段使用
|
||||||
|
- ✅ 需要随时查看程序输出
|
||||||
|
- ✅ 临时运行,不需要开机自启
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方案B:使用 systemd 服务(推荐,生产环境)
|
||||||
|
|
||||||
|
**`grade-monitor.service` 文件用途:**
|
||||||
|
这是 systemd 服务配置文件,告诉系统如何启动、管理和自动重启你的程序。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 编辑服务文件,修改用户名和路径
|
||||||
|
nano grade-monitor.service
|
||||||
|
|
||||||
|
# 确保这些路径正确:
|
||||||
|
# User=你的用户名
|
||||||
|
# WorkingDirectory=/home/你的用户名/gpa_monitor
|
||||||
|
# ExecStart=/home/你的用户名/gpa_monitor/venv/bin/python3 /home/你的用户名/gpa_monitor/monitor.py
|
||||||
|
|
||||||
|
# 2. 复制服务文件到系统目录
|
||||||
|
sudo cp grade-monitor.service /etc/systemd/system/
|
||||||
|
|
||||||
|
# 3. 重新加载 systemd
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
# 4. 启动服务
|
||||||
|
sudo systemctl start grade-monitor
|
||||||
|
|
||||||
|
# 5. 查看状态
|
||||||
|
sudo systemctl status grade-monitor
|
||||||
|
|
||||||
|
# 6. 设置开机自启
|
||||||
|
sudo systemctl enable grade-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
**systemd 常用命令:**
|
||||||
|
```bash
|
||||||
|
# 启动
|
||||||
|
sudo systemctl start grade-monitor
|
||||||
|
|
||||||
|
# 停止
|
||||||
|
sudo systemctl stop grade-monitor
|
||||||
|
|
||||||
|
# 重启
|
||||||
|
sudo systemctl restart grade-monitor
|
||||||
|
|
||||||
|
# 查看状态
|
||||||
|
sudo systemctl status grade-monitor
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
journalctl -u grade-monitor -f
|
||||||
|
|
||||||
|
# 开机自启
|
||||||
|
sudo systemctl enable grade-monitor
|
||||||
|
|
||||||
|
# 禁用自启
|
||||||
|
sudo systemctl disable grade-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
**systemd 使用时机:**
|
||||||
|
- ✅ 生产环境长期运行
|
||||||
|
- ✅ 需要开机自启动
|
||||||
|
- ✅ 程序崩溃后自动重启
|
||||||
|
- ✅ 系统化管理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 方案C:使用 nohup(最简单,但不推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 后台运行
|
||||||
|
source venv/bin/activate
|
||||||
|
nohup python3 monitor.py > output.log 2>&1 &
|
||||||
|
|
||||||
|
# 查看进程
|
||||||
|
ps aux | grep monitor.py
|
||||||
|
|
||||||
|
# 停止程序
|
||||||
|
pkill -f monitor.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**nohup 使用时机:**
|
||||||
|
- ✅ 临时快速运行
|
||||||
|
- ❌ 不适合长期运行
|
||||||
|
- ❌ 程序崩溃不会自动重启
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 监控和维护
|
||||||
|
|
||||||
|
### 查看日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看监控日志(程序自己的日志)
|
||||||
|
tail -f ~/gpa_monitor/monitor.log
|
||||||
|
|
||||||
|
# 查看最后100行
|
||||||
|
tail -n 100 ~/gpa_monitor/monitor.log
|
||||||
|
|
||||||
|
# 搜索关键词
|
||||||
|
grep "新增课程" ~/gpa_monitor/monitor.log
|
||||||
|
|
||||||
|
# 查看系统日志(如果用systemd)
|
||||||
|
sudo journalctl -u grade-monitor -f
|
||||||
|
sudo journalctl -u grade-monitor --since "1 hour ago"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 检查运行状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 方法1:查看进程
|
||||||
|
ps aux | grep monitor.py
|
||||||
|
|
||||||
|
# 方法2:查看日志时间戳
|
||||||
|
ls -lh ~/gpa_monitor/monitor.log
|
||||||
|
|
||||||
|
# 方法3:systemd状态(如果用systemd)
|
||||||
|
sudo systemctl status grade-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 更新程序
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 停止程序
|
||||||
|
# tmux: Ctrl+C 或 tmux kill-session -t grade_monitor
|
||||||
|
# systemd: sudo systemctl stop grade-monitor
|
||||||
|
|
||||||
|
# 2. 备份配置
|
||||||
|
cp config.ini config.ini.backup
|
||||||
|
|
||||||
|
# 3. 上传新版本文件并解压
|
||||||
|
|
||||||
|
# 4. 恢复配置
|
||||||
|
cp config.ini.backup config.ini
|
||||||
|
|
||||||
|
# 5. 重启程序
|
||||||
|
# tmux: 重新运行
|
||||||
|
# systemd: sudo systemctl restart grade-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 安全建议
|
||||||
|
|
||||||
|
### 1. 保护配置文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置文件权限(只有所有者可读写)
|
||||||
|
chmod 600 config.ini
|
||||||
|
|
||||||
|
# 查看权限
|
||||||
|
ls -l config.ini
|
||||||
|
# 应该显示:-rw------- 1 用户名 用户名
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 不要上传敏感文件到 GitHub
|
||||||
|
|
||||||
|
在 `.gitignore` 中添加:
|
||||||
|
```
|
||||||
|
config.ini
|
||||||
|
*.log
|
||||||
|
.last_*
|
||||||
|
venv/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 定期检查日志
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查是否有异常
|
||||||
|
grep -i "error\|fail\|warning" monitor.log
|
||||||
|
|
||||||
|
# 检查登录情况
|
||||||
|
grep "登录" monitor.log | tail -20
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❓ 常见问题
|
||||||
|
|
||||||
|
### Q1: 如何确认程序在运行?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 方法1:查看进程
|
||||||
|
ps aux | grep monitor.py
|
||||||
|
|
||||||
|
# 方法2:查看日志最后几行
|
||||||
|
tail monitor.log
|
||||||
|
|
||||||
|
# 方法3:查看文件修改时间
|
||||||
|
ls -lh monitor.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q2: 程序报错"请不要过快点击"怎么办?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 编辑配置文件,增加检查间隔
|
||||||
|
nano config.ini
|
||||||
|
|
||||||
|
# 修改 CHECK_INTERVAL 为更大的值(如300秒)
|
||||||
|
CHECK_INTERVAL = 300
|
||||||
|
|
||||||
|
# 重启程序
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q3: 如何在多台服务器部署?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 每台服务器重复部署步骤,注意:
|
||||||
|
# 1. 每台服务器使用不同的监控账号(如果可能)
|
||||||
|
# 2. 适当增加 CHECK_INTERVAL 避免同时访问
|
||||||
|
# 3. 可以设置不同的邮件接收地址
|
||||||
|
```
|
||||||
|
|
||||||
|
### Q4: 忘记 tmux 会话名怎么办?
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 列出所有会话
|
||||||
|
tmux ls
|
||||||
|
|
||||||
|
# 连接到第一个会话
|
||||||
|
tmux attach
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 快速命令参考
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# === 部署 ===
|
||||||
|
tar -xzf gpa_monitor.tar.gz
|
||||||
|
cd gpa_monitor
|
||||||
|
chmod +x setup_python.sh
|
||||||
|
./setup_python.sh
|
||||||
|
|
||||||
|
# === 运行 ===
|
||||||
|
# 测试
|
||||||
|
source venv/bin/activate && python3 monitor.py --test
|
||||||
|
|
||||||
|
# tmux运行
|
||||||
|
tmux new -s grade_monitor
|
||||||
|
source venv/bin/activate && python3 monitor.py
|
||||||
|
|
||||||
|
# systemd运行
|
||||||
|
sudo cp grade-monitor.service /etc/systemd/system/
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl start grade-monitor
|
||||||
|
sudo systemctl enable grade-monitor
|
||||||
|
|
||||||
|
# === 监控 ===
|
||||||
|
# 查看日志
|
||||||
|
tail -f monitor.log
|
||||||
|
|
||||||
|
# 查看状态
|
||||||
|
sudo systemctl status grade-monitor
|
||||||
|
|
||||||
|
# === 停止 ===
|
||||||
|
# tmux: Ctrl+C
|
||||||
|
# systemd: sudo systemctl stop grade-monitor
|
||||||
|
# nohup: pkill -f monitor.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 获取帮助
|
||||||
|
|
||||||
|
- 查看项目 README: `cat readme.md`
|
||||||
|
- 查看常见问题: `cat 常见问题解决.md`
|
||||||
|
- 查看详细部署: `cat 部署.md`
|
||||||
|
- 查看程序帮助: `python3 monitor.py --help`
|
||||||
35
config(模板).ini
Normal file
35
config(模板).ini
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# 成绩监控配置文件
|
||||||
|
# 请根据实际情况修改以下配置
|
||||||
|
|
||||||
|
[login]
|
||||||
|
# 统一身份认证账号
|
||||||
|
USERNAME =
|
||||||
|
# 统一身份认证密码
|
||||||
|
PASSWORD =
|
||||||
|
# 登录页面URL
|
||||||
|
LOGIN_URL = https://webvpn.neu.edu.cn/http/62304135386136393339346365373340e2b0fd71d8941093ab4e2527/eams/homeExt.action
|
||||||
|
# 成绩查询URL
|
||||||
|
GRADE_URL = https://webvpn.neu.edu.cn/http/62304135386136393339346365373340e2b0fd71d8941093ab4e2527/eams/teach/grade/course/person!search.action?semesterId=113&projectType=
|
||||||
|
|
||||||
|
[email]
|
||||||
|
# 发件人邮箱
|
||||||
|
SENDER_EMAIL =
|
||||||
|
# 163邮箱SMTP授权码(不是邮箱密码!)
|
||||||
|
# 需要在163邮箱设置中开启SMTP服务并获取授权码
|
||||||
|
SENDER_PASSWORD =
|
||||||
|
# 收件人邮箱
|
||||||
|
RECEIVER_EMAIL =
|
||||||
|
# SMTP服务器
|
||||||
|
SMTP_SERVER =
|
||||||
|
# SMTP端口
|
||||||
|
SMTP_PORT =
|
||||||
|
|
||||||
|
[monitor]
|
||||||
|
# 检查间隔(秒),建议60秒以上
|
||||||
|
CHECK_INTERVAL = 60
|
||||||
|
# 请求间隔(秒),建议5秒以上
|
||||||
|
REQUEST_DELAY = 5
|
||||||
|
# 重试次数
|
||||||
|
MAX_RETRIES = 3
|
||||||
|
# 重试间隔(秒)
|
||||||
|
RETRY_DELAY = 10
|
||||||
16
grade-monitor.service
Normal file
16
grade-monitor.service
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=GPA Grade Monitor Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=chuxun
|
||||||
|
WorkingDirectory=/mnt/e/50425/Documents/Github/GPA_Monitoring
|
||||||
|
ExecStart=/mnt/e/50425/Documents/Github/GPA_Monitoring/venv/bin/python3 /mnt/e/50425/Documents/Github/GPA_Monitoring/monitor.py
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=30
|
||||||
|
StandardOutput=append:/mnt/e/50425/Documents/Github/GPA_Monitoring/monitor.log
|
||||||
|
StandardError=append:/mnt/e/50425/Documents/Github/GPA_Monitoring/monitor.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
739
monitor.py
Normal file
739
monitor.py
Normal file
@@ -0,0 +1,739 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
成绩监控系统
|
||||||
|
功能:自动登录并监控成绩页面变化
|
||||||
|
作者:GitHub Copilot
|
||||||
|
日期:2026-01-17
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
import hashlib
|
||||||
|
import smtplib
|
||||||
|
import ssl
|
||||||
|
import requests
|
||||||
|
import argparse
|
||||||
|
import random
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
from configparser import ConfigParser
|
||||||
|
from email.mime.text import MIMEText
|
||||||
|
from email.mime.multipart import MIMEMultipart
|
||||||
|
from typing import Optional, Dict
|
||||||
|
import signal
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='[%(levelname)s] [%(asctime)s] %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('monitor.log', encoding='utf-8'),
|
||||||
|
logging.StreamHandler()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GradeMonitor:
|
||||||
|
"""成绩监控类"""
|
||||||
|
|
||||||
|
def __init__(self, config_file: str = 'config.ini', test_mode: bool = False):
|
||||||
|
"""初始化监控器"""
|
||||||
|
self.config_file = config_file
|
||||||
|
self.config = self._load_config()
|
||||||
|
self.test_mode = test_mode
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.headers.update({
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
||||||
|
})
|
||||||
|
|
||||||
|
# 文件路径
|
||||||
|
self.script_dir = Path(__file__).parent.absolute()
|
||||||
|
self.last_grade_file = self.script_dir / '.last_grade_hash.txt'
|
||||||
|
self.last_grade_html_file = self.script_dir / '.last_grade_page.html'
|
||||||
|
self.last_grade_content_file = self.script_dir / '.last_grade_content.txt'
|
||||||
|
|
||||||
|
# 运行标志
|
||||||
|
self.running = True
|
||||||
|
|
||||||
|
# 设置信号处理
|
||||||
|
signal.signal(signal.SIGINT, self._signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||||
|
|
||||||
|
def _signal_handler(self, signum, frame):
|
||||||
|
"""处理终止信号"""
|
||||||
|
logger.info("收到停止信号,正在退出...")
|
||||||
|
self.running = False
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def _load_config(self) -> Dict:
|
||||||
|
"""加载配置文件"""
|
||||||
|
if not os.path.exists(self.config_file):
|
||||||
|
logger.error(f"配置文件不存在: {self.config_file}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
parser = ConfigParser()
|
||||||
|
parser.read(self.config_file, encoding='utf-8')
|
||||||
|
|
||||||
|
try:
|
||||||
|
config = {
|
||||||
|
# 登录配置
|
||||||
|
'username': parser.get('login', 'USERNAME', fallback=''),
|
||||||
|
'password': parser.get('login', 'PASSWORD', fallback=''),
|
||||||
|
'login_url': parser.get('login', 'LOGIN_URL', fallback=''),
|
||||||
|
'grade_url': parser.get('login', 'GRADE_URL', fallback=''),
|
||||||
|
|
||||||
|
# 邮件配置
|
||||||
|
'sender_email': parser.get('email', 'SENDER_EMAIL', fallback=''),
|
||||||
|
'sender_password': parser.get('email', 'SENDER_PASSWORD', fallback=''),
|
||||||
|
'receiver_email': parser.get('email', 'RECEIVER_EMAIL', fallback=''),
|
||||||
|
'smtp_server': parser.get('email', 'SMTP_SERVER', fallback='smtp.163.com'),
|
||||||
|
'smtp_port': parser.getint('email', 'SMTP_PORT', fallback=465),
|
||||||
|
|
||||||
|
# 监控配置
|
||||||
|
'check_interval': parser.getint('monitor', 'CHECK_INTERVAL', fallback=60),
|
||||||
|
'request_delay': parser.getint('monitor', 'REQUEST_DELAY', fallback=5),
|
||||||
|
'max_retries': parser.getint('monitor', 'MAX_RETRIES', fallback=3),
|
||||||
|
'retry_delay': parser.getint('monitor', 'RETRY_DELAY', fallback=10),
|
||||||
|
}
|
||||||
|
|
||||||
|
# 验证必要配置
|
||||||
|
if not config['username'] or not config['password']:
|
||||||
|
logger.error("请在配置文件中设置 USERNAME 和 PASSWORD")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not config['sender_email'] or not config['sender_password'] or not config['receiver_email']:
|
||||||
|
logger.error("请在配置文件中设置邮件相关配置")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
logger.info("配置文件加载成功")
|
||||||
|
return config
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"配置文件格式错误: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def login(self) -> bool:
|
||||||
|
"""登录统一身份认证"""
|
||||||
|
logger.info("开始登录统一身份认证...")
|
||||||
|
|
||||||
|
for attempt in range(self.config['max_retries']):
|
||||||
|
try:
|
||||||
|
logger.info(f"登录尝试 {attempt + 1}/{self.config['max_retries']}")
|
||||||
|
|
||||||
|
# 第一步:访问成绩页面,会重定向到登录页
|
||||||
|
logger.info("步骤1:访问成绩页面,获取登录页面...")
|
||||||
|
response = self.session.get(
|
||||||
|
self.config['grade_url'],
|
||||||
|
timeout=30,
|
||||||
|
allow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 随机等待5-10秒,模拟真实用户
|
||||||
|
wait_time = random.uniform(5, 10)
|
||||||
|
logger.info(f"第一次访问完成,等待 {wait_time:.1f} 秒(模拟用户行为)...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
# 检查是否需要登录
|
||||||
|
if '统一身份认证' in response.text or 'tpass/login' in response.url:
|
||||||
|
logger.info("需要登录,提取登录表单信息...")
|
||||||
|
|
||||||
|
# 提取lt值
|
||||||
|
import re
|
||||||
|
lt_match = re.search(r'name="lt"\s+value="([^"]+)"', response.text)
|
||||||
|
execution_match = re.search(r'name="execution"\s+value="([^"]+)"', response.text)
|
||||||
|
|
||||||
|
lt = lt_match.group(1) if lt_match else ''
|
||||||
|
execution = execution_match.group(1) if execution_match else 'e1s1'
|
||||||
|
|
||||||
|
if not lt:
|
||||||
|
logger.warning("无法提取lt值,使用默认值")
|
||||||
|
lt = '00000000'
|
||||||
|
|
||||||
|
logger.info(f"提取到 lt={lt}, execution={execution}")
|
||||||
|
|
||||||
|
# 获取登录提交URL
|
||||||
|
login_submit_url = response.url.split('?')[0]
|
||||||
|
|
||||||
|
# 准备登录数据(按照login_neu.js的逻辑)
|
||||||
|
username = self.config['username']
|
||||||
|
password = self.config['password']
|
||||||
|
|
||||||
|
login_data = {
|
||||||
|
'rsa': username + password + lt,
|
||||||
|
'ul': str(len(username)),
|
||||||
|
'pl': str(len(password)),
|
||||||
|
'sl': '0',
|
||||||
|
'lt': lt,
|
||||||
|
'execution': execution,
|
||||||
|
'_eventId': 'submit'
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(f"步骤2:提交登录表单到 {login_submit_url}")
|
||||||
|
# 随机等待5-10秒后提交,模拟用户填写表单
|
||||||
|
wait_time = random.uniform(5, 10)
|
||||||
|
logger.info(f"等待 {wait_time:.1f} 秒后提交登录(模拟填写表单)...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
response = self.session.post(
|
||||||
|
login_submit_url,
|
||||||
|
data=login_data,
|
||||||
|
timeout=30,
|
||||||
|
allow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 登录后等待5-10秒,让服务器处理
|
||||||
|
wait_time = random.uniform(5, 10)
|
||||||
|
logger.info(f"登录表单已提交,等待 {wait_time:.1f} 秒处理...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
# 第三步:再次访问成绩页面验证登录
|
||||||
|
logger.info("步骤3:验证登录状态,访问成绩页面...")
|
||||||
|
# 随机等待5-10秒,模拟用户操作
|
||||||
|
wait_time = random.uniform(5, 10)
|
||||||
|
logger.info(f"等待 {wait_time:.1f} 秒后访问成绩页面(模拟用户操作)...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
response = self.session.get(
|
||||||
|
self.config['grade_url'],
|
||||||
|
timeout=30,
|
||||||
|
allow_redirects=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查是否成功(不再是登录页面,且不是"请不要过快点击")
|
||||||
|
if '统一身份认证' not in response.text and 'tpass/login' not in response.url and '请不要过快点击' not in response.text:
|
||||||
|
# 进一步验证:检查是否包含成绩相关内容
|
||||||
|
if '学年学期' in response.text or '课程名称' in response.text or '成绩' in response.text:
|
||||||
|
logger.info("登录成功!成功访问成绩页面")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.warning(f"页面内容异常,可能不是成绩页面")
|
||||||
|
logger.info(f"当前URL: {response.url}")
|
||||||
|
else:
|
||||||
|
if '请不要过快点击' in response.text:
|
||||||
|
wait_time = 30 + (attempt * 15) # 大幅递增等待时间:30秒、45秒、60秒...
|
||||||
|
logger.warning(f"⚠️ 请求过快被拦截!等待 {wait_time} 秒后重试...")
|
||||||
|
logger.info("建议:1) 检查 config.ini 中的 CHECK_INTERVAL 是否 >= 120 秒")
|
||||||
|
logger.info(" 2) 避免频繁手动测试,建议间隔至少5分钟")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
continue # 直接进入下一次循环,不再额外sleep
|
||||||
|
else:
|
||||||
|
logger.warning(f"登录失败,尝试重试 {attempt + 1}/{self.config['max_retries']}")
|
||||||
|
logger.info(f"当前URL: {response.url}")
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.warning(f"登录请求异常: {e},尝试重试 {attempt + 1}/{self.config['max_retries']}")
|
||||||
|
|
||||||
|
if '请不要过快点击' not in response.text:
|
||||||
|
logger.info(f"等待 {self.config['retry_delay']} 秒后重试...")
|
||||||
|
time.sleep(self.config['retry_delay'])
|
||||||
|
|
||||||
|
logger.error("登录失败,已达到最大重试次数")
|
||||||
|
logger.info("建议:1) 检查用户名密码是否正确")
|
||||||
|
logger.info(" 2) 手动在浏览器中登录一次,确认账号正常")
|
||||||
|
logger.info(" 3) 查看 .debug_response.html 了解实际响应内容")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def fetch_grade_page(self) -> Optional[str]:
|
||||||
|
"""获取成绩页面"""
|
||||||
|
logger.info("获取成绩页面...")
|
||||||
|
|
||||||
|
for attempt in range(self.config['max_retries']):
|
||||||
|
try:
|
||||||
|
# 在每次请求前增加延迟,避免触发"请不要过快点击"
|
||||||
|
if attempt > 0:
|
||||||
|
wait_time = random.uniform(5, 8)
|
||||||
|
logger.info(f"等待 {wait_time:.1f} 秒后发送请求...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
response = self.session.get(
|
||||||
|
self.config['grade_url'],
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查是否触发"请不要过快点击"
|
||||||
|
if '请不要过快点击' in response.text:
|
||||||
|
wait_time = 30 + (attempt * 15)
|
||||||
|
logger.warning(f"⚠️ 请求过快被拦截!等待 {wait_time} 秒后重试...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.text
|
||||||
|
elif response.status_code in [302, 401]:
|
||||||
|
logger.warning("会话已过期,重新登录...")
|
||||||
|
if self.login():
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
logger.warning(f"获取成绩页面失败 (HTTP {response.status_code}),尝试重试 {attempt + 1}/{self.config['max_retries']}")
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.warning(f"获取成绩页面异常: {e},尝试重试 {attempt + 1}/{self.config['max_retries']}")
|
||||||
|
|
||||||
|
time.sleep(self.config['retry_delay'])
|
||||||
|
|
||||||
|
logger.error("获取成绩页面失败,已达到最大重试次数")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_grade_info(self, html: str) -> str:
|
||||||
|
"""提取成绩信息的关键内容"""
|
||||||
|
try:
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
soup = BeautifulSoup(html, 'html.parser')
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
# 提取总平均绩点
|
||||||
|
gpa_div = soup.find('div', string=lambda x: x and '总平均绩点' in x)
|
||||||
|
if gpa_div:
|
||||||
|
gpa_text = gpa_div.get_text(strip=True)
|
||||||
|
result.append("=" * 60)
|
||||||
|
result.append(gpa_text)
|
||||||
|
result.append("=" * 60)
|
||||||
|
result.append("")
|
||||||
|
|
||||||
|
# 提取成绩表格
|
||||||
|
table = soup.find('table', {'class': 'gridtable'})
|
||||||
|
if table:
|
||||||
|
# 提取表头
|
||||||
|
thead = table.find('thead')
|
||||||
|
if thead:
|
||||||
|
headers = [th.get_text(strip=True) for th in thead.find_all('th')]
|
||||||
|
result.append(' | '.join(headers))
|
||||||
|
result.append("-" * 120)
|
||||||
|
|
||||||
|
# 提取每一行成绩
|
||||||
|
tbody = table.find('tbody')
|
||||||
|
if tbody:
|
||||||
|
for row in tbody.find_all('tr'):
|
||||||
|
cells = row.find_all('td')
|
||||||
|
if cells:
|
||||||
|
row_data = []
|
||||||
|
for cell in cells:
|
||||||
|
# 提取文本,包括sup标签的内容
|
||||||
|
text = cell.get_text(strip=True, separator=' ')
|
||||||
|
row_data.append(text)
|
||||||
|
result.append(' | '.join(row_data))
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
logger.warning("未能提取到成绩信息,返回原始文本")
|
||||||
|
return self._fallback_extract(html)
|
||||||
|
|
||||||
|
return '\n'.join(result)
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
logger.warning("未安装 beautifulsoup4,使用简单文本提取")
|
||||||
|
return self._fallback_extract(html)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"提取成绩信息时出错: {e}")
|
||||||
|
return self._fallback_extract(html)
|
||||||
|
|
||||||
|
def _fallback_extract(self, html: str) -> str:
|
||||||
|
"""备用的简单文本提取方法"""
|
||||||
|
import re
|
||||||
|
# 移除script和style标签
|
||||||
|
html = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.DOTALL)
|
||||||
|
html = re.sub(r'<style[^>]*>.*?</style>', '', html, flags=re.DOTALL)
|
||||||
|
return html
|
||||||
|
|
||||||
|
def parse_courses(self, grade_text: str) -> list:
|
||||||
|
"""解析成绩文本,提取课程列表"""
|
||||||
|
courses = []
|
||||||
|
lines = grade_text.split('\n')
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
# 跳过标题行、分隔行和空行
|
||||||
|
stripped = line.strip()
|
||||||
|
# 跳过空行、纯分隔符行、标题行
|
||||||
|
if not stripped or stripped.startswith('=') or stripped.startswith('-') or '学年学期' in line or '总平均绩点' in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 解析课程行
|
||||||
|
parts = [p.strip() for p in line.split('|')]
|
||||||
|
if len(parts) >= 4: # 至少有学期、代码、序号、名称
|
||||||
|
# 提取关键信息:学期 + 课程代码 + 课程名称
|
||||||
|
semester = parts[0] if len(parts) > 0 else ''
|
||||||
|
course_code = parts[1] if len(parts) > 1 else ''
|
||||||
|
course_name = parts[3] if len(parts) > 3 else ''
|
||||||
|
|
||||||
|
if course_code and course_name:
|
||||||
|
course_key = f"{semester}|{course_code}|{course_name}"
|
||||||
|
courses.append(course_key)
|
||||||
|
|
||||||
|
return courses
|
||||||
|
|
||||||
|
def calculate_hash(self, content: str) -> str:
|
||||||
|
"""计算内容的哈希值"""
|
||||||
|
return hashlib.md5(content.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def check_grade_changes(self, current_content: str, current_html: str = None) -> bool:
|
||||||
|
"""检查成绩是否有变化"""
|
||||||
|
# 保存当前内容(供用户查看)
|
||||||
|
self.last_grade_content_file.write_text(current_content, encoding='utf-8')
|
||||||
|
if current_html:
|
||||||
|
self.last_grade_html_file.write_text(current_html, encoding='utf-8')
|
||||||
|
|
||||||
|
# 解析当前课程列表
|
||||||
|
current_courses = self.parse_courses(current_content)
|
||||||
|
|
||||||
|
if not self.last_grade_file.exists():
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info("首次运行,保存当前成绩状态")
|
||||||
|
logger.info(f"当前共有 {len(current_courses)} 门课程")
|
||||||
|
logger.info(f"原始HTML已保存到: {self.last_grade_html_file}")
|
||||||
|
logger.info(f"提取的成绩内容已保存到: {self.last_grade_content_file}")
|
||||||
|
logger.info("请检查这些文件确认内容是否正确!")
|
||||||
|
logger.info("=" * 60)
|
||||||
|
|
||||||
|
# 保存课程列表
|
||||||
|
courses_file = self.script_dir / '.last_courses.txt'
|
||||||
|
courses_file.write_text('\n'.join(current_courses), encoding='utf-8')
|
||||||
|
|
||||||
|
# 计算哈希
|
||||||
|
current_hash = self.calculate_hash(current_content)
|
||||||
|
self.last_grade_file.write_text(current_hash, encoding='utf-8')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 读取上次的课程列表
|
||||||
|
courses_file = self.script_dir / '.last_courses.txt'
|
||||||
|
if courses_file.exists():
|
||||||
|
last_courses = courses_file.read_text(encoding='utf-8').strip().split('\n')
|
||||||
|
else:
|
||||||
|
last_courses = []
|
||||||
|
|
||||||
|
# 比较课程数量和内容,检测新增课程
|
||||||
|
new_courses = [c for c in current_courses if c not in last_courses]
|
||||||
|
# 检测课程成绩变化(课程存在但内容不同)
|
||||||
|
current_hash = self.calculate_hash(current_content)
|
||||||
|
last_hash = self.last_grade_file.read_text(encoding='utf-8').strip() if self.last_grade_file.exists() else ''
|
||||||
|
content_changed = current_hash != last_hash
|
||||||
|
|
||||||
|
if new_courses:
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info(f"✓ 检测到新增课程成绩!共 {len(new_courses)} 门")
|
||||||
|
logger.info("-" * 60)
|
||||||
|
for idx, course in enumerate(new_courses, 1):
|
||||||
|
parts = course.split('|')
|
||||||
|
if len(parts) >= 3:
|
||||||
|
semester = parts[0] if parts[0] else '未知学期'
|
||||||
|
course_name = parts[2] if len(parts) > 2 else '未知课程'
|
||||||
|
course_code = parts[1] if len(parts) > 1 else ''
|
||||||
|
logger.info(f" {idx}. [{semester}] {course_name} ({course_code})")
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info(f"详细内容已保存到: {self.last_grade_content_file}")
|
||||||
|
|
||||||
|
# 更新保存的课程列表和哈希
|
||||||
|
courses_file.write_text('\n'.join(current_courses), encoding='utf-8')
|
||||||
|
self.last_grade_file.write_text(current_hash, encoding='utf-8')
|
||||||
|
|
||||||
|
return True
|
||||||
|
elif content_changed and current_courses:
|
||||||
|
# 课程数量没变,但内容变了(可能是成绩更新)
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info("✓ 检测到成绩内容发生变化(可能是成绩更新)")
|
||||||
|
logger.info(f"当前共有 {len(current_courses)} 门课程")
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info(f"详细内容已保存到: {self.last_grade_content_file}")
|
||||||
|
|
||||||
|
# 更新哈希值
|
||||||
|
self.last_grade_file.write_text(current_hash, encoding='utf-8')
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.info(f"成绩无变化(共 {len(current_courses)} 门课程)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_email_notification(self, new_courses: list = None) -> bool:
|
||||||
|
"""发送邮件通知"""
|
||||||
|
logger.info("准备发送邮件通知...")
|
||||||
|
|
||||||
|
if new_courses:
|
||||||
|
subject = f"【成绩更新通知】新增 {len(new_courses)} 门课程成绩"
|
||||||
|
courses_text = "\n".join([f" - {course.split('|')[0]} {course.split('|')[2]}" for course in new_courses if len(course.split('|')) >= 3])
|
||||||
|
body = f"""尊敬的用户:
|
||||||
|
|
||||||
|
您好!系统检测到成绩页面已更新。
|
||||||
|
|
||||||
|
新增课程(共{len(new_courses)}门):
|
||||||
|
{courses_text}
|
||||||
|
|
||||||
|
请及时查看详细成绩。
|
||||||
|
|
||||||
|
查看地址:{self.config['grade_url']}
|
||||||
|
|
||||||
|
检测时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||||
|
|
||||||
|
此邮件由系统自动发送,请勿回复。"""
|
||||||
|
else:
|
||||||
|
subject = "【成绩更新通知】成绩页面已更新"
|
||||||
|
body = f"""尊敬的用户:
|
||||||
|
|
||||||
|
您好!系统检测到成绩页面已更新。
|
||||||
|
|
||||||
|
请及时查看详细成绩。
|
||||||
|
|
||||||
|
查看地址:{self.config['grade_url']}
|
||||||
|
|
||||||
|
检测时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||||
|
|
||||||
|
此邮件由系统自动发送,请勿回复。"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = MIMEMultipart("alternative")
|
||||||
|
message["Subject"] = subject
|
||||||
|
message["From"] = self.config['sender_email']
|
||||||
|
message["To"] = self.config['receiver_email']
|
||||||
|
|
||||||
|
part = MIMEText(body, "plain", "utf-8")
|
||||||
|
message.attach(part)
|
||||||
|
|
||||||
|
context = ssl.create_default_context()
|
||||||
|
|
||||||
|
with smtplib.SMTP_SSL(
|
||||||
|
self.config['smtp_server'],
|
||||||
|
self.config['smtp_port'],
|
||||||
|
context=context
|
||||||
|
) as server:
|
||||||
|
server.login(
|
||||||
|
self.config['sender_email'],
|
||||||
|
self.config['sender_password']
|
||||||
|
)
|
||||||
|
server.sendmail(
|
||||||
|
self.config['sender_email'],
|
||||||
|
self.config['receiver_email'],
|
||||||
|
message.as_string()
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("邮件发送成功")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"邮件发送失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_error_notification(self, error_msg: str) -> bool:
|
||||||
|
"""发送错误通知邮件"""
|
||||||
|
logger.info("准备发送错误通知邮件...")
|
||||||
|
|
||||||
|
subject = "【成绩监控系统】程序运行出错"
|
||||||
|
body = f"""尊敬的用户:
|
||||||
|
|
||||||
|
成绩监控系统在运行过程中遇到错误:
|
||||||
|
|
||||||
|
错误信息:
|
||||||
|
{error_msg}
|
||||||
|
|
||||||
|
发生时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||||
|
|
||||||
|
请检查系统状态或查看日志文件 monitor.log 了解详情。
|
||||||
|
|
||||||
|
此邮件由系统自动发送,请勿回复。"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = MIMEMultipart("alternative")
|
||||||
|
message["Subject"] = subject
|
||||||
|
message["From"] = self.config['sender_email']
|
||||||
|
message["To"] = self.config['receiver_email']
|
||||||
|
|
||||||
|
part = MIMEText(body, "plain", "utf-8")
|
||||||
|
message.attach(part)
|
||||||
|
|
||||||
|
context = ssl.create_default_context()
|
||||||
|
|
||||||
|
with smtplib.SMTP_SSL(
|
||||||
|
self.config['smtp_server'],
|
||||||
|
self.config['smtp_port'],
|
||||||
|
context=context
|
||||||
|
) as server:
|
||||||
|
server.login(
|
||||||
|
self.config['sender_email'],
|
||||||
|
self.config['sender_password']
|
||||||
|
)
|
||||||
|
server.sendmail(
|
||||||
|
self.config['sender_email'],
|
||||||
|
self.config['receiver_email'],
|
||||||
|
message.as_string()
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("错误通知邮件发送成功")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"错误通知邮件发送失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_fetch(self):
|
||||||
|
"""测试模式:获取一次成绩并显示内容"""
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info("测试模式:获取成绩页面")
|
||||||
|
logger.info("=" * 60)
|
||||||
|
|
||||||
|
# 登录
|
||||||
|
if not self.login():
|
||||||
|
logger.error("登录失败,无法获取成绩页面")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取成绩页面
|
||||||
|
logger.info("正在获取成绩页面...")
|
||||||
|
grade_html = self.fetch_grade_page()
|
||||||
|
if not grade_html:
|
||||||
|
logger.error("获取成绩页面失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 保存原始HTML
|
||||||
|
self.last_grade_html_file.write_text(grade_html, encoding='utf-8')
|
||||||
|
logger.info(f"✓ 原始HTML已保存到: {self.last_grade_html_file}")
|
||||||
|
|
||||||
|
# 提取成绩信息
|
||||||
|
grade_info = self.extract_grade_info(grade_html)
|
||||||
|
self.last_grade_content_file.write_text(grade_info, encoding='utf-8')
|
||||||
|
logger.info(f"✓ 提取的成绩内容已保存到: {self.last_grade_content_file}")
|
||||||
|
|
||||||
|
# 显示前500个字符
|
||||||
|
logger.info("=" * 60)
|
||||||
|
logger.info("提取的成绩内容预览(前500字符):")
|
||||||
|
logger.info("-" * 60)
|
||||||
|
print(grade_info[:500])
|
||||||
|
if len(grade_info) > 500:
|
||||||
|
logger.info(f"\n... (还有 {len(grade_info) - 500} 个字符)")
|
||||||
|
logger.info("-" * 60)
|
||||||
|
logger.info(f"完整内容请查看文件: {self.last_grade_content_file}")
|
||||||
|
logger.info("=" * 60)
|
||||||
|
|
||||||
|
def monitor_loop(self):
|
||||||
|
"""主监控循环"""
|
||||||
|
logger.info("=" * 50)
|
||||||
|
logger.info("成绩监控程序启动")
|
||||||
|
logger.info(f"检查间隔: {self.config['check_interval']} 秒")
|
||||||
|
logger.info("按 Ctrl+C 停止监控")
|
||||||
|
logger.info("=" * 50)
|
||||||
|
|
||||||
|
# 首次登录
|
||||||
|
if not self.login():
|
||||||
|
logger.error("初始登录失败,程序退出")
|
||||||
|
self.send_error_notification("初始登录失败,无法访问成绩页面")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 登录成功后等待一段时间再进行首次检查,避免触发"请不要过快点击"
|
||||||
|
wait_time = random.uniform(5, 10)
|
||||||
|
logger.info(f"登录成功,等待 {wait_time:.1f} 秒后开始首次检查(避免请求过快)...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
consecutive_errors = 0
|
||||||
|
max_consecutive_errors = 5
|
||||||
|
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
logger.info("-" * 50)
|
||||||
|
logger.info("开始新一轮检查")
|
||||||
|
|
||||||
|
# 获取成绩页面
|
||||||
|
grade_html = self.fetch_grade_page()
|
||||||
|
if not grade_html:
|
||||||
|
consecutive_errors += 1
|
||||||
|
logger.error(f"获取成绩页面失败,等待下次检查 (连续失败: {consecutive_errors}/{max_consecutive_errors})")
|
||||||
|
|
||||||
|
if consecutive_errors >= max_consecutive_errors:
|
||||||
|
error_msg = f"连续 {consecutive_errors} 次获取成绩页面失败"
|
||||||
|
logger.error(error_msg)
|
||||||
|
self.send_error_notification(error_msg)
|
||||||
|
consecutive_errors = 0 # 重置计数,避免重复发送
|
||||||
|
|
||||||
|
time.sleep(self.config['check_interval'])
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 提取成绩信息
|
||||||
|
grade_info = self.extract_grade_info(grade_html)
|
||||||
|
|
||||||
|
# 检查变化
|
||||||
|
if self.check_grade_changes(grade_info, grade_html):
|
||||||
|
# 获取新增课程列表
|
||||||
|
courses_file = self.script_dir / '.last_courses.txt'
|
||||||
|
if courses_file.exists():
|
||||||
|
current_courses = self.parse_courses(grade_info)
|
||||||
|
# 简单通知有新课程
|
||||||
|
self.send_email_notification(current_courses[:3]) # 只显示前3门
|
||||||
|
else:
|
||||||
|
self.send_email_notification()
|
||||||
|
|
||||||
|
# 成功执行,重置错误计数
|
||||||
|
consecutive_errors = 0
|
||||||
|
|
||||||
|
logger.info(f"等待 {self.config['check_interval']} 秒后进行下次检查...")
|
||||||
|
time.sleep(self.config['check_interval'])
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
consecutive_errors += 1
|
||||||
|
error_msg = f"监控循环发生异常: {e}"
|
||||||
|
logger.error(error_msg)
|
||||||
|
|
||||||
|
if consecutive_errors >= max_consecutive_errors:
|
||||||
|
logger.error(f"连续 {consecutive_errors} 次出现异常")
|
||||||
|
self.send_error_notification(f"{error_msg}\n\n连续失败次数: {consecutive_errors}")
|
||||||
|
consecutive_errors = 0
|
||||||
|
|
||||||
|
time.sleep(self.config['retry_delay'])
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""运行监控"""
|
||||||
|
try:
|
||||||
|
if self.test_mode:
|
||||||
|
self.test_fetch()
|
||||||
|
else:
|
||||||
|
self.monitor_loop()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.info("收到键盘中断信号,程序退出")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"程序异常退出: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""主函数"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description='成绩监控系统 - 自动监控成绩变化并发送邮件通知',
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog="""
|
||||||
|
使用示例:
|
||||||
|
# 正常监控模式
|
||||||
|
python3 monitor.py
|
||||||
|
|
||||||
|
# 测试模式(获取一次成绩并显示内容)
|
||||||
|
python3 monitor.py --test
|
||||||
|
|
||||||
|
# 查看保存的成绩内容
|
||||||
|
cat .last_grade_content.txt
|
||||||
|
|
||||||
|
# 查看原始HTML
|
||||||
|
cat .last_grade_page.html
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--test',
|
||||||
|
action='store_true',
|
||||||
|
help='测试模式:仅获取一次成绩页面并显示内容,不进行监控'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--config',
|
||||||
|
default='config.ini',
|
||||||
|
help='配置文件路径(默认:config.ini)'
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
monitor = GradeMonitor(config_file=args.config, test_mode=args.test)
|
||||||
|
monitor.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
157
readme.md
Normal file
157
readme.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# 成绩监控系统
|
||||||
|
|
||||||
|
自动监控东北大学成绩系统,当有新课程成绩发布时自动发送邮件通知。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 安装依赖(WSL环境)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/e/50425/Documents/Github/GPA_Monitoring
|
||||||
|
chmod +x setup_python.sh
|
||||||
|
./setup_python.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 配置
|
||||||
|
|
||||||
|
编辑 `config.ini` 文件,填入你的信息:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[login]
|
||||||
|
USERNAME = 你的学号
|
||||||
|
PASSWORD = 你的密码
|
||||||
|
|
||||||
|
[email]
|
||||||
|
SENDER_EMAIL = your_email@163.com
|
||||||
|
SENDER_PASSWORD = SMTP授权码(不是邮箱密码!)
|
||||||
|
RECEIVER_EMAIL = 接收通知的邮箱
|
||||||
|
```
|
||||||
|
|
||||||
|
**获取163邮箱SMTP授权码:**
|
||||||
|
1. 登录163邮箱网页版
|
||||||
|
2. 设置 → POP3/SMTP/IMAP
|
||||||
|
3. 开启"IMAP/SMTP服务"
|
||||||
|
4. 发送短信获取授权码
|
||||||
|
|
||||||
|
### 3. 测试运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source venv/bin/activate
|
||||||
|
python3 monitor.py --test
|
||||||
|
```
|
||||||
|
|
||||||
|
检查生成的文件确认内容正确:
|
||||||
|
- `.last_grade_content.txt` - 提取的成绩内容(整齐格式)
|
||||||
|
- `.last_grade_page.html` - 原始HTML页面
|
||||||
|
|
||||||
|
### 4. 正式运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用tmux后台运行(推荐)
|
||||||
|
tmux new -s grade_monitor
|
||||||
|
source venv/bin/activate
|
||||||
|
python3 monitor.py
|
||||||
|
# 按 Ctrl+B 然后按 D 离开会话
|
||||||
|
```
|
||||||
|
|
||||||
|
## 功能特点
|
||||||
|
|
||||||
|
✅ **智能检测** - 检测新增课程(而非简单的页面变化)
|
||||||
|
✅ **详细通知** - 邮件包含新增课程名称
|
||||||
|
✅ **格式化输出** - 成绩保存为整齐的文本格式,包含总绩点
|
||||||
|
✅ **错误通知** - 连续失败5次自动发送错误通知邮件
|
||||||
|
✅ **稳定可靠** - 自动重试、会话管理、异常处理
|
||||||
|
✅ **防封禁** - 请求间隔控制,避免过快访问
|
||||||
|
|
||||||
|
## 文件说明
|
||||||
|
|
||||||
|
**主要文件:**
|
||||||
|
- `monitor.py` - 主程序
|
||||||
|
- `config.ini` - 配置文件
|
||||||
|
- `requirements.txt` - Python依赖
|
||||||
|
- `setup_python.sh` - 安装脚本
|
||||||
|
|
||||||
|
**自动生成的文件:**
|
||||||
|
- `.last_grade_content.txt` - 当前成绩内容(格式化)
|
||||||
|
- `.last_grade_page.html` - 原始HTML页面
|
||||||
|
- `.last_courses.txt` - 课程列表(用于检测新增)
|
||||||
|
- `.last_grade_hash.txt` - 内容哈希(用于检测变化)
|
||||||
|
- `monitor.log` - 运行日志
|
||||||
|
|
||||||
|
## 命令说明
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看帮助
|
||||||
|
python3 monitor.py --help
|
||||||
|
|
||||||
|
# 测试模式(获取一次成绩)
|
||||||
|
python3 monitor.py --test
|
||||||
|
|
||||||
|
# 正常监控模式
|
||||||
|
python3 monitor.py
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
tail -f monitor.log
|
||||||
|
|
||||||
|
# 查看成绩
|
||||||
|
cat .last_grade_content.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## 监控原理
|
||||||
|
|
||||||
|
1. **定时访问** - 每60秒(可配置)访问一次成绩页面
|
||||||
|
2. **解析成绩** - 提取总绩点和每门课程信息
|
||||||
|
3. **智能对比** - 对比课程列表,检测新增课程
|
||||||
|
4. **邮件通知** - 发现新课程立即发送邮件
|
||||||
|
|
||||||
|
## 故障排查
|
||||||
|
|
||||||
|
### 问题:登录失败
|
||||||
|
|
||||||
|
**解决:**
|
||||||
|
1. 检查 `config.ini` 中的用户名密码
|
||||||
|
2. 手动登录网页版确认账号正常
|
||||||
|
3. 查看 `.debug_response.html` 了解实际响应
|
||||||
|
|
||||||
|
### 问题:邮件发送失败
|
||||||
|
|
||||||
|
**解决:**
|
||||||
|
1. 确认使用SMTP授权码(不是邮箱密码)
|
||||||
|
2. 检查163邮箱是否开启SMTP服务
|
||||||
|
3. 查看 `monitor.log` 了解错误详情
|
||||||
|
|
||||||
|
### 问题:提示"请不要过快点击"
|
||||||
|
|
||||||
|
**解决:**
|
||||||
|
- 程序会自动增加等待时间
|
||||||
|
- 可在 `config.ini` 中增大 `CHECK_INTERVAL`
|
||||||
|
|
||||||
|
## 配置说明
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[monitor]
|
||||||
|
CHECK_INTERVAL = 60 # 检查间隔(秒)
|
||||||
|
REQUEST_DELAY = 5 # 请求延迟(秒)
|
||||||
|
MAX_RETRIES = 3 # 重试次数
|
||||||
|
RETRY_DELAY = 10 # 重试间隔(秒)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 停止监控
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 前台运行时按 Ctrl+C
|
||||||
|
|
||||||
|
# tmux中
|
||||||
|
tmux attach -t grade_monitor
|
||||||
|
# 然后按 Ctrl+C
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
⚠️ **请勿过于频繁地检查** - 建议检查间隔不小于60秒
|
||||||
|
⚠️ **保护好配置文件** - 包含账号密码,不要上传到公共仓库
|
||||||
|
⚠️ **授权码不是密码** - 163邮箱需要单独获取SMTP授权码
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
requests>=2.31.0
|
||||||
|
beautifulsoup4>=4.12.0
|
||||||
91
setup_python.sh
Normal file
91
setup_python.sh
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
#############################################
|
||||||
|
# Python版本依赖安装脚本
|
||||||
|
# 适用于 Debian 12 (WSL)
|
||||||
|
#############################################
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "========== 成绩监控系统 (Python版本) - 依赖安装 =========="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
|
||||||
|
# 更新软件包列表
|
||||||
|
echo "1. 更新软件包列表..."
|
||||||
|
sudo apt-get update
|
||||||
|
|
||||||
|
# 安装Python3和pip
|
||||||
|
echo ""
|
||||||
|
echo "2. 安装Python3和pip..."
|
||||||
|
sudo apt-get install -y python3 python3-pip python3-venv
|
||||||
|
|
||||||
|
# 创建虚拟环境(可选但推荐)
|
||||||
|
echo ""
|
||||||
|
echo "3. 创建Python虚拟环境..."
|
||||||
|
if [[ ! -d "venv" ]]; then
|
||||||
|
python3 -m venv venv
|
||||||
|
echo "虚拟环境创建成功"
|
||||||
|
else
|
||||||
|
echo "虚拟环境已存在,跳过创建"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 激活虚拟环境并安装依赖
|
||||||
|
echo ""
|
||||||
|
echo "4. 安装Python依赖包..."
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "5. 重命名配置文件..."
|
||||||
|
if [[ -f "config_new.ini" ]] && [[ ! -f "config.ini" ]]; then
|
||||||
|
mv config_new.ini config.ini
|
||||||
|
echo "配置文件已重命名为 config.ini"
|
||||||
|
elif [[ -f "config_new.ini" ]]; then
|
||||||
|
echo "config.ini 已存在,保留原配置"
|
||||||
|
echo "新配置保存在 config_new.ini,请手动合并"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 给脚本添加执行权限
|
||||||
|
echo ""
|
||||||
|
echo "6. 添加执行权限..."
|
||||||
|
chmod +x monitor.py
|
||||||
|
|
||||||
|
# 验证安装
|
||||||
|
echo ""
|
||||||
|
echo "========== 验证安装 =========="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
python3 --version
|
||||||
|
pip --version
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "已安装的Python包:"
|
||||||
|
pip list | grep -E "requests|beautifulsoup4"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "========== 所有依赖安装成功! =========="
|
||||||
|
echo ""
|
||||||
|
echo "下一步:"
|
||||||
|
echo "1. 确认 config.ini 文件中的配置正确"
|
||||||
|
echo "2. 运行监控程序:"
|
||||||
|
echo " 方式1(使用虚拟环境 - 推荐):"
|
||||||
|
echo " source venv/bin/activate"
|
||||||
|
echo " python3 monitor.py"
|
||||||
|
echo ""
|
||||||
|
echo " 方式2(直接运行):"
|
||||||
|
echo " ./monitor.py"
|
||||||
|
echo ""
|
||||||
|
echo " 方式3(后台运行):"
|
||||||
|
echo " nohup python3 monitor.py > /dev/null 2>&1 &"
|
||||||
|
echo ""
|
||||||
|
echo " 方式4(使用tmux - 推荐):"
|
||||||
|
echo " tmux new -s grade_monitor"
|
||||||
|
echo " source venv/bin/activate"
|
||||||
|
echo " python3 monitor.py"
|
||||||
|
echo " # 按 Ctrl+B 然后按 D 离开会话"
|
||||||
|
echo ""
|
||||||
|
echo "提示:"
|
||||||
|
echo "- 163邮箱需要在邮箱设置中开启SMTP服务并获取授权码"
|
||||||
|
echo "- 查看日志: tail -f monitor.log"
|
||||||
144
常见问题解决.md
Normal file
144
常见问题解决.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# 常见问题解决方案
|
||||||
|
|
||||||
|
## ⚠️ "请不要过快点击" 错误
|
||||||
|
|
||||||
|
### 问题原因
|
||||||
|
学校WebVPN系统有严格的访问频率限制,短时间内多次访问会被拦截。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
|
||||||
|
#### 1. 停止手动测试至少5分钟
|
||||||
|
```bash
|
||||||
|
# 如果在服务器上运行,请先停止程序
|
||||||
|
ps aux | grep monitor.py
|
||||||
|
kill <进程ID>
|
||||||
|
|
||||||
|
# 等待至少5分钟后再重新测试
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 修改配置文件增加间隔
|
||||||
|
编辑 `config.ini`:
|
||||||
|
```ini
|
||||||
|
[monitor]
|
||||||
|
# 将检查间隔改为5分钟(300秒)或更长
|
||||||
|
CHECK_INTERVAL = 300
|
||||||
|
|
||||||
|
# 增加请求延迟
|
||||||
|
REQUEST_DELAY = 15
|
||||||
|
|
||||||
|
# 增加重试间隔
|
||||||
|
RETRY_DELAY = 60
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 使用正确的测试方式
|
||||||
|
```bash
|
||||||
|
# 测试前确保距离上次测试至少5分钟
|
||||||
|
python3 monitor.py --test
|
||||||
|
|
||||||
|
# 如果还是被拦截,等10分钟后再试
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 部署后不要频繁测试
|
||||||
|
```bash
|
||||||
|
# 部署systemd服务后,让它自动运行
|
||||||
|
sudo systemctl start grade-monitor
|
||||||
|
|
||||||
|
# 只通过日志查看运行情况
|
||||||
|
tail -f monitor.log
|
||||||
|
|
||||||
|
# 不要反复启停服务或手动测试
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 如何判断系统正常工作
|
||||||
|
|
||||||
|
### 方法1:查看日志
|
||||||
|
```bash
|
||||||
|
tail -f ~/grade_monitor/monitor.log
|
||||||
|
```
|
||||||
|
|
||||||
|
正常日志应该类似:
|
||||||
|
```
|
||||||
|
2026-01-17 10:00:00 - INFO - 开始检查成绩变化...
|
||||||
|
2026-01-17 10:00:05 - INFO - 登录成功!
|
||||||
|
2026-01-17 10:00:10 - INFO - 成功获取成绩页面
|
||||||
|
2026-01-17 10:00:12 - INFO - 未发现新课程
|
||||||
|
2026-01-17 10:02:00 - INFO - 等待下次检查...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方法2:查看保存的成绩文件
|
||||||
|
```bash
|
||||||
|
cat ~/.last_grade_content.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
如果文件包含完整的课程列表和GPA,说明系统工作正常。
|
||||||
|
|
||||||
|
### 方法3:查看进程状态
|
||||||
|
```bash
|
||||||
|
ps aux | grep monitor.py
|
||||||
|
```
|
||||||
|
|
||||||
|
如果有进程在运行,说明程序正在监控中。
|
||||||
|
|
||||||
|
## 📝 最佳实践
|
||||||
|
|
||||||
|
### 测试阶段
|
||||||
|
1. **首次测试**:运行 `python3 monitor.py --test`
|
||||||
|
2. **等待5分钟**
|
||||||
|
3. **再次测试**:确认能正常获取成绩
|
||||||
|
4. **等待10分钟**
|
||||||
|
5. **部署服务**:配置systemd或使用tmux
|
||||||
|
|
||||||
|
### 运行阶段
|
||||||
|
1. **设置CHECK_INTERVAL为120秒或更长**
|
||||||
|
2. **让程序自动运行,不要手动干预**
|
||||||
|
3. **每天查看一次日志即可**
|
||||||
|
4. **不要频繁重启服务**
|
||||||
|
|
||||||
|
### 如果被持续拦截
|
||||||
|
1. **停止所有监控程序**
|
||||||
|
2. **等待至少30分钟**
|
||||||
|
3. **将CHECK_INTERVAL改为600秒(10分钟)**
|
||||||
|
4. **重新启动,让它慢慢运行**
|
||||||
|
5. **确认稳定后,再逐步减少间隔**
|
||||||
|
|
||||||
|
## 🛠️ 调试技巧
|
||||||
|
|
||||||
|
### 查看实际响应内容
|
||||||
|
```bash
|
||||||
|
cat ~/grade_monitor/.debug_response.html
|
||||||
|
```
|
||||||
|
|
||||||
|
如果文件包含"请不要过快点击",说明请求被拦截。
|
||||||
|
|
||||||
|
### 检查登录状态
|
||||||
|
```bash
|
||||||
|
# 运行测试模式
|
||||||
|
python3 monitor.py --test
|
||||||
|
|
||||||
|
# 查看是否成功登录
|
||||||
|
grep "登录成功" monitor.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试网络连接
|
||||||
|
```bash
|
||||||
|
# 测试是否能访问学校网站
|
||||||
|
curl -I https://webvpn.neu.edu.cn
|
||||||
|
|
||||||
|
# 测试SMTP邮件服务器
|
||||||
|
nc -zv smtp.163.com 465
|
||||||
|
```
|
||||||
|
|
||||||
|
## 💡 温馨提示
|
||||||
|
|
||||||
|
1. **成绩更新频率不高**:学校不会每分钟更新成绩,建议CHECK_INTERVAL设为120-300秒
|
||||||
|
2. **避免过度监控**:频繁访问可能被学校系统封禁IP
|
||||||
|
3. **合理设置间隔**:既能及时发现新成绩,又不会触发限流
|
||||||
|
4. **信任自动化**:部署后让程序自己运行,不需要频繁检查
|
||||||
|
|
||||||
|
## 📞 仍然无法解决?
|
||||||
|
|
||||||
|
1. 检查配置文件中的账号密码是否正确
|
||||||
|
2. 确认能在浏览器中正常登录学校系统
|
||||||
|
3. 查看 `.debug_response.html` 了解实际响应
|
||||||
|
4. 将CHECK_INTERVAL增加到600秒(10分钟)
|
||||||
|
5. 考虑只在特定时间段运行(如每天上午10点和下午3点)
|
||||||
69
打包.sh
Normal file
69
打包.sh
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 成绩监控系统 - 打包脚本
|
||||||
|
# 用途:打包项目文件准备上传到服务器
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ARCHIVE_NAME="gpa_monitor.tar.gz"
|
||||||
|
|
||||||
|
echo "========================================="
|
||||||
|
echo " 成绩监控系统 - 打包工具"
|
||||||
|
echo "========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# 检查config.ini是否存在
|
||||||
|
if [ ! -f "config.ini" ]; then
|
||||||
|
echo "⚠️ 警告: 未找到 config.ini"
|
||||||
|
echo " 请先配置 config.ini 文件"
|
||||||
|
echo ""
|
||||||
|
read -p "是否继续打包?(y/n): " continue_pack
|
||||||
|
if [ "$continue_pack" != "y" ]; then
|
||||||
|
echo "已取消"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "📦 正在打包必需文件..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 打包必需文件
|
||||||
|
tar -czf "$ARCHIVE_NAME" \
|
||||||
|
monitor.py \
|
||||||
|
config.ini \
|
||||||
|
requirements.txt \
|
||||||
|
setup_python.sh \
|
||||||
|
grade-monitor.service \
|
||||||
|
readme.md \
|
||||||
|
"Debian服务器部署指南.md" \
|
||||||
|
2>/dev/null
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✓ 打包成功!"
|
||||||
|
echo ""
|
||||||
|
echo "压缩包信息:"
|
||||||
|
ls -lh "$ARCHIVE_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "包含文件:"
|
||||||
|
tar -tzf "$ARCHIVE_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "========================================="
|
||||||
|
echo "下一步:"
|
||||||
|
echo "1. 上传到服务器:"
|
||||||
|
echo " scp $ARCHIVE_NAME 用户名@服务器IP:~/"
|
||||||
|
echo ""
|
||||||
|
echo "2. 在服务器上解压:"
|
||||||
|
echo " tar -xzf $ARCHIVE_NAME"
|
||||||
|
echo " cd gpa_monitor"
|
||||||
|
echo ""
|
||||||
|
echo "3. 运行安装脚本:"
|
||||||
|
echo " chmod +x setup_python.sh"
|
||||||
|
echo " ./setup_python.sh"
|
||||||
|
echo ""
|
||||||
|
echo "详细说明请查看: Debian服务器部署指南.md"
|
||||||
|
echo "========================================="
|
||||||
|
else
|
||||||
|
echo "✗ 打包失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
516
部署.md
Normal file
516
部署.md
Normal file
@@ -0,0 +1,516 @@
|
|||||||
|
# 服务器部署指南
|
||||||
|
|
||||||
|
本文档详细说明如何将成绩监控系统部署到Linux服务器上。
|
||||||
|
|
||||||
|
## 📋 前置要求
|
||||||
|
|
||||||
|
- Linux服务器(Debian/Ubuntu/CentOS等)
|
||||||
|
- Python 3.7+
|
||||||
|
- 服务器能访问外网
|
||||||
|
- SSH访问权限
|
||||||
|
|
||||||
|
## 📦 准备文件
|
||||||
|
|
||||||
|
### 需要上传的文件
|
||||||
|
|
||||||
|
```
|
||||||
|
grade_monitor/
|
||||||
|
├── monitor.py # 主程序(必需)
|
||||||
|
├── config.ini # 配置文件(必需)
|
||||||
|
├── requirements.txt # Python依赖(必需)
|
||||||
|
├── setup_python.sh # 安装脚本(必需)
|
||||||
|
└── readme.md # 说明文档(可选)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 打包文件
|
||||||
|
|
||||||
|
在Windows本地执行:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# 方法1:使用tar(需要WSL或Git Bash)
|
||||||
|
tar -czf grade_monitor.tar.gz monitor.py config.ini requirements.txt setup_python.sh readme.md
|
||||||
|
|
||||||
|
# 方法2:使用7-Zip或WinRAR手动打包
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 部署步骤
|
||||||
|
|
||||||
|
### 步骤1:修改配置文件
|
||||||
|
|
||||||
|
**在上传前**,确保 `config.ini` 中填入了正确的信息:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[login]
|
||||||
|
USERNAME = 你的学号
|
||||||
|
PASSWORD = 你的密码
|
||||||
|
LOGIN_URL = ...
|
||||||
|
GRADE_URL = ...
|
||||||
|
|
||||||
|
[email]
|
||||||
|
SENDER_EMAIL = your_email@163.com
|
||||||
|
SENDER_PASSWORD = SMTP授权码
|
||||||
|
RECEIVER_EMAIL = 接收通知的邮箱
|
||||||
|
|
||||||
|
[monitor]
|
||||||
|
CHECK_INTERVAL = 60
|
||||||
|
REQUEST_DELAY = 5
|
||||||
|
MAX_RETRIES = 3
|
||||||
|
RETRY_DELAY = 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤2:上传到服务器
|
||||||
|
|
||||||
|
#### 方法A:使用SCP上传
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 上传打包文件
|
||||||
|
scp grade_monitor.tar.gz username@server-ip:/home/username/
|
||||||
|
|
||||||
|
# 或直接上传文件
|
||||||
|
scp monitor.py config.ini requirements.txt setup_python.sh username@server-ip:/home/username/grade_monitor/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方法B:使用SFTP上传
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sftp username@server-ip
|
||||||
|
put grade_monitor.tar.gz
|
||||||
|
exit
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 方法C:使用FTP客户端
|
||||||
|
|
||||||
|
使用FileZilla、WinSCP等工具上传文件。
|
||||||
|
|
||||||
|
### 步骤3:连接到服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh username@server-ip
|
||||||
|
```
|
||||||
|
|
||||||
|
### 步骤4:解压和安装
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建工作目录
|
||||||
|
mkdir -p ~/grade_monitor
|
||||||
|
cd ~/grade_monitor
|
||||||
|
|
||||||
|
# 添加执行权限
|
||||||
|
chmod +x setup_python.sh
|
||||||
|
|
||||||
|
# 运行安装脚本
|
||||||
|
./setup_python.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
安装脚本会自动:
|
||||||
|
- 更新apt包列表
|
||||||
|
- 安装Python3和pip
|
||||||
|
- 创建虚拟环境
|
||||||
|
- 安装所需的Python包(requests、beautifulsoup4)
|
||||||
|
|
||||||
|
### 步骤5:测试运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 激活虚拟环境
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# 测试模式运行
|
||||||
|
python3 monitor.py --test
|
||||||
|
```
|
||||||
|
|
||||||
|
**检查输出:**
|
||||||
|
- 是否成功登录
|
||||||
|
- 是否获取到成绩页面
|
||||||
|
- 查看提取的成绩内容
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看提取的成绩
|
||||||
|
cat .last_grade_content.txt
|
||||||
|
|
||||||
|
# 应该看到类似这样的内容:
|
||||||
|
# ============================================================
|
||||||
|
# 总平均绩点:4.3471
|
||||||
|
# ============================================================
|
||||||
|
#
|
||||||
|
# 学年学期 | 课程代码 | 课程序号 | 课程名称 | ...
|
||||||
|
# --------------------------------------------------------
|
||||||
|
# 2025-2026 秋季 | A0801051020 | A095478 | C++程序设计 | ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔄 后台运行方案
|
||||||
|
|
||||||
|
### 方案A:使用systemd(推荐,适合长期运行)
|
||||||
|
|
||||||
|
#### 1. 创建服务文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nano /etc/systemd/system/grade-monitor.service
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 填入以下内容
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=Grade Monitoring Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=你的用户名
|
||||||
|
WorkingDirectory=/home/你的用户名/grade_monitor
|
||||||
|
ExecStart=/home/你的用户名/grade_monitor/venv/bin/python3 /home/你的用户名/grade_monitor/monitor.py
|
||||||
|
Restart=always
|
||||||
|
RestartSec=30
|
||||||
|
StandardOutput=append:/home/你的用户名/grade_monitor/monitor.log
|
||||||
|
StandardError=append:/home/你的用户名/grade_monitor/monitor.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意:** 将上面的"你的用户名"替换为实际的Linux用户名
|
||||||
|
|
||||||
|
#### 3. 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 重载systemd配置
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
# 启用开机自启
|
||||||
|
sudo systemctl enable grade-monitor
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
sudo systemctl start grade-monitor
|
||||||
|
|
||||||
|
# 查看服务状态
|
||||||
|
sudo systemctl status grade-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. 管理服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看日志
|
||||||
|
sudo journalctl -u grade-monitor -f
|
||||||
|
|
||||||
|
# 停止服务
|
||||||
|
sudo systemctl stop grade-monitor
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
sudo systemctl restart grade-monitor
|
||||||
|
|
||||||
|
# 禁用开机自启
|
||||||
|
sudo systemctl disable grade-monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案B:使用tmux(简单,适合临时运行)
|
||||||
|
|
||||||
|
#### 1. 安装tmux
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Debian/Ubuntu
|
||||||
|
sudo apt install tmux
|
||||||
|
|
||||||
|
# CentOS/RHEL
|
||||||
|
sudo yum install tmux
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 创建会话并运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建新的tmux会话
|
||||||
|
tmux new -s grade_monitor
|
||||||
|
|
||||||
|
# 激活虚拟环境
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# 运行程序
|
||||||
|
python3 monitor.py
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 离开和重连会话
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 离开会话(程序继续运行)
|
||||||
|
# 按键:Ctrl+B,然后按 D
|
||||||
|
|
||||||
|
# 重新连接到会话
|
||||||
|
tmux attach -t grade_monitor
|
||||||
|
|
||||||
|
# 查看所有会话
|
||||||
|
tmux ls
|
||||||
|
|
||||||
|
# 关闭会话
|
||||||
|
tmux kill-session -t grade_monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案C:使用screen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装screen
|
||||||
|
sudo apt install screen
|
||||||
|
|
||||||
|
# 创建新会话
|
||||||
|
screen -S grade_monitor
|
||||||
|
|
||||||
|
# 激活环境并运行
|
||||||
|
source venv/bin/activate
|
||||||
|
python3 monitor.py
|
||||||
|
|
||||||
|
# 离开会话:Ctrl+A,然后按 D
|
||||||
|
|
||||||
|
# 重新连接
|
||||||
|
screen -r grade_monitor
|
||||||
|
|
||||||
|
# 查看所有会话
|
||||||
|
screen -ls
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方案D:使用nohup(最简单)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 激活虚拟环境
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
# 后台运行
|
||||||
|
nohup python3 monitor.py > monitor.log 2>&1 &
|
||||||
|
|
||||||
|
# 查看进程
|
||||||
|
ps aux | grep monitor.py
|
||||||
|
|
||||||
|
# 停止进程
|
||||||
|
kill <进程ID>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 监控和维护
|
||||||
|
|
||||||
|
### 查看运行状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 方法1:查看日志文件
|
||||||
|
tail -f ~/grade_monitor/monitor.log
|
||||||
|
|
||||||
|
# 方法2:查看systemd日志(如果使用systemd)
|
||||||
|
sudo journalctl -u grade-monitor -n 50 -f
|
||||||
|
|
||||||
|
# 方法3:查看进程
|
||||||
|
ps aux | grep monitor.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查看当前成绩
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/grade_monitor
|
||||||
|
cat .last_grade_content.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 手动触发测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/grade_monitor
|
||||||
|
source venv/bin/activate
|
||||||
|
python3 monitor.py --test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 常见问题
|
||||||
|
|
||||||
|
### 问题1:连接服务器失败
|
||||||
|
|
||||||
|
**可能原因:**
|
||||||
|
- SSH端口被防火墙拦截
|
||||||
|
- 服务器IP或用户名错误
|
||||||
|
- SSH密钥配置问题
|
||||||
|
|
||||||
|
**解决方法:**
|
||||||
|
```bash
|
||||||
|
# 指定端口
|
||||||
|
ssh -p 端口号 username@server-ip
|
||||||
|
|
||||||
|
# 使用密钥
|
||||||
|
ssh -i /path/to/key.pem username@server-ip
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题2:无法访问学校网站
|
||||||
|
|
||||||
|
**可能原因:**
|
||||||
|
- 服务器在校外,需要VPN
|
||||||
|
- 防火墙拦截
|
||||||
|
|
||||||
|
**解决方法:**
|
||||||
|
- 使用校内服务器
|
||||||
|
- 配置代理
|
||||||
|
- 联系网络管理员
|
||||||
|
|
||||||
|
### 问题3:pip安装失败
|
||||||
|
|
||||||
|
**解决方法:**
|
||||||
|
```bash
|
||||||
|
# 更新pip
|
||||||
|
pip install --upgrade pip
|
||||||
|
|
||||||
|
# 使用国内镜像
|
||||||
|
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||||
|
|
||||||
|
# 或使用清华源
|
||||||
|
pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题4:权限不足
|
||||||
|
|
||||||
|
**解决方法:**
|
||||||
|
```bash
|
||||||
|
# 修改文件所有者
|
||||||
|
sudo chown -R username:username ~/grade_monitor
|
||||||
|
|
||||||
|
# 修改执行权限
|
||||||
|
chmod +x ~/grade_monitor/*.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 问题5:邮件发送失败
|
||||||
|
|
||||||
|
**解决方法:**
|
||||||
|
- 确认SMTP授权码正确(不是邮箱密码)
|
||||||
|
- 检查服务器能否访问smtp.163.com(端口465)
|
||||||
|
- 查看详细错误信息:`tail -f monitor.log`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 测试网络连接
|
||||||
|
telnet smtp.163.com 465
|
||||||
|
# 或
|
||||||
|
nc -zv smtp.163.com 465
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔐 安全建议
|
||||||
|
|
||||||
|
### 1. 保护配置文件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 限制config.ini权限
|
||||||
|
chmod 600 ~/grade_monitor/config.ini
|
||||||
|
|
||||||
|
# 确保只有自己能访问
|
||||||
|
ls -la ~/grade_monitor/config.ini
|
||||||
|
# 应显示:-rw------- 1 username username
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 使用环境变量(可选)
|
||||||
|
|
||||||
|
不在配置文件中存储明文密码,而是使用环境变量:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置环境变量
|
||||||
|
export GRADE_USERNAME="你的学号"
|
||||||
|
export GRADE_PASSWORD="你的密码"
|
||||||
|
export EMAIL_PASSWORD="SMTP授权码"
|
||||||
|
|
||||||
|
# 添加到.bashrc使其永久生效
|
||||||
|
echo 'export GRADE_USERNAME="你的学号"' >> ~/.bashrc
|
||||||
|
echo 'export GRADE_PASSWORD="你的密码"' >> ~/.bashrc
|
||||||
|
echo 'export EMAIL_PASSWORD="SMTP授权码"' >> ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 定期更新
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 更新系统
|
||||||
|
sudo apt update && sudo apt upgrade
|
||||||
|
|
||||||
|
# 更新Python包
|
||||||
|
cd ~/grade_monitor
|
||||||
|
source venv/bin/activate
|
||||||
|
pip install --upgrade requests beautifulsoup4
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📱 监控建议
|
||||||
|
|
||||||
|
### 设置监控脚本
|
||||||
|
|
||||||
|
创建一个检查脚本 `check_status.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 检查进程是否运行
|
||||||
|
if pgrep -f "monitor.py" > /dev/null; then
|
||||||
|
echo "✓ 监控程序正在运行"
|
||||||
|
else
|
||||||
|
echo "✗ 监控程序未运行!"
|
||||||
|
# 可以在这里添加重启逻辑
|
||||||
|
# systemctl start grade-monitor
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查最近的日志
|
||||||
|
echo ""
|
||||||
|
echo "最近的日志:"
|
||||||
|
tail -n 5 ~/grade_monitor/monitor.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### 定期检查(使用cron)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 编辑crontab
|
||||||
|
crontab -e
|
||||||
|
|
||||||
|
# 添加以下行(每天检查一次)
|
||||||
|
0 12 * * * /home/username/grade_monitor/check_status.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 完整部署示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# === 本地操作(Windows/WSL) ===
|
||||||
|
cd E:\50425\Documents\Github\GPA_Monitoring
|
||||||
|
tar -czf grade_monitor.tar.gz monitor.py config.ini requirements.txt setup_python.sh readme.md
|
||||||
|
scp grade_monitor.tar.gz user@server.com:~/
|
||||||
|
|
||||||
|
# === 服务器操作 ===
|
||||||
|
ssh user@server.com
|
||||||
|
|
||||||
|
# 解压和安装
|
||||||
|
mkdir -p ~/grade_monitor
|
||||||
|
cd ~/grade_monitor
|
||||||
|
tar -xzf ../grade_monitor.tar.gz
|
||||||
|
chmod +x setup_python.sh
|
||||||
|
./setup_python.sh
|
||||||
|
|
||||||
|
# 测试
|
||||||
|
source venv/bin/activate
|
||||||
|
python3 monitor.py --test
|
||||||
|
cat .last_grade_content.txt
|
||||||
|
|
||||||
|
# 配置systemd服务
|
||||||
|
sudo nano /etc/systemd/system/grade-monitor.service
|
||||||
|
# (填入服务配置)
|
||||||
|
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable grade-monitor
|
||||||
|
sudo systemctl start grade-monitor
|
||||||
|
sudo systemctl status grade-monitor
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
sudo journalctl -u grade-monitor -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✅ 部署检查清单
|
||||||
|
|
||||||
|
- [ ] 文件已上传到服务器
|
||||||
|
- [ ] config.ini配置正确(账号、密码、邮箱)
|
||||||
|
- [ ] 运行setup_python.sh安装依赖
|
||||||
|
- [ ] 测试模式运行成功
|
||||||
|
- [ ] 成绩提取格式正确
|
||||||
|
- [ ] 配置后台运行(systemd/tmux/screen)
|
||||||
|
- [ ] 服务正常启动
|
||||||
|
- [ ] 日志输出正常
|
||||||
|
- [ ] 收到测试邮件
|
||||||
|
- [ ] 设置文件权限(chmod 600 config.ini)
|
||||||
|
|
||||||
|
## 📞 获取帮助
|
||||||
|
|
||||||
|
如果遇到问题:
|
||||||
|
1. 查看 `monitor.log` 了解详细错误
|
||||||
|
2. 运行 `python3 monitor.py --test` 测试
|
||||||
|
3. 检查 `.debug_response.html` 了解实际响应
|
||||||
|
4. 查看 `readme.md` 了解更多功能说明
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
部署完成后,系统将自动监控成绩变化,有新课程成绩时会立即发送邮件通知!
|
||||||
19
重启服务.md
Normal file
19
重启服务.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# 停止服务
|
||||||
|
systemctl stop grade-monitor
|
||||||
|
|
||||||
|
# 备份旧文件
|
||||||
|
cp monitor.py monitor.py.backup
|
||||||
|
|
||||||
|
# 解压新文件(会覆盖)
|
||||||
|
tar -xzf ~/gpa_monitor.tar.gz -C ~/grade_monitor --strip-components=0
|
||||||
|
|
||||||
|
# 或者只替换 monitor.py
|
||||||
|
# 你可以直接编辑:
|
||||||
|
nano monitor.py
|
||||||
|
# 找到 parse_courses 函数,修改那一行判断条件
|
||||||
|
|
||||||
|
# 重启服务
|
||||||
|
systemctl restart grade-monitor
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
tail -f monitor.log
|
||||||
Reference in New Issue
Block a user