python语言-密码登录自动化脚本软件代码-QZQ-2026-9-25 - 1
import win32con import win32gui import pyautogui import time import threading import tkinter as tk import clipboard from pynput import keyboard as pk_keyboard pyautogui.FAILSAFE = False # ============================================================ # 配 置 区 # ============================================================ LOGIN_WINDOW_TITLE = "密码登录系统" # 登录窗口标题(支持部分匹配) PASSWORD_FILE = "password_dict.txt" # 密码字典文件 WAIT_AFTER_LOGIN = 0.6 # 每次点登录后等待秒数 # 密码框序号:按 y 从上到下排序后,取第几个"内层小矩形" # 你的日志里内层候选: # 候选[0] (350, 234) y=234 → 若密码框在上,填 1 # 候选[1] (350, 277) y=277 → 若密码框在下,填 2(默认) PASSWORD_ENTRY_INDEX = 2 # 登录按钮序号:按 x 从左到右排序后取第几个 # Button[0] 左 (222,334) / Button[1] 中 (312,334) / Button[2] 右 (402,334) LOGIN_BUTTON_INDEX = 1 # ---------- 登录结果判定 ---------- # 永远忽略的窗口标题(完全相等) IGNORE_TITLES = { '', '操作程序', 'Default IME', 'MSCTFIME UI', 'Program Manager', 'Microsoft Text Input Application', 'Windows Input Experience', } # 标题含这些关键字一律忽略(编辑器 / 浏览器 / 输入法 / 你的项目名) IGNORE_KEYWORDS = ( 'main.py', 'PyCharm', 'Visual Studio', 'Code', 'Edge', 'Chrome', 'Firefox', 'DeepSeek', '密码解密原代码', ) # 明确表示"登录失败"的窗口标题关键字 FAIL_KEYWORDS = ( '错误', '失败', 'error', 'Error', '无效', '不正确', '提示', '警告', 'Warning', ) # 明确表示"登录成功"的窗口标题关键字 SUCCESS_KEYWORDS = ( '成功', '欢迎', '主界面', '主菜单', 'Success', 'Welcome', ) # ============================================================ root = tk.Tk() root.title("操作程序") root.geometry("720x620") # ---------- 全局停止标志 ---------- stop_flag = threading.Event() # ============================================================ # 日 志 输 出 # ============================================================ log_text = tk.Text(root, height=28, width=88, font=("Consolas", 9)) log_text.pack(pady=5) def log(msg): print(msg) try: log_text.insert(tk.END, str(msg) + "\n") log_text.see(tk.END) root.update() except Exception: pass def log_sep(title=""): log("=" * 64) if title: log(f" {title}") log("=" * 64) # ============================================================ # 空 格 键 停 止(双保险) # ============================================================ def trigger_stop(source=""): if not stop_flag.is_set(): stop_flag.set() log(f">>> 收到停止信号 (来源: {source}), 将尽快退出 <<<") else: log(f">>> 再次收到停止信号 (来源: {source}) <<<") def on_global_key_press(key): try: if key == pk_keyboard.Key.space: trigger_stop("pynput全局") except Exception as e: print("全局监听异常:", e) def start_global_listener(): try: listener = pk_keyboard.Listener(on_press=on_global_key_press) listener.daemon = True listener.start() log("[监听] pynput 全局空格监听已启动") except Exception as e: log(f"[监听] pynput 启动失败: {e}") def on_tk_space(event=None): trigger_stop("Tk窗口") root.bind_all('', on_tk_space) root.bind_all('', on_tk_space) # ============================================================ # 窗 口 查 找 与 激 活 # ============================================================ def find_login_window(title): log(f"[查找窗口] 目标标题: '{title}'") hwnd = win32gui.FindWindow(None, title) if hwnd: log(f"[查找窗口] 精确匹配成功, hwnd={hwnd}") return hwnd log("[查找窗口] 精确匹配失败, 改为模糊匹配...") found = [] def cb(h, _): t = win32gui.GetWindowText(h) if t and title in t: found.append((h, t)) win32gui.EnumWindows(cb, None) if found: for h, t in found: log(f"[查找窗口] 模糊匹配到: hwnd={h} 标题='{t}'") return found[0][0] log("[查找窗口] 未找到任何匹配窗口") return 0 def activate_window(hwnd): if not hwnd: return False try: if win32gui.IsIconic(hwnd): log("[激活窗口] 窗口最小化, 先还原") win32gui.ShowWindow(hwnd, win32con.SW_RESTORE) win32gui.SetForegroundWindow(hwnd) time.sleep(0.2) cur = win32gui.GetForegroundWindow() ok = (cur == hwnd) log(f"[激活窗口] hwnd={hwnd} 当前前台={cur} 成功={ok}") return ok except Exception as e: log(f"[激活窗口] 异常: {e}") return False # ============================================================ # 枚 举 子 控 件 # ============================================================ def enum_child_controls(hwnd): controls = [] def cb(h, _): if not win32gui.IsWindowVisible(h): return controls.append(( h, win32gui.GetClassName(h), win32gui.GetWindowText(h), win32gui.GetWindowRect(h) )) win32gui.EnumChildWindows(hwnd, cb, None) return controls def dump_all_controls(hwnd): log_sep(f"枚举窗口 hwnd={hwnd} 的子控件") controls = enum_child_controls(hwnd) if not controls: log("[枚举] 未枚举到任何可见子控件") return controls for i, (h, cls, txt, rect) in enumerate(controls): cx = (rect[0] + rect[2]) // 2 cy = (rect[1] + rect[3]) // 2 log(f" [{i:02d}] hwnd={h:<8} class={cls:<10} text='{txt}' " f"rect={rect} center=({cx},{cy})") log(f"[枚举] 共 {len(controls)} 个控件") return controls # ============================================================ # 定 位 输 入 框(Tk 版,只保留内层小矩形) # ============================================================ def find_tk_entries(hwnd, inner_max_width=200): controls = enum_child_controls(hwnd) button_rows = [(r[1], r[3]) for (h, c, t, r) in controls if c == 'Button'] log(f"[Tk输入框] Button 行 y 范围: {button_rows}") def is_button_row(rect): top, bottom = rect[1], rect[3] for bt, bb in button_rows: if not (bottom < bt or top > bb): return True return False candidates = [] for h, cls, txt, rect in controls: if cls != 'TkChild': continue if is_button_row(rect): continue width = rect[2] - rect[0] if width >= inner_max_width: log(f"[Tk输入框] 跳过大容器 hwnd={h} width={width} rect={rect}") continue candidates.append((h, cls, txt, rect)) candidates.sort(key=lambda c: (c[3][1], c[3][0])) log(f"[Tk输入框] 候选 {len(candidates)} 个(已按从上到下排序):") for i, (h, cls, txt, rect) in enumerate(candidates): cx = (rect[0] + rect[2]) // 2 cy = (rect[1] + rect[3]) // 2 log(f" 候选[{i}] hwnd={h} rect={rect} center=({cx},{cy})") return candidates def find_nth_tk_entry(hwnd, n): cands = find_tk_entries(hwnd) if len(cands) < n: log(f"[Tk输入框] 数量不足 ({len(cands)} < {n})") return None rect = cands[n - 1][3] xy = ((rect[0] + rect[2]) // 2, (rect[1] + rect[3]) // 2) log(f"[Tk输入框] 选中第 {n} 个, 点击坐标={xy}") return xy # ============================================================ # 定 位 按 钮(按 x 从左到右排序) # ============================================================ def find_nth_button(hwnd, n, keywords=('登录', '登陆', '确定', 'Login')): buttons = [c for c in enum_child_controls(hwnd) if c[1] == 'Button'] buttons.sort(key=lambda c: c[3][0]) log(f"[定位按钮] 共 {len(buttons)} 个 Button (已按从左到右排序)") for i, (h, cls, txt, rect) in enumerate(buttons): cx = (rect[0] + rect[2]) // 2 cy = (rect[1] + rect[3]) // 2 log(f" Button[{i}] hwnd={h} text='{txt}' center=({cx},{cy})") if not buttons: return None matched = [b for b in buttons if any(k in b[2] for k in keywords)] target = matched if matched else buttons if matched: log(f"[定位按钮] 关键字匹配到 {len(matched)} 个") else: log("[定位按钮] 关键字未匹配, 按从左到右顺序取") if len(target) < n: log(f"[定位按钮] 数量不足 ({len(target)} < {n})") return None rect = target[n - 1][3] xy = ((rect[0] + rect[2]) // 2, (rect[1] + rect[3]) // 2) log(f"[定位按钮] 选中第 {n} 个 (从左数), 点击坐标={xy}") return xy # ============================================================ # 登 录 结 果 判 定(修正版) # ============================================================ def enum_clean_top_windows(exclude_hwnd=None): """枚举所有可见顶层窗口,过滤掉编辑器/浏览器/输入法等干扰""" result = [] def cb(h, _): if h == exclude_hwnd: return if not win32gui.IsWindowVisible(h): return t = win32gui.GetWindowText(h) if not t: return if t in IGNORE_TITLES: return if any(k in t for k in IGNORE_KEYWORDS): return result.append((h, t)) try: win32gui.EnumWindows(cb, None) except Exception as e: log(f"[检测] 枚举顶层窗口异常: {e}") return result def check_login_result(hwnd, original_title): """ 返回: 'success' → 登录成功 'fail' → 出现错误提示(密码错) 'unknown' → 无法判断 """ # ---- 1. 原窗口是否还活着 ---- if not win32gui.IsWindow(hwnd): log("[检测] 原登录窗口已关闭 → 登录成功") return 'success' # ---- 2. 原窗口标题是否变化 ---- try: title = win32gui.GetWindowText(hwnd) except Exception: log("[检测] 读取原窗口标题异常 → 视为成功") return 'success' if title != original_title: log(f"[检测] 原窗口标题变为 '{title}' → 登录成功") return 'success' # ---- 3. 枚举所有"干净"的顶层窗口 ---- others = enum_clean_top_windows(exclude_hwnd=hwnd) log(f"[检测] 当前其他可见窗口: {[t for _, t in others]}") # ---- 4. 是否有失败提示 ---- for h, t in others: if any(k in t for k in FAIL_KEYWORDS): log(f"[检测] 发现错误提示窗口 '{t}' → 密码错误") try: win32gui.PostMessage(h, win32con.WM_CLOSE, 0, 0) time.sleep(0.2) except Exception: pass return 'fail' # ---- 5. 是否有成功提示 ---- for h, t in others: if any(k in t for k in SUCCESS_KEYWORDS): log(f"[检测] 发现成功窗口 '{t}' → 登录成功") return 'success' # ---- 6. 无法判断 ---- return 'unknown' # ============================================================ # 辅 助 操 作 # ============================================================ def interruptible_sleep(seconds, step=0.05): end = time.time() + seconds while time.time() < end: if stop_flag.is_set(): return False time.sleep(step) return True def click_at(xy, label=""): if not xy: log(f"[点击] {label} 坐标为空, 跳过") return False log(f"[点击] {label} ({xy[0]}, {xy[1]})") pyautogui.click(xy[0], xy[1]) time.sleep(0.1) return True def clear_edit(): log("[清空] Ctrl+A -> Delete") pyautogui.hotkey('ctrl', 'a') time.sleep(0.05) pyautogui.press('delete') time.sleep(0.05) # ============================================================ # 主 流 程 # ============================================================ def try_passwords(): stop_flag.clear() log_sep("开始执行 (按空格键可随时停止)") # ---------- 第一步:登录窗口置前 ---------- log(">>> 第一步: 把登录窗口置于最前") hwnd = find_login_window(LOGIN_WINDOW_TITLE) if not hwnd: log(f"!!! 未找到标题包含 '{LOGIN_WINDOW_TITLE}' 的窗口") return log(f"窗口句柄: {hwnd}") if not activate_window(hwnd): log("!!! 激活登录窗口失败") return original_title = win32gui.GetWindowText(hwnd) log(f"窗口已激活, 原标题='{original_title}'") dump_all_controls(hwnd) if stop_flag.is_set(): log("!!! 收到停止信号, 中止") return # ---------- 第二步:读取 txt 文件 ---------- log(">>> 第二步: 读取 txt 文件") try: with open(PASSWORD_FILE, 'r', encoding='utf-8') as f: passwords = [ln.strip() for ln in f if ln.strip()] except FileNotFoundError: log(f"!!! 未找到文件: {PASSWORD_FILE}") return except Exception as e: log(f"!!! 读取文件异常: {e}") return log(f"共读取 {len(passwords)} 条密码") for i, p in enumerate(passwords[:10], 1): log(f" 第{i}条: {p}") if len(passwords) > 10: log(f" ... (剩余 {len(passwords) - 10} 条省略)") if not passwords: log("!!! 密码字典为空") return if stop_flag.is_set(): log("!!! 收到停止信号, 中止") return # ---------- 第三步:定位密码输入框 ---------- log(">>> 第三步: 定位密码输入框") edit_xy = find_nth_tk_entry(hwnd, n=PASSWORD_ENTRY_INDEX) if not edit_xy: log("!!! 未找到密码输入框") return log(f"密码输入框坐标: {edit_xy}") # ---------- 第四步:定位登录按钮 ---------- log(">>> 第四步: 定位登录按钮") btn_xy = find_nth_button(hwnd, n=LOGIN_BUTTON_INDEX) if not btn_xy: log("!!! 未找到登录按钮") return log(f"登录按钮坐标: {btn_xy}") if stop_flag.is_set(): log("!!! 收到停止信号, 中止") return # ---------- 第五步:循环输入、登录 ---------- log(">>> 第五步: 循环输入密码并登录") success_password = None total = len(passwords) for idx, pwd in enumerate(passwords, start=1): if stop_flag.is_set(): log(f"!!! 第 {idx} 轮开始前检测到停止信号, 退出循环") break log_sep(f"尝试 {idx}/{total} 密码='{pwd}'") # 每轮开始前重新激活窗口 log("[循环] 重新激活窗口") if not activate_window(hwnd): log("[循环] !!! 激活失败, 跳过本次") continue if stop_flag.is_set(): log("[循环] !!! 停止信号, 退出") break log("[循环] 点击密码输入框") click_at(edit_xy, "密码输入框") log("[循环] 清空输入框") clear_edit() log(f"[循环] 复制密码到剪贴板: '{pwd}'") clipboard.copy(pwd) time.sleep(0.03) log("[循环] Ctrl+V 粘贴") pyautogui.hotkey('ctrl', 'v') time.sleep(0.15) log("[循环] 点击登录按钮") click_at(btn_xy, "登录按钮") log(f"[循环] 等待 {WAIT_AFTER_LOGIN}s (按空格可打断)") if not interruptible_sleep(WAIT_AFTER_LOGIN): log("[循环] !!! 等待期间被空格打断, 退出循环") break # 检测登录结果(三态) result = check_login_result(hwnd, original_title) if result == 'success': log_sep(f"!!! 登录成功! 密码 = '{pwd}'") success_password = pwd break elif result == 'fail': log(f"[循环] 密码 '{pwd}' 错误, 继续下一个") else: log("[循环] 结果未知, 继续下一个") # 收尾 if success_password: log_sep(f"结果: 登录成功, 密码='{success_password}'") elif stop_flag.is_set(): log_sep("已由用户按空格键停止") else: log_sep("全部密码已尝试完毕, 未检测到成功") # ============================================================ # 界 面 # ============================================================ btn_run = tk.Button(root, text="执行操作", command=try_passwords, height=2, width=20) btn_run.pack(pady=5) btn_clear = tk.Button(root, text="清空日志", command=lambda: log_text.delete(1.0, tk.END)) btn_clear.pack(pady=2) start_global_listener() log("[提示] 按空格键可停止。") log(f"[配置] 密码框序号={PASSWORD_ENTRY_INDEX} (从上往下), " f"登录按钮序号={LOGIN_BUTTON_INDEX} (从左往右)") log(f"[配置] 每次登录等待 {WAIT_AFTER_LOGIN}s") root.mainloop()