资讯详情

挂机脚本技术骨架:看判动、图像识别与UI自动化落地

📅 2026/10/31 23:35:55 | 华诺云谱 👁 阅读
挂机脚本技术骨架:看判动、图像识别与UI自动化落地
挂机脚本、辅助工具、传奇类手游的自动化这几个词凑在一起几乎每个月都会有人来问我一次。上周又有个做前端的哥们甩了张截图过来满屏的怪、血条、技能冷却圈问我能不能照着写一套让角色在他上班的时候自己升级、自己捡装备。他的诉求特别朴素朴素到我一眼就能看出他没想过后面会发生什么。这类需求我见得多了问的人背景也杂有刚学Python三个月的也有做了五六年后端的老手但大家最初的想象都差不多——觉得挂机工具嘛不就是让程序代替手指点屏幕真动手之后才会发现难点从来不在“怎么点”而在“点什么、什么时候点、点错了怎么办、被封了怎么算”。这篇东西我不打算给你一段拿来就能跑的成品那种东西生命周期通常按天算而且对谁都没好处。我想干的是把这类工具的技术骨架彻底拆开它到底由哪几块组成每块用什么方案实现为什么大多数人的第一版活不过三天以及同一套技术能力还能挪到什么更长久的地方去。全文偏实战代码是可运行的通用片段跑在我自己的测试环境和自建Demo上你可以直接抄去练手但请别往别人运营的服务里塞——用户协议里写着呢账号是你自己的风险也是。1. 拆开看挂机工具其实只有“看、判、动”三步很多人一上来就想写代码写了两百行发现跑不通然后开始怀疑人生。问题往往出在没做拆解。任何一套自动化工具不管你叫它辅助脚本、挂机工具还是RPA机器人剥到最里面都只有三个动作在循环看当前是什么状态判断该做什么执行这个动作。人打游戏也是这三步只是人的“看”和“判”在脑子里完成快到你意识不到。1.1 从人的操作习惯反推程序需要的能力你先回忆一下自己手动刷本的流程。眼睛扫一眼屏幕确认血条还够、技能好了、面前有怪大脑判断现在该放技能还是该喝药手指落在屏幕某个位置。整个过程大概三百毫秒其中真正花在“看”上的时间极少因为你的视觉系统会自动过滤掉无关信息只盯着血条和技能图标。程序没有这个能力。它看到的是一张两百万像素的完整截图怪物、地面、UI、飘字、光效全在里面没有任何东西告诉它“血条在这里”。所以第一件事必须是缩小关注范围——要么靠控件树精确定位要么靠图像匹配找出目标区域要么干脆把截图裁成几块分别处理。这一步做不好后面全是白费力气。判断环节的难点在于状态是有歧义的。人看到血条剩三分之一会喝药程序看到的是什么是一个红色像素占比。你得定义清楚“占比低于多少算危险”还得考虑不同分辨率下这个占比会不会变。我见过太多脚本卡在这里写死了一个坐标去点血条区域换个手机就点到了技能上角色当场表演原地罚站。动作环节反而是最简单的ADB一条命令、控件一个click()就完事。但也别高兴太早动作有个隐藏要求——幂等性。也就是说你点了一次没生效重试一次不能变成点两次。技能连点两下可能进冷却商店连点两下可能买两份。这个坑我在早期项目里踩过一晚上刷掉了一堆资源心疼得很。1.2 为什么“循环”比“单次点击”难十倍单次点击是脚本循环才是工具。这两者的差距不在代码量在异常处理。单次点击的假设是界面在预期位置、网络通畅、没有弹窗、没有断线重连、没有活动公告糊你一脸。循环跑一千次上面每一条都会至少发生一次。弹窗是最常见的杀手一个“今日签到”提示框盖住半个屏幕你的脚本还在傻乎乎地找怪找了一晚上什么都没找到日志里干干净净因为它“没报错”只是每次匹配都失败而已。所以一个能长时间跑的循环必须包含四样东西超时机制这一步最多等多久、重试上限失败几次就换策略、兜底动作实在不行就退回主界面重来、状态快照失败时把截图存下来事后能复盘。这四样东西加起来代码量通常是你核心逻辑的三倍但正是它们决定了你的工具能跑一小时还是能跑一晚上。经验之谈我写这类循环有个固定习惯主循环里任何一步只要超过设定时限没拿到预期状态立刻截图存盘并打印当前控件树。后面排查问题时这些快照比重跑十次都管用。2. 三条技术路线的成本账截屏识别、控件树、内存与协议同样是“看”实现方式差了十万八千里。业内常见的有三条路成本、稳定性、门槛完全不是一个量级。选错路线你会在错误的坑里挖很久。2.1 截屏加图像识别门槛最低也最脆这条路的核心是定时截一张图用模板匹配或特征点匹配找出目标图标的位置然后算出坐标去点。工具链就是 ADB或系统截图接口加 OpenCVPython 环境下十行代码就能跑通第一个 Demo。它的好处太明显了与目标应用完全解耦。你不需要知道对方用什么引擎、什么框架甚至不需要它能被调试。屏幕上有这个图标我就能找到它。这对学习来说是最友好的入口因为你可以拿任何一个App练手不涉及任何侵入性操作。坏处同样明显。第一它只认像素不认语义。图标换个皮肤、加个描边、透明度变了匹配度就从 0.95 掉到 0.6你的阈值一卡直接失效。第二速度受截图限制。一次全屏截图在安卓上通常要 100 到 300 毫秒加上匹配计算一个完整循环轻松超过半秒。这在快节奏场景里是致命的——你判断出来该放技能的时候怪已经被别人打死了。第三它对动态画面极其敏感。技能特效、飘字、粒子光效全都会干扰匹配结果你截十张图能匹配上六张就算不错。我个人的定位很明确这条路线适合做低频、界面稳定、容错高的自动化比如每日签到、固定流程的页面巡检。指望它跑高强度战斗属于用螺丝刀砍树。2.2 控件树读取稳但被大量应用主动封死安卓的 Accessibility 体系会把界面上的可交互元素组织成一棵树每个节点带文本、描述、类名、坐标。拿到这棵树你就不用猜了——直接按文本或ID定位稳得一批。uiautomator2 这类库就是干这个的一行d(text确定).click()比图像匹配优雅太多。问题在于这套体系对渲染型界面基本无效。如果一个应用是用游戏引擎或者自绘控件把整个界面画在一张画布上控件树里往往只有一个巨大的 SurfaceView 节点什么信息都没有。这不是谁故意为难你纯粹是渲染方式决定的。另一个现实是出于安全考虑很多应用会主动关闭无障碍支持或者对关键页面做屏蔽处理。你打开控件树一看空空如也。所以这条路线的适用面其实比想象中窄它擅长的是原生控件构成的标准界面比如系统设置、表单、列表页。这也正是它在自动化测试领域混得风生水起的原因——被测应用就是自己写的控件树当然是完整的。2.3 内存与协议层效率最高代价也最高第三条路是直接读进程内存或者重放网络协议。这条路的技术含量最高效果也最“真”——因为它拿到的不是屏幕上的像素而是应用内部的状态值血量、坐标、冷却时间全是精确数字没有任何识别误差。但我必须把话说透这条路的风险等级和前面两条完全不在一个层面。它需要突破进程隔离、需要处理数据加密与校验、需要面对随时可能变化的内部结构任何一个环节都涉及对他人系统的实质性侵入。在别人的服务上做这件事轻则账号不保重则惹上麻烦。而且从纯技术角度看它的维护成本高得离谱——对方一次版本更新你的偏移量全部作废一夜回到解放前。我把这条路摆出来是为了让技术版图完整不是为了推荐。真正值得学的部分在别处理解协议分层的思路、理解数据校验的意义这些知识放到接口测试、协议分析、自己的服务端调试里都是硬通货。路线实现门槛稳定性维护成本适用场景截屏 图像识别低中低中界面稳定、节奏慢、容错高的流程控件树读取中高低原生控件构成的标准界面内存/协议层高视实现而定极高仅限自有系统的调试与分析3. 为什么照抄坐标的脚本活不过三天新手最容易犯的错是把第一次成功跑通的坐标当成真理写死。tap(540, 1200)在自己手机上跑得好好的换台设备、换个分辨率、甚至只是系统更新改了状态栏高度全线崩塌。这不是运气问题是必然。3.1 分辨率、DPI与刘海屏的三重错位安卓设备的屏幕参数差异大得惊人。同样是 1080 宽的屏幕有的逻辑分辨率是 1080有的是 720 缩放上来的状态栏高度从 24dp 到 48dp 不等还有挖孔屏、曲面屏、手势条每一种都会让实际可用区域发生偏移。解决办法只有一个永远不要用绝对坐标用相对坐标或者语义定位。具体做法有三种按推荐程度排序。第一种是按比例换算。记录坐标时同时记录当时的屏幕宽高运行时按当前屏幕宽高等比缩放。这个方法简单但对非等比变化比如宽高比不同会失效。第二种是限定搜索区域ROI。不要求全屏匹配先在固定的相对区域里找。比如技能栏永远在屏幕下方 15% 的范围内那就只截这一块去匹配。搜索范围小了误匹配率骤降速度也快了好几倍。这是性价比最高的一招。第三种是多尺度匹配。模板图按 0.8 到 1.2 的比例生成一系列缩放版本逐个匹配取最高分。代价是耗时翻几倍一般只在对精度要求极高、且设备分辨率不确定的场景下用。屏幕 1080x2340 → 技能按钮中心 (540, 2100) 换到 720x1560 → 等比缩放 (360, 1400) 换到 1080x2400长了一截→ 按宽度缩放得 (540, 2154)纵向偏差 54px最后那 54 像素的偏差就是很多人脚本“看起来能跑但偶尔点空”的根源。用 ROI 加相对定位能吃掉大部分误差剩下的靠点击区域放大——按钮中心点不准没关系只要落点在按钮范围内就行。3.2 界面的随机化设计与它带来的连锁反应很多产品会在界面上做位置扰动同一个按钮这次在左边下次在右边弹窗的出现时机随机活动入口的位置每天变。这些设计本来是为了运营和防误触副作用是让基于固定坐标的自动化彻底失效。应对办法不是硬刚而是把定位逻辑从“位置”换成“特征”。图像匹配本身就是特征定位天然免疫位置扰动只要你匹配的是按钮本身而不是它的坐标。同理控件树定位也是按文本和属性找位置变了照样能找到。真正的麻烦在于目标不存在。你今天要点的按钮明天可能因为活动下线直接消失了。这时候脚本会一直等等到超时然后重试然后超时循环往复。所以每个查找动作都必须有“找不到”的分支而不是死等。我的习惯是找不到就降级降级再找不到就退出当前流程回到主界面主界面认不出来就整段重启。三级兜底能救回九成的意外。3.3 行为节律机器比人“太完美”反而是破绽这一点纯从技术角度聊聊因为它解释了为什么很多脚本跑得越久越危险。人的操作有三个特征间隔不均匀、位置有抖动、有疲劳和中断。点击间隔服从一个偏态分布大多数在 200 到 500 毫秒之间偶尔会有几秒的停顿喝水、看消息落点在按钮范围内随机散布不会每次都精准命中中心连续操作一两个小时后节奏会明显变慢。机器如果按固定间隔、精准坐标、24 小时不间断地跑这三个特征全都不符合。从数据角度看这种模式和人类行为的距离非常远识别起来毫无难度。这也是为什么“加随机延时”“加坐标抖动”成了这类工具的标配——不是为了效果更好是为了看起来没那么假。但我得说清楚这是一场注定不对称的博弈。你加的扰动是有限度的稍有常识的检测手段都能穿过表层特征去看更深层的东西——设备指纹、操作序列的语义合理性、异常时段的活跃度。你把延时调得再像人一个连续 72 小时没下线、每秒精确处理一次状态的角色依然一眼就能被挑出来。所以与其研究怎么更像人不如早点想明白这条路的天花板在哪。4. 用公开工具链搭一套能跑的自动化骨架前面讲的都是判断这一节讲怎么动手。我用一套完全公开、合法的工具链搭个骨架跑在安卓模拟器和我自己写的一个 Demo App 上。代码你可以直接复现跑通之后你会对“看、判、动”有实感这比看十篇文章都值。4.1 环境准备模拟器、ADB与两个Python库准备四样东西。一台装了安卓模拟器的电脑自带 ADB 就行Python 3.9 以上pip install uiautomator2 opencv-python numpy以及一个用来练手的目标应用——建议自己写个简单的列表页或者拿系统设置练手。模拟器启动后先确认连接adb devices # 应该能看到 emulator-5554 device python -m uiautomator2 init # 给模拟器装 atx-agent成功后 u2.connect() 才能用 python -c import uiautomator2 as u2; du2.connect(); print(d.info)最后一行能打印出设备信息说明链路通了。这一步最容易卡住的地方是端口占用和多设备冲突如果你开了两个模拟器u2.connect()不带参数会随机连一个稳妥做法是显式传序列号u2.connect(emulator-5554)。我选 uiautomator2 而不是 Appium理由很实际前者是纯 Python 库装上就能用启动开销小适合写短平快的自动化脚本Appium 走 WebDriver 协议功能更全、生态更规范但启动服务和会话管理对新手来说太重了。做测试用例管理选 Appium做一次性自动化任务选 uiautomator2这个取舍我用了几年没变过。4.2 图像匹配的几个参数决定成败OpenCV 的matchTemplate有六种算法实际用下来TM_CCOEFF_NORMED最稳因为它做的是归一化相关匹配对整体亮度变化有一定容忍度结果落在 0 到 1 之间阈值好设。import cv2 import numpy as np def locate(screen_path, template_path, threshold0.85, roiNone): 在屏幕截图中定位模板返回中心坐标和匹配分数 img cv2.imread(screen_path) tpl cv2.imread(template_path) if img is None or tpl is None: return None # 限定搜索区域大幅降低误匹配 if roi is not None: x1, y1, x2, y2 roi img img[y1:y2, x1:x2] else: x1 y1 0 res cv2.matchTemplate(img, tpl, cv2.TM_CCOEFF_NORMED) _, max_val, _, max_loc cv2.minMaxLoc(res) if max_val threshold: return None h, w tpl.shape[:2] cx x1 max_loc[0] w // 2 cy y1 max_loc[1] h // 2 return {x: cx, y: cy, score: round(float(max_val), 3)}三个参数需要你亲自调。threshold阈值0.8 是个起点不是标准答案。背景干净、图标固定0.85 到 0.9 都行背景杂乱、有半透明遮罩可能得降到 0.75。阈值定太高会漏检定太低会误检而误检比漏检可怕得多——漏检只是没动作误检是点错地方可能引发一连串连锁反应。我的习惯是从 0.9 往下试找到第一个能稳定命中的值再减 0.03 当运行阈值。模板图的选择千万别直接截一大块图当模板。模板越小越精确但太小又容易误匹配。经验尺寸是目标元素的 60% 到 80%并且只保留特征最明显的部分——比如按钮上的文字或图标把边框和背景裁掉。模板图最好从多张截图里裁同一块区域挑差异最小的那张。ROI 的设置这个前面提过但要强调它的收益。全屏 1080x2340 的匹配耗时可能是限定区域的三到五倍。把搜索范围压到目标可能出现的区域内速度快了误匹配率也降了是双赢。4.3 一个带超时与重试的主循环长什么样下面这个骨架把“看、判、动”和四件套超时、重试、兜底、快照串起来。它不针对任何具体应用你可以把decide()换成自己的判断逻辑。import time import os class Runner: def __init__(self, device, shot_dir./shots): self.d device self.shot_dir shot_dir os.makedirs(shot_dir, exist_okTrue) self.fail_count 0 def snap(self, tagnow): path os.path.join(self.shot_dir, f{tag}_{int(time.time())}.png) self.d.screenshot(path) return path def safe_click(self, x, y, max_retry2): 点击带重试但不盲目连点 for i in range(max_retry): self.d.click(x, y) time.sleep(0.35) # 给界面反应时间 if self.verify(): # 必须验证结果 return True return False def verify(self): 子类覆写确认上一步动作是否生效 return True def run_once(self): try: state self.observe() # 看 action self.decide(state) # 判 if action is None: time.sleep(1.0) return ok self.safe_click(*action) # 动 if not ok: self.fail_count 1 self.snap(ffail_{self.fail_count}) if self.fail_count 5: self.recover() # 兜底 self.fail_count 0 except Exception as e: self.snap(exception) print(f[异常] {e}) def recover(self): 兜底回到已知的初始状态 self.d.press(back) time.sleep(0.5) self.d.press(home) time.sleep(0.5)这里有两个设计我坚持了很多年值得单独说。点击后必须验证。safe_click里的verify()不是可选项。点击是一个请求不是结果界面可能没响应、可能响应延迟、可能被弹窗拦截。少了验证这一步你的脚本就是在盲操作错了也不知道。重试不等于连点。很多人写重试就是for i in range(3): click()中间不等待。这在游戏里是灾难三次点击可能触发三个不同的东西。正确做法是每次点击后等待一个合理的反应时间再检查状态状态对了就退出重试。4.4 日志、截图快照与失败回放这一节讲的东西不带技术含量但决定了你的调试效率是十分钟还是一整天。日志不是print(点击成功)。有效的日志至少包含时间戳、当前动作、耗时、结果、关键参数。格式不用花哨一行一条方便 grepdef log(action, cost, result, extra): ts time.strftime(%H:%M:%S) print(f{ts} | {action:16} | {cost*1000:5.0f}ms | {result:8} | {extra})截图快照要分两类别混在一起。定期快照比如每三十秒一张用来观察整体趋势异常快照用来定位具体问题。后者才是重点一定要在失败发生的当下截不能等循环结束再截那时候界面早变了。我的做法是异常快照文件名里带上失败类型和重试次数比如fail_5_match_none_1723456789.png事后扫一眼文件名就知道当时卡在哪个环节。还有个小技巧把失败时的控件树也 dump 一份。d.dump_hierarchy()能拿到完整 XML配合截图一起看基本能还原现场。这个习惯帮我省掉的排查时间按小时算都不止。5. 这套技术真正的用武之地写到这里我猜有人要问那到底能不能做我的回答是——技术本身是中性的但落地场景差别巨大。你在这套技术里练出来的能力是真金白银截图、图像识别、状态机、异常处理、日志体系这些东西换个场景就是生产力。下面两个方向是我这些年真正在用的。5.1 应用的UI回归测试这是最正统的落点也是这套技术最舒服的地方。你写的应用你自己测控件树完整界面可控不需要跟任何检测对抗。具体怎么落地把本文第 4 节那套骨架稍微改一下把“无限循环”换成“用例列表”就变成了一个可用的冒烟测试框架。CASES [ {name: 启动页加载, steps: [等待首页元素出现, 断言标题文本正确]}, {name: 列表页滚动, steps: [滑动到底部, 断言加载更多按钮出现]}, {name: 表单提交, steps: [填入三个字段, 点击提交, 断言提示语为成功]}, ]关键在于断言。自动化测试和自动化操作的区别就在这里测试必须能判定“对还是错”。元素找不到算失败文本不匹配算失败流程走不通算失败。有了断言它才是一套测试而不是一个只会点屏幕的机器人。我维护过的一套用例集大概两百多条跑一遍十二分钟接在每次提测前自动触发。它的价值不在于替代人工而在于把人从重复点击里解放出来去做真正需要判断力的事情——比如设计边界用例、分析偶发失败的原因。5.2 重复性人工操作的自动化第二个方向更贴近个人效率。凡是“每天固定时间、对着固定界面、点固定几个按钮”的活都可以交给程序。比如每天早上导出某个后台的报表、批量给图片打水印、定时把某些数据从A系统搬到B系统。这类场景的特点是频率低、流程固定、出错容忍度高。正好是图像识别路线的舒适区。你甚至不需要写得多精巧一天跑一次的任务跑五秒和跑三十秒没区别只要结果对就行。我自己的习惯是给这类自动化加一个**“先试运行再执行”**的开关。试运行时只截图、只匹配、只打印将要点击的坐标不真正点击。确认一遍日志没问题再切到执行模式。这个习惯救过我不少次——有些后台的按钮位置会因为窗口大小变化而移动试运行一眼就能发现。5.3 给自己的技术栈做个取舍建议如果你刚入门我的建议顺序是这样的。先学 uiautomator2 或 Appium用自己写的 Demo App 练把它跑通、跑稳。这一步你会学到状态机的设计、超时重试的写法、日志的组织方式这些是通用能力。再学 OpenCV 的图像匹配重点是理解模板选择、阈值调优、ROI 裁剪三件事。这一步不需要多深的数学功底但需要你亲手调几十次参数建立手感。最后再去看协议和内存的东西而且要带着“理解原理”的目的去看别急着动手。理解了数据是怎么在客户端和服务端之间流转的、校验是怎么做的你对整个系统的认知会上一个台阶。这部分知识用到接口调试、性能分析、自己的服务端优化上价值远超一个挂机脚本。至于要不要真的做一个跑在别人服务上的工具这是你的选择我不替你决定但事实得摆在桌面上绝大多数商业运营的手游用户协议里都明确禁止第三方自动化工具检测手段在持续升级收益和代价完全不成比例。我见过太多人花两周写出来的东西跑了两天号没了脚本也没心思维护了。我自己现在的做法是这套技术只跑在两种地方——我自己写的应用和明确允许自动化的内部工具。其他场景我会直接告诉你性价比太低。最后分享一个我觉得最值钱的小习惯。不管你做什么自动化第一次跑通之后先别急着优化把整个流程完整录一遍屏幕然后一帧一帧看回放。你会看到很多你以为不存在的问题点了没反应、界面闪了一下、有个提示框一闪而过。这些问题在正常运行时不报错但会在某个随机的时刻要你的命。录屏十分钟胜过调试十小时——这话我逢人就讲。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑