1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/Bg6shY若失效,可用地址
阿里云:
服务器购买地址
https://t.aliyun.com/U/Bg6shY若失效,可用地址
https://www.aliyun.com/daily-act/ecs/activity_selection?source=5176.29345612&userCode=49hts92d腾讯云:
https://curl.qcloud.com/wJpWmSfU若失效,可用地址
https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console华为云
https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=2019052.部署教程
3.代码如下
import sysimport osimport configparserimport threadingfrom datetime import datetimefrom pathlib import Pathimport pyautoguifrom apscheduler.schedulers.background import BackgroundSchedulerfrom PyQt6.QtCore import (Qt, QThread, pyqtSignal, QTimer, QObject)from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,QGroupBox, QLabel, QLineEdit, QPushButton, QTextEdit, QMessageBox,QSpinBox, QFrame, QSizePolicy)from PyQt6.QtGui import QFont, QPalette, QColor, QIcon# -------------------------- 全局配置 --------------------------CONFIG_PATH = Path("qiandao_config.ini")LOG_FILE = "qiandao_log.txt"SCREENSHOT_DIR = Path("screenshots")SCREENSHOT_DIR.mkdir(exist_ok=True)# -------------------------- 日志重定向 --------------------------import loggingclass QtLogHandler(logging.Handler, QObject):log_signal = pyqtSignal(str)def __init__(self):logging.Handler.__init__(self)QObject.__init__(self)def emit(self, record):msg = self.format(record)self.log_signal.emit(msg)def setup_logging(log_widget):logger = logging.getLogger()logger.setLevel(logging.INFO)logger.handlers.clear()console_handler = logging.StreamHandler()console_handler.setLevel(logging.INFO)file_handler = logging.FileHandler(LOG_FILE, encoding="utf-8")file_handler.setLevel(logging.INFO)qt_handler = QtLogHandler()qt_handler.log_signal.connect(log_widget.append_log)qt_handler.setLevel(logging.INFO)formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s",datefmt="%Y-%m-%d %H:%M:%S")for handler in [console_handler, file_handler, qt_handler]:handler.setFormatter(formatter)logger.addHandler(handler)return logger# -------------------------- 核心优化:智能签到线程 --------------------------class SignInThread(QThread):finish_signal = pyqtSignal(bool, str)log_signal = pyqtSignal(str)# 配置参数(可根据实际情况调整)RETRY_TIMES = 3 # 每个步骤重试次数ELEMENT_TIMEOUT = 20 # 元素等待超时(秒)SHORT_WAIT = 0.5 # 短等待(秒)LONG_WAIT = 2 # 长等待(秒)def __init__(self):super().__init__()# 目标坐标配置(可根据实际情况修改)self.coords = {"browser_icon": (508, 1055),"bookmark": (1385, 116),"game_subtag": (1446, 168),"game_link": (1131, 340),"sign_tag": (1870, 713),"sign_btn": (1740, 302)}def wait_for_pixel_color(self, pos, target_color=None, timeout=10):"""智能等待:检测目标位置像素颜色(可选)pos: 坐标(x,y)target_color: 目标RGB颜色(如(255,0,0)),None则仅等待窗口激活timeout: 超时时间"""start_time = datetime.now()while (datetime.now() - start_time).total_seconds() < timeout:# 检查鼠标位置是否在前台窗口try:pyautogui.moveTo(pos, duration=0.2)if target_color is None:return True# 检测像素颜色current_color = pyautogui.pixel(*pos)if current_color == target_color:return Trueexcept Exception:passpyautogui.sleep(self.SHORT_WAIT)self.log_signal.emit(f"⚠️ 等待坐标{pos}超时({timeout}秒)")return Falsedef click_with_retry(self, pos, desc, target_color=None):"""带重试的点击操作pos: 坐标(x,y)desc: 操作描述(日志用)target_color: 点击后等待的目标颜色"""for retry in range(self.RETRY_TIMES):try:self.log_signal.emit(f"🔍 尝试{retry + 1}/{self.RETRY_TIMES}:{desc}")# 移动鼠标到目标位置(平滑移动)pyautogui.moveTo(pos, duration=0.3)# 点击pyautogui.click(pos)self.log_signal.emit(f"✅ 点击{desc}成功")# 等待元素加载/颜色匹配if target_color:if self.wait_for_pixel_color(pos, target_color, self.ELEMENT_TIMEOUT):return Trueelse:pyautogui.sleep(self.LONG_WAIT)return Trueexcept Exception as e:self.log_signal.emit(f"❌ 点击{desc}失败:{str(e)}")pyautogui.sleep(self.LONG_WAIT)self.log_signal.emit(f"❌ {desc}重试{self.RETRY_TIMES}次仍失败")return Falsedef activate_browser_window(self):"""激活浏览器窗口(确保在前台)"""self.log_signal.emit("📌 激活浏览器窗口...")# 先点击浏览器图标if self.click_with_retry(self.coords["browser_icon"], "浏览器图标"):# 等待浏览器窗口加载pyautogui.sleep(3)# 再次点击确保窗口前置pyautogui.click(self.coords["browser_icon"])return Truereturn Falsedef run(self):try:self.log_signal.emit("🚀 开始智能签到流程...")pyautogui.FAILSAFE = Truepyautogui.PAUSE = self.SHORT_WAITscreen_width, screen_height = pyautogui.size()self.log_signal.emit(f"🖥️ 屏幕分辨率:{screen_width} x {screen_height}")# 步骤1:激活浏览器窗口if not self.activate_browser_window():raise Exception("浏览器窗口激活失败")# 步骤2:打开游戏页面(带重试)self.log_signal.emit("📖 打开游戏书签...")if not self.click_with_retry(self.coords["bookmark"], "书签栏"):raise Exception("点击书签栏失败")if not self.click_with_retry(self.coords["game_subtag"], "游戏子标签"):raise Exception("点击游戏子标签失败")if not self.click_with_retry(self.coords["game_link"], "游戏链接"):raise Exception("点击游戏链接失败")# 步骤3:二次验证(确保页面打开)self.log_signal.emit("🔄 二次验证游戏页面...")pyautogui.sleep(self.LONG_WAIT)if not self.click_with_retry(self.coords["bookmark"], "书签栏(二次)"):raise Exception("二次点击书签栏失败")if not self.click_with_retry(self.coords["game_subtag"], "游戏子标签(二次)"):raise Exception("二次点击游戏子标签失败")if not self.click_with_retry(self.coords["game_link"], "游戏链接(二次)"):raise Exception("二次点击游戏链接失败")# 步骤4:等待页面加载(智能超时)self.log_signal.emit("⏳ 等待游戏页面加载(最大20秒)...")if not self.wait_for_pixel_color(self.coords["game_link"], None, 20):raise Exception("游戏页面加载超时")# 步骤5:点击签到标签self.log_signal.emit("📝 点击签到标签...")if not self.click_with_retry(self.coords["sign_tag"], "签到标签"):raise Exception("点击签到标签失败")# 步骤6:等待签到面板加载self.log_signal.emit("⏳ 等待签到面板加载(最大15秒)...")if not self.wait_for_pixel_color(self.coords["sign_btn"], None, 15):raise Exception("签到面板加载超时")# 步骤7:点击签到按钮self.log_signal.emit("🎯 点击签到按钮...")if not self.click_with_retry(self.coords["sign_btn"], "签到按钮"):raise Exception("点击签到按钮失败")# 步骤8:验证签到结果pyautogui.sleep(3)screenshot_name = f"qiandao_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"screenshot_path = SCREENSHOT_DIR / screenshot_namepyautogui.screenshot(str(screenshot_path))msg = f"🎉 签到成功!截图已保存至:{screenshot_path}"self.log_signal.emit(msg)self.finish_signal.emit(True, msg)except Exception as e:error_msg = f"❌ 签到失败:{str(e)}"self.log_signal.emit(error_msg)self.finish_signal.emit(False, error_msg)# -------------------------- 主界面类(无修改) --------------------------class SignInGUI(QMainWindow):def __init__(self):super().__init__()self.scheduler = Noneself.sign_in_thread = Noneself.last_sign_in_time = Noneself.config = self.load_config()self.setWindowTitle("自动签到助手 v2.0(优化版)")self.setMinimumSize(800, 600)self.setStyleSheet(self.get_style_sheet())central_widget = QWidget()self.setCentralWidget(central_widget)main_layout = QVBoxLayout(central_widget)main_layout.setSpacing(20)main_layout.setContentsMargins(30, 30, 30, 30)# 1. 标题栏title_layout = QHBoxLayout()title_label = QLabel("🎯 自动签到助手(智能优化版)")title_label.setFont(QFont("微软雅黑", 24, QFont.Weight.Bold))title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)title_layout.addWidget(title_label)main_layout.addLayout(title_layout)# 2. 功能区func_group = QGroupBox("核心功能")func_group.setFont(QFont("微软雅黑", 14))func_layout = QGridLayout(func_group)func_layout.setSpacing(15)func_layout.setContentsMargins(20, 20, 20, 20)# 定时时间设置func_layout.addWidget(QLabel("定时签到时间:"), 0, 0, Qt.AlignmentFlag.AlignRight)self.hour_spin = QSpinBox()self.hour_spin.setRange(0, 23)self.hour_spin.setValue(int(self.config.get("hour", 8)))self.hour_spin.setFixedWidth(80)func_layout.addWidget(self.hour_spin, 0, 1)func_layout.addWidget(QLabel("时"), 0, 2)self.minute_spin = QSpinBox()self.minute_spin.setRange(0, 59)self.minute_spin.setValue(int(self.config.get("minute", 30)))self.minute_spin.setFixedWidth(80)func_layout.addWidget(self.minute_spin, 0, 3)func_layout.addWidget(QLabel("分"), 0, 4)# 保存配置按钮save_btn = QPushButton("💾 保存时间设置")save_btn.clicked.connect(self.save_config)func_layout.addWidget(save_btn, 0, 5)# 手动签到按钮manual_btn = QPushButton("🚀 立即手动签到")manual_btn.clicked.connect(self.start_manual_sign_in)func_layout.addWidget(manual_btn, 1, 0, 1, 3)# 启动/停止定时任务按钮self.timer_btn = QPushButton("▶️ 启动定时签到")self.timer_btn.clicked.connect(self.toggle_scheduler)func_layout.addWidget(self.timer_btn, 1, 3, 1, 3)# 状态显示func_layout.addWidget(QLabel("当前状态:"), 2, 0, Qt.AlignmentFlag.AlignRight)self.status_label = QLabel("未启动")self.status_label.setStyleSheet("color: #ff6b6b; font-weight: bold;")func_layout.addWidget(self.status_label, 2, 1, 1, 2)# 最后签到时间func_layout.addWidget(QLabel("最后签到:"), 2, 3, Qt.AlignmentFlag.AlignRight)self.last_sign_label = QLabel("从未执行")func_layout.addWidget(self.last_sign_label, 2, 4, 1, 2)main_layout.addWidget(func_group)# 3. 日志区log_group = QGroupBox("运行日志")log_group.setFont(QFont("微软雅黑", 14))log_layout = QVBoxLayout(log_group)log_layout.setContentsMargins(20, 20, 20, 20)self.log_text = QTextEdit()self.log_text.setReadOnly(True)self.log_text.setFont(QFont("Consolas", 10))log_layout.addWidget(self.log_text)# 清空日志按钮clear_log_btn = QPushButton("🗑️ 清空日志")clear_log_btn.clicked.connect(lambda: self.log_text.clear())log_layout.addWidget(clear_log_btn, alignment=Qt.AlignmentFlag.AlignRight)main_layout.addWidget(log_group, stretch=1)# 4. 底部信息footer_layout = QHBoxLayout()footer_label = QLabel("© 2025 自动签到助手(智能优化版)- 请勿用于非法用途")footer_label.setAlignment(Qt.AlignmentFlag.AlignCenter)footer_label.setStyleSheet("color: #888888; font-size: 12px;")footer_layout.addWidget(footer_label)main_layout.addLayout(footer_layout)# 初始化日志self.logger = setup_logging(self)self.logger.info("程序启动成功(智能优化版)")# 定时更新状态self.status_timer = QTimer()self.status_timer.timeout.connect(self.update_status)self.status_timer.start(1000)def append_log(self, msg):self.log_text.append(msg)cursor = self.log_text.textCursor()cursor.movePosition(cursor.MoveOperation.End)self.log_text.setTextCursor(cursor)def get_style_sheet(self):return """QWidget {background-color: #f8f9fa;color: #333333;font-family: "微软雅黑";}QGroupBox {border: 2px solid #e9ecef;border-radius: 10px;padding-top: 15px;margin-top: 8px;font-weight: bold;}QGroupBox::title {subcontrol-origin: margin;left: 10px;padding: 0 10px 0 10px;color: #495057;}QPushButton {background-color: #007bff;color: white;border: none;border-radius: 8px;padding: 10px 20px;font-size: 14px;font-weight: bold;min-height: 40px;}QPushButton:hover {background-color: #0056b3;border: 1px solid #004085;}QPushButton:pressed {background-color: #004085;padding-left: 11px;padding-top: 11px;}QSpinBox, QLineEdit {border: 2px solid #e9ecef;border-radius: 8px;padding: 8px 10px;font-size: 14px;background-color: white;}QSpinBox:focus, QLineEdit:focus {border-color: #007bff;outline: none;}QTextEdit {border: 2px solid #e9ecef;border-radius: 10px;padding: 10px;background-color: white;selection-background-color: #007bff;selection-color: white;}QLabel {font-size: 14px;}QMainWindow {border: 1px solid #dee2e6;border-radius: 15px;}"""def load_config(self):config = configparser.ConfigParser()if CONFIG_PATH.exists():config.read(CONFIG_PATH, encoding="utf-8")if "SIGNIN" not in config:config["SIGNIN"] = {"hour": 8, "minute": 30}return config["SIGNIN"]def save_config(self):self.config["hour"] = str(self.hour_spin.value())self.config["minute"] = str(self.minute_spin.value())with open(CONFIG_PATH, "w", encoding="utf-8") as f:config = configparser.ConfigParser()config["SIGNIN"] = self.configconfig.write(f)self.logger.info(f"配置已保存:定时签到时间 {self.hour_spin.value()}:{self.minute_spin.value():02d}")QMessageBox.information(self, "成功", "时间设置已保存!")def start_manual_sign_in(self):if self.sign_in_thread and self.sign_in_thread.isRunning():QMessageBox.warning(self, "提示", "签到操作正在进行中,请稍候...")returnself.sign_in_thread = SignInThread()self.sign_in_thread.log_signal.connect(self.append_log)self.sign_in_thread.finish_signal.connect(self.on_sign_in_finish)self.sign_in_thread.start()def on_sign_in_finish(self, success, msg):self.last_sign_in_time = datetime.now()self.last_sign_label.setText(self.last_sign_in_time.strftime("%Y-%m-%d %H:%M:%S"))if success:QMessageBox.information(self, "成功", msg)else:QMessageBox.critical(self, "失败", msg)def toggle_scheduler(self):if self.scheduler and self.scheduler.running:self.scheduler.shutdown()self.scheduler = Noneself.timer_btn.setText("▶️ 启动定时签到")self.timer_btn.setStyleSheet("")self.status_label.setText("已停止")self.status_label.setStyleSheet("color: #ff6b6b; font-weight: bold;")self.logger.info("定时签到任务已停止")else:hour = self.hour_spin.value()minute = self.minute_spin.value()self.scheduler = BackgroundScheduler(timezone="Asia/Shanghai")self.scheduler.add_job(func=self.start_manual_sign_in,trigger="cron",hour=hour,minute=minute,id="daily_sign_in",replace_existing=True,misfire_grace_time=300)self.scheduler.start()self.timer_btn.setText("⏹️ 停止定时签到")self.timer_btn.setStyleSheet("QPushButton { background-color: #dc3545; } QPushButton:hover { background-color: #c82333; }")self.status_label.setText(f"运行中(每日{hour:02d}:{minute:02d}执行)")self.status_label.setStyleSheet("color: #28a745; font-weight: bold;")self.logger.info(f"定时签到任务已启动,每日{hour:02d}:{minute:02d}自动执行")QMessageBox.information(self, "成功", f"定时签到已启动!每日{hour:02d}:{minute:02d}自动执行")def update_status(self):if self.scheduler and self.scheduler.running:self.status_label.setText(f"运行中(每日{self.hour_spin.value():02d}:{self.minute_spin.value():02d}执行)")else:self.status_label.setText("已停止")def closeEvent(self, event):if self.scheduler and self.scheduler.running:self.scheduler.shutdown()if self.sign_in_thread and self.sign_in_thread.isRunning():self.sign_in_thread.terminate()self.logger.info("程序已退出")event.accept()# -------------------------- 主函数 --------------------------if __name__ == "__main__":pyautogui.FAILSAFE = Truepyautogui.PAUSE = 0.5app = QApplication(sys.argv)app.setStyle("Fusion")window = SignInGUI()window.show()sys.exit(app.exec())
这脚本是带可视化界面的"电脑自动签到助手(智能优化版),核心功能:
用 PyQt6 做桌面 GUI:
可以设置"每天几点几分自动签到";
一键手动执行签到;
实时查看运行日志、最后一次签到时间、当前定时任务状态。
用 pyautogui 做桌面自动化操作:
自动移动鼠标、点击浏览器图标、点击书签、进入游戏页面、点击签到按钮;
支持重试、等待页面加载、失败提示;
签到结束自动截图保存到
screenshots目录。用 APScheduler 做定时任务:
在后台以"每天固定时刻(如 08:30)"自动启动签到线程;
可随时从 GUI 上启用/停止定时签到。
用 logging + 自定义 Qt 日志 Handler:
日志既写到文件
qiandao_log.txt,也展示在窗口的"运行日志"文本框中。
总结一句:
这是一个"有界面可配置 + 后台定时 + 带智能重试 + 自动截图 + 日志可视化"的自动签到小工具。
主要方法
1. 日志相关
(1)QtLogHandler 类
继承
logging.Handler+QObject,把日志记录通过
log_signal信号发给 GUI,让所有
logging.info(...)之类的输出 可以同步显示在界面日志窗里。
(2)setup_logging(log_widget)
初始化全局日志系统:
控制台输出;
写入文件
qiandao_log.txt;发送到
QtLogHandler→ 更新 GUI 日志;统一日志格式(时间 + 等级 + 内容)。
2. 自动签到线程:SignInThread 类
真正"动鼠标点签到"的逻辑,都封装在这个线程里,避免卡住界面。
主要成员与方法:
(1)__init__
配置一些固定参数:
重试次数
RETRY_TIMES;等待时间
ELEMENT_TIMEOUT / SHORT_WAIT / LONG_WAIT;各个控件的屏幕坐标(浏览器图标、书签栏、游戏子标签、签到按钮等)。
(2)wait_for_pixel_color(pos, target_color=None, timeout=10)
智能等待工具方法:
把鼠标移到某个坐标;
可选:检查该坐标处像素颜色是否变为指定值;
在超时时间内轮询,用于判断"页面/元素是否加载完成"。
(3)click_with_retry(pos, desc, target_color=None)
带重试的点击操作:
多次尝试移动 → 点击某坐标;
每次点击后根据
target_color或时间进行等待;失败会写日志,直到超过最大重试次数。
(4)activate_browser_window()
负责将浏览器窗口激活到前台:
点击任务栏/桌面上的浏览器图标;
等待几秒;
再点击一次保证窗口前置。
(5)run()(线程核心逻辑)
整体流程大致是:
初始化
pyautogui,打印屏幕分辨率;激活浏览器窗口;
按顺序执行:
点击书签栏 → 游戏子标签 → 游戏链接(带重试、二次验证);
等待游戏页面加载(带超时保护);
点击"签到标签";
等待签到面板加载;
点击"签到按钮";
等待几秒,截图保存到
screenshots/qiandao_时间戳.png;通过
finish_signal把 成功/失败状态 + 文本消息 通知给主界面。
3. 图形界面主窗口:SignInGUI 类
这是整个程序的 GUI 中心,负责:界面布局 + 用户操作 + 调度定时任务 + 管理签到线程。
(1)__init__
初始化窗口标题、大小、整体样式;
创建并布局:
时间设置(小时、分钟 SpinBox);
"保存时间设置"按钮;
"立即手动签到"按钮;
"启动/停止定时签到"按钮;
状态显示(当前状态 / 最后签到时间);
日志展示文本框 + "清空日志"按钮;
底部版权信息。
调用
setup_logging(self)接管日志;启动
QTimer每秒更新状态文本。
(2)append_log(self, msg)
被日志 Handler 信号调用,给日志文本框追加一行文字并自动滚动到底部。
(3)get_style_sheet(self)
返回整个 GUI 的样式表(颜色、按钮圆角、边框等)用于美化界面。
(4)load_config(self) / save_config(self)
load_config:从
qiandao_config.ini中读取签到时间(默认 8:30);save_config:把当前界面上设置的小时/分钟保存回配置文件;
写日志并弹框提示"保存成功"。
(5)start_manual_sign_in(self)
点击"立即手动签到"时调用:
如果已有签到线程在跑,则提示"正在执行中";
否则新建
SignInThread,连接好日志信号/完成信号,启动线程。
(6)on_sign_in_finish(self, success, msg)
被
SignInThread.finish_signal回调:更新"最后签到时间"标签;
根据
success弹出"成功/失败"的消息框。
(7)toggle_scheduler(self)
控制定时任务开关的按钮逻辑:
根据界面设置的小时/分钟,新建
BackgroundScheduler;注册一个每日定时任务:到点执行
start_manual_sign_in;启动 scheduler,按钮变为"停止定时签到",状态变为"运行中(每日 xx:xx 执行)"。
如果当前有 scheduler 并且在运行 → 关闭、改按钮文字为"启动定时签到"、状态变为"已停止";
如果当前没有 scheduler →
(8)update_status(self)
每秒刷新状态标签:
如果 scheduler 正在运行 → 显示当前计划执行时间;
否则显示"已停止"。
(9)closeEvent(self, event)
窗口关闭事件:
如果 scheduler 在运行,先关闭;
如果签到线程在运行,先终止;
写日志"程序已退出",然后允许窗口关闭。
4. 程序入口
if __name__ == "__main__":pyautogui.FAILSAFE = Truepyautogui.PAUSE = 0.5app = QApplication(sys.argv)app.setStyle("Fusion")window = SignInGUI()window.show()sys.exit(app.exec())
设置
pyautogui的全局安全与间隔;创建 Qt 应用,使用
Fusion风格;实例化
SignInGUI并展示窗口;进入 Qt 事件循环,程序开始运行。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论