背景与目标:学习通「信仰知识挑战」要求每天在 10 个题库板块答题,每个板块每天 1 轮、每轮约 10 题、每题限时 20 秒,人工反复操作既耗时又容易超时丢分。目标:用一台安卓手机+一台电脑把这套流程自动化,并且严格"不越界"——不做任何与答题无关的手机操作。
功能模块:1)设备层:USB/无线 adb 自动连接、多设备自动选择、掉线重连;配套 PhoneWindow 手机投屏(scrcpy 内核+图形界面)实时看手机画面、按需留帧。2)识别层:截图+Windows 内置 OCR 拿到每行文字与像素坐标,按"题干左对齐、选项居中"把文字聚类切分成选项块。3)作答层:本地题库 bank.tsv 相似度匹配(字符二元组多重集,阈值 0.72)命中即秒答;未命中调用大模型(OpenAI 兼容接口)判题并把新题回写题库(标记未核实);判断题、多选题、填空题分别处理——填写空题通过 ADBKeyBoard 广播中文输入再回读核对,多选题逐个点选后自动提交。4)核对层:进入"答题记录"逐场抓取官方答案,与题库比对修正、补录,输出核对报告。5)编排层:自检 probe、单轮答题 run_quiz、日常 daily(10 个板块额度检查+逐轮答题+抓官方答案)、单板块刷满 grind_category、抓记录 harvest_records、夜间巡检 night。6)桌面程序:QuizWindow.exe 图形界面(首页/任务/日志/题库/说明 五个页签)+QuizWindowCLI.exe 命令行版,双击桌面快捷方式即用;每次运行都生成逐步日志(run.log/steps.tsv/summary.txt/shots 文件夹)。
业务流程:手机停在答题页→脚本先过"前台闸门"确认学习通在前台,否则什么都不做→逐轮截图+OCR→题库或大模型判题→以原地长按方式点击选项→遇到不会的题等超时后继续答后面的→一轮结束进记录页抓官方答案修正题库→全过程写入日志与报告。每个板块每天 1 轮,题库随轮次增长,命中率趋近 100%。
技术选型:Python 3.13+PySide6 6.11(Qt)+PyInstaller 6.22 打包双 EXE;PowerShell 5.1 脚本族;Android platform-tools 33(adb/uiautomator);Windows.Media.Ocr;ADBKeyBoard 输入法桥;OpenAI 兼容大模型接口。我独立完成方案设计、脚本内核、自动化流程、图形程序、打包流水线、文档与交接体系。
设计思路:① 分层——工具层/平台层/设备层/编排层/数据层/界面层,将来移植到 Linux 或安卓自跑时只需改平台层。② 先"包装"后重写——第一阶段图形界面只做编排与显示,真正的动作仍由已验证的 PowerShell 脚本执行,保证行为与老脚本完全一致;第二阶段再逐块把内核搬进 Python,每搬一块都与旧脚本对照跑一轮,一致才切换。③ 安全闸门写进内核:要动手机的任务先确认学习通在前台,不在前台直接返回"nothing was touched";识别不到题目编号标题绝不点击;题库未命中宁可不答也不乱点。④ 打包——one-dir 双 EXE(GUI 无控制台+CLI 有控制台,共用 _internal),构建五步:静态检查→预检查→PyInstaller→塞入 adb→冒烟测试(GUI 出错会弹框并永久等待,所以冒烟必须带超时)。
难点与解决:① uiautomator 控件树在 H5 答题页"惰性构建":同一个页面有时导出 33 个节点、有时只有 14 个空壳→改用"截图+OCR",彻底不依赖无障碍树。② adb shell input tap 会被该页面忽略→所有点击改为 input swipe x y x y 120~130(原地按住再松开)。③ "答案对却点错":大模型思考的 3~5 秒里 H5 重排版,先前算好的坐标失效→点击前再截一次屏,按答案文字重新定位选项行。④ 退出确认框出现时倒计时照走→按 OCR 找到"取消"尽快点掉,保证当轮答完。⑤ 填空题在无障碍树里没有输入框→用 OCR 定位"第 N 空",经 ADBKeyBoard 广播输入后回读核对,共四层兜底。⑥ 中文路径、PowerShell 5.1 按 ANSI 解析脚本、BOM、subst 虚拟盘、adb 二进制重定向等工程坑,全部固化成文档与自检脚本(dev_check/dev_precheck/selftest)。
结果:题库从 6 题增长到 171 题(含官方核实项、结构破损 0);首轮实战 6 题 6 分、160 秒内完成(约 18 秒/题,命中题库后 2~4 秒/题);判断、多选、填空三种题型全部实测通过;GUI 与 CLI 打包自检全过(--selftest/--qtsmoke 退出码 0);每一步都有日志与截图可复盘。