ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AutoJS脚本工程化解析:从无障碍服务到稳定签到任务

AutoJS脚本工程化解析:从无障碍服务到稳定签到任务 简介收集各种薅羊毛脚本.zip 是一份基于 AutoJS 的 Android 自动化脚本合集面向希望借助工具参与平台优惠活动、减少重复操作的用户。压缩包共包含 79 个文件以 73 个 JavaScript 脚本为主涉及签到、做任务、领奖励等常见场景另附 2 个 Python 辅助脚本、Shell 脚本以及配置和说明文档整包仅 1.44MB便于下载和筛选。目前已有 288 人学习下载。脚本集合覆盖多个平台和活动如主流电商、阅读、视频等文件名直观便于按需选用。通过阅读这些代码可以了解 AutoJS 的触摸模拟、定时任务、网络请求等 API 用法也能学习如何在脚本中处理异常、维护登录状态。需要留意的是自动化薅羊毛存在账号风控与合规风险使用前应仔细阅读平台规则避免因滥用导致封号。适合有一定 JavaScript 基础、想提升自动化效率或研究脚本编写的开发者参考。1. 拆开一份几十个 ym_ 脚本的 AutoJS 项目包先看到什么这份收集各种薅羊毛脚本.zip解开之后bienaoccc_hym-main目录里躺着几十个命名整齐的.js文件ym_jd.js、ym_meituan.js、ym_elm.js、ym_wangyiyanxuan.js按平台缩写区分的主脚本挤满了第一屏旁边还跟着sendNotify.py、sendNotify.js、ql.js这类支撑文件。说句让多数人意外的话这类脚本包真正容易翻车的地方从来不是签到逻辑本身而是脚本开头没等无障碍服务就绪、选择器超时设置不合理、App 弹窗把流程打断之后没有兜底。下面按 AutoJS 运行机制、脚本骨架、工程化改造三条线来拆适合已经碰过一点 Android 自动化、想把脚本从“能跑”做到“长期不挂”的工程师。2. AutoJS 运行原理控件树、选择器与引擎边界2.1 无障碍服务、脚本引擎和任务触发三层分工AutoJS 不是把 JavaScript 直接塞进目标 App 进程里它本身是一个常驻设备的前台与后台混合应用通过 Android 无障碍服务拿到当前窗口的 View 树。脚本引擎在 AutoJS 进程内解析 JS把text(签到).click()这类调用翻译成“在 View 树上找到匹配节点再注入一个点击事件”。整个体系可以拆成三层来理解。第一层是无障碍服务。系统把当前界面的控件结构序列化成节点树脚本里所有 find 系列方法都在这棵树上做遍历搜索。凡是uiautomator dump能导出界面结构的应用AutoJS 的选择器基本就能定位到控件。第二层是 JavaScript 运行时和封装 APIlaunchApp、currentPackage、device、files这些全局对象由 AutoJS 提供脚本只负责编排调用顺序。第三层是调度AutoJS 自带的定时任务、setInterval以及外部ql.js入口本质都是在不同时间点拉起同一批业务函数。这三层决定了脚本的能力边界它擅长找按钮、填输入框、模拟滑动它不擅长判断图片内容也不能脱离无障碍服务工作。排错时可以顺着这条链走——脚本说找不到控件先去看系统是否关闭了 AutoJS 的无障碍授权再去看目标页面是否真的渲染出了对应节点。2.2 选择器 API 的优先级text、desc、className 怎么取舍分析这类项目时第一步总是先搜findOne和findOnce的调用。两者有一个容易被忽略的差别无参数的findOne()会一直阻塞等待findOne(3000)才会在超时后返回 nullfindOnce()则立即返回适合做页面状态的瞬时判断。方式返回类型阻塞行为推荐用途findOne()UiObject不传毫秒则无限等待主流程里的强依赖控件findOne(timeout)UiObject/null最多等 timeout 毫秒日常脚本里最常用必须给超时上限findOnce()UiObject/null立即返回判断页面是否具备某个特征find()UiCollection持续监听可配untilFind()需要批量处理的控件集合选择器本身的匹配优先级一般按这个顺序尝试text精确文本、textContains包含文本、desc语义描述、最后才是className加属性过滤。前两者可读性最好App 改版时一眼能看出哪一条失效desc多出现在图片按钮和 WebView 内部控件上className太宽泛同一屏里往往命中几十个节点只适合当兜底。// 四种定位方式按优先级依次尝试命中即返回 function pickNode(selectorList, timeout) { for (let i 0; i selectorList.length; i) { let node selectorList[i].findOnce(timeout); if (node) return node; } return null; } var target pickNode([ text(立即签到), textContains(签到), desc(签到), className(android.view.View).clickable(true).depth(5) ], 3000);这段代码给一串选择器排队先命中就直接返回timeout是单次findOnce的等待上限不是整个数组的总上限。写在最前面的text(立即签到)匹配最精确代价是文案稍微改动就会失效textContains(签到)用包含关系扩大容错最后的className组合虽然抗文案变化却可能命中页面里其他可点击区域误触风险最高所以永远放在最后一位。2.3 坐标点击和控件点击的边界坐标点击的写法很短click(540, 1200)一行就够但它把脚本和屏幕像素绑死。AutoJS 提供setScreenMetrics(1080, 2400)可以按设计分辨率做等比换算可是 Android 的全面屏手势、底部导航栏高度、系统字体缩放都会造成偏移。控件点击基于 View 树和屏幕尺寸无关这是签到脚本普遍选择节点查找而不是坐标的根本原因。不过控件方式也有明确边界。应用内用 Canvas 自绘按钮或者页面整体跑在 WebView 里且前端没有暴露可访问性语义时节点树里找不到可操作对象。这类场景只能退回图片识别或坐标并且必须接受一个事实稳定性和适配成本都会高一个量级。判断标准很直接——uiautomator dump导出的 XML 里能看到目标控件就走节点方案看不到才考虑坐标。3. 从 ZIP 到可运行工程文件布局、入口脚本与通知链路3.1 解压后先判断项目结构再动手拿到 zip 压缩包很多人第一反应是找密码破解工具或者直接把ym_jd.js塞进 AutoJS 运行。实际更稳妥的顺序是先验证文件完整性再观察目录结构。先用解压软件看一下压缩包是否带加密标记、文件列表是否完整尤其注意README.md和.idea这些辅助文件是否齐全——它们往往记录了项目原本的运行方式和 IDE 工程配置。一个缺了.idea但脚本完整的包通常没问题一个连README.md都没有的包就要多留一个心眼。解压后重点看这几类文件的分布文件角色推断运行时的作用bienaoccc_hym-main仓库解压出来的根目录所有脚本的统一引用路径ym_*.js平台签到主脚本AutoJS 直接执行的对象sendNotify.py通知脚本的 Python 版设备装了 Python 时优先使用sendNotify.js通知脚本的 JS 版纯 AutoJS 环境也能推送状态ql.js青龙面板适配入口让服务端任务系统能拉起脚本.idea/git_toolbox_prj.xmlJetBrains IDE 工程配置说明原项目在桌面 IDE 中维护ym_lb_elm.shShell 启动脚本通过 adb 或终端触发任务目录里同名文件出现多份并不奇怪sendNotify.js和sendNotify.py共存通常是同一份逻辑在不同运行环境下的双实现。AutoJS 的 JavaScript 引擎可以直接跑 JS 版通知模块设备没装 Python 时也能走完整链路ym_lb_elm.sh则是给 adb 远程调用准备的入口里面一般就是一行am start或者调用 AutoJS 主程序的命令。3.2 ql.jsAutoJS 与服务端调度之间的适配层青龙面板这类任务系统跑在 Linux 服务端没法直接调用 AutoJS 的无障碍服务。ql.js在这里起的是协议翻译作用。常见做法有两种一种是把 AutoJS 脚本改造成可导出模块青龙通过 HTTP 或 adb 远程触发设备端 AutoJS 主程序执行另一种是脚本同时维护一条“纯服务端分支”不依赖 UI直接调目标平台接口把整个流程从设备端搬到服务端。第二种更稳定但需要逆向接口签名维护成本高得多。zip 里的ym_*.js仍以控件操作为主说明第一类是主线。// ql.js 的兼容层写法由青龙定时调用再由它串起各平台任务 const taskMap { jd: require(./ym_jd.js), meituan: require(./ym_meituan.js), elm: require(./ym_elm.js), }; exports.start function (platform) { if (platform) { let task taskMap[platform]; if (task typeof task.run function) { return task.run(); } console.log(unsupported platform: platform); return; } // 未指定平台时串行跑一遍主任务 Object.keys(taskMap).forEach((key) { try { taskMap[key].run(); } catch (e) { console.error(key failed: e.message); } }); };这里的关键约束有两个所有ym_*.js都要导出run()方法接口统一才好编排forEach里用try/catch包住每次调用一个平台挂了不能阻断后续任务。青龙面板只需在 cron 配置里执行node ql.js start或node ql.js start jd就能选择全量跑还是只跑某平台。3.3 sendNotify.py任务状态怎么抵达手机sendNotify.py的价值在于让脚本结束后的状态可感知。脚本在设备上跑人不可能一直盯着屏幕通知才是闭环。Python 版通知脚本一般读取命令行参数再通过 Bark、Server酱、Telegram Bot 等渠道推送。import sys import json import requests def send_msg(payload): # payload 里包含 title/content 两个字段 resp requests.post( https://api.day.app/YOUR_BARK_KEY, json{ title: payload.get(title, auto task), body: payload.get(content, ), }, timeout10, ) return resp.status_code 200 if __name__ __main__: data json.loads(sys.argv[1]) ok send_msg(data) print(push ok if ok else push fail) sys.exit(0 if ok else 1)这个脚本用sys.argv[1]接收 AutoJS 传过来的 JSON 字符串requests.post调 Bark 的接口完成推送返回码 0 或 1 让调用方知道推送是否成功。AutoJS 侧调用时要注意 shell 转义中文和引号最好先encodeURIComponent再拼接否则 Python 拿到的参数会被截断。4. 签到脚本的通用骨架唤醒、定位与异常分支4.1 一个最小可运行的签到流程梳理这类项目时会发现ym_*.js尽管平台不同主体流程高度一致差异只在启动方式、签到按钮文案和弹窗特征。把流程提炼出来就是一个固定骨架以下写法可以套用到大多数签到场景。auto.waitFor(); setScreenMetrics(1080, 2400); const APP_NAME 京东; const FIND_TIMEOUT 5000; // 亮屏解锁避免黑屏状态下操作 if (!device.isScreenOn()) { device.wakeUp(); sleep(1200); } // 按应用名拉起目标 AppAutoJS 会做包名匹配 launchApp(APP_NAME); sleep(2000); // 文本和语义描述双通道找签到入口 let node textContains(签到).findOnce(FIND_TIMEOUT) || descContains(签到).findOnce(FIND_TIMEOUT); if (node) { node.click(); console.log(sign ok); } else { console.error(sign button not found); }代码按顺序做了四件事等待无障碍服务、唤醒屏幕、启动 App、查找签到控件并点击。device.wakeUp()处理锁屏状态下脚本空跑的问题因为无障碍事件在息屏时不可靠。textContains(签到).findOnce(FIND_TIMEOUT)给单次查找定下 5 秒上限||短路保证文本匹配失败才走 desc 通道不会两个通道各点击一次。4.2 等页面稳定而不是盲目叠加固定 sleep新手容易写出一堆sleep(3000)这在页面加载快时浪费大量时间在页面加载慢时又起不到保护作用。更可控的写法是轮询等待某个标志性控件出现超时才放弃。// 循环轮询直到目标节点出现或超过最大等待时间 function waitForNode(selector, maxWait) { let waited 0; while (waited maxWait) { let n selector.findOnce(); if (n) return n; sleep(500); waited 500; } return null; } let entry waitForNode(textContains(领取奖励), 20000); if (!entry) { console.error(timeout waiting for page); }waitForNode每 500 毫秒检查一次和一次性findOne(20000)相比它可以在等待期间插入状态输出也方便在条件变化时提前退出。实际使用中最大等待时间按页面复杂度配置纯原生页面 5 秒足够WebView 或启动广告较多的页面再放宽到 20 秒。4.3 两类高频异常分支升级弹窗和登录态失效应用升级引导和登录过期是打断脚本流程最多的两类异常。处理思路分两步在每步关键操作前检查这两个特征控件发现后要么点关闭、要么终止任务不能带着异常状态继续往下走。function checkBlockers() { // 升级弹窗通常有“稍后”或关闭按钮 let upgrade textContains(立即升级).findOnce() || textContains(稍后再说).findOnce(); if (upgrade) { textContains(稍后再说).click(); return true; } // 登录态失效时继续点击只会触发登录页 let login textContains(立即登录).findOnce() || textContains(登录已过期).findOnce(); if (login) { console.error(login expired, abort task); return true; } return false; }这个函数返回“当前页面有没有需要停下来的异常”。上层循环每轮操作后调用一次返回 true 就重试或退出。注意textContains匹配比较宽泛如果目标页面本身常有“登录”二字就要改用“立即登录”“登录已过期”这种更精确的词避免误伤正常流程。5. 做长期任务时怎么保持稳定随机延时、退避重试与串行纪律5.1 随机间隔参数怎么设计固定间隔的自动化操作在风控系统眼里有明显的节奏特征。AutoJS 的random(a, b)用来生成区间随机数配合sleep就能把操作间隔打散。function humanPause() { // 800~2500ms 随机停顿模拟人工操作节奏 let pause random(800, 2500); console.log(human pause pause ms); sleep(pause); }区间设计有一个常见误区把范围拉得越大越安全实际影响是单次任务时长无法预估长时间无法完成反而显得异常。常用的区间是 800ms 到 3000ms在关键提交动作之前再额外加一次 1.5 到 2 秒的停顿因为人手在关键操作前确实会犹豫片刻。随机延时并不会让你完全免于风控真正的作用是让行为特征不至于一眼被识别为程序。5.2 指数退避失败时不要立刻猛敲控件找不到后立刻重试三次每次都间隔 1 秒这是脚本里最常见的写法。但连续快速重试恰好是自动化脚本最明显的特征之一。合理的做法是按指数增长间隔退避同时给重试次数封顶。function runWithRetry(taskFn, maxTimes) { let lastError null; for (let i 1; i maxTimes; i) { try { let result taskFn(); if (result) return result; } catch (e) { lastError e; console.warn(attempt i failed: e.message); } // 退避间隔按 2 的幂增长最多停 10 秒 let backoff Math.min(1000 * Math.pow(2, i - 1), 10000); sleep(backoff); } throw lastError || new Error(retry exhausted); }这个函数值得强调的不是退避本身而是封顶逻辑。Math.min(..., 10000)把单次等待限制在 10 秒以内避免脚本在无人值守时长时间停在 sleep 里。taskFn()返回 true 才认为成功否则继续下一轮。指数退避配合 3 次上限对偶发的页面加载延迟足够宽容又不会在 App 真正出问题时浪费过多时间。5.3 串行为主少开并行AutoJS 的threads.start()能开多线程但这不意味任务应该用并发跑。一个 App 只有一个前台界面两个线程同时找“签到”控件先到先得后到的要么找不到节点、要么点错位置。所以多个平台任务之间默认用串行队列。const tasks [./ym_jd.js, ./ym_meituan.js, ./ym_elm.js]; for (const path of tasks) { const mod require(path); console.log(start path); try { mod.run(); } catch (e) { console.error(path aborted: e); } humanPause(); } console.log(queue finished);串行的好处除了避免界面竞争还让每次任务的结果天然可追踪——上一个任务结束下一个才启动。如果设备资源充足可以考虑把不同平台拆到多台设备上并行每台设备只跑一个任务既避开单设备并发问题又能摊平总耗时。6. 收编进定时任务前的三个工程化细节6.1 cron 表达式不要整点扎堆把脚本交给青龙面板或 Linux crontab 之后执行时间要刻意错峰。多个任务同时启动即使运行在不同 App 上也会因为设备同一时间只有一个前台窗口而产生队列等待。cron 里给每个任务分配不同分钟段比全部安排在 8:00 更合理。# 京东签到定在 8 点 10 分 10 8 * * * node /data/scripts/ql.js start jd # 美团签到比 jd 晚 15 分钟 25 8 * * * node /data/scripts/ql.js start meituan错峰设置还要考虑任务执行时长。如果一个任务可能耗时 20 分钟下一个任务的触发时间至少要比它晚半个小时否则队列会被上一个任务阻塞。6.2 账号配置从脚本里拆出去直接写在ym_*.js里的账号信息会让脚本没法分享、也没法多账号复用。统一改成从外部 JSON 读取脚本本身保持无状态。[ {platform: jd, username: u1, password: p1}, {platform: meituan, username: u2, password: p2} ]对应在 AutoJS 里就是文件系统读取加 JSON 解析运行时按当前任务名选出对应账号。改配置时只动 JSON不动代码出问题也好定位。6.3 加一个空跑校验入口用最小成本抓住版本更新问题最后一个值得落进代码的技巧是 dry-run 模式。脚本通过标记文件触发后只做控件探测、不做点击把“选择器还能不能匹配到目标节点”作为输出。App 每次发版改掉按钮文案之后第一个报警的一定是这个自检项。// 存在 dry_run 标记文件时只探测控件不执行点击 if (files.exists(/sdcard/scripts/.dry_run)) { let found textContains(签到).findOnce(3000); console.log(found ? check ok : check lost); files.remove(/sdcard/scripts/.dry_run); exit(); }这个方式把“失效”和“出错”分开选择器失效不等于任务失败但它预示后续正常执行都会失败。定时任务可以先写标记文件、再跑脚本得到 check lost 告警时人工介入。这个检查逻辑不依赖外部服务改动量很小配合青龙面板的 cron 任务就能在 App 发版后第一时间收到选择器失配的通知而不是等用户手动打开脚本跑一遍才发现。本文还有配套的精品资源点击获取
返回列表