首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >#WorkBuddy# 给 Electron 客户端写自动签到机器人:8 个 UI 自动化深坑与修复实录

#WorkBuddy# 给 Electron 客户端写自动签到机器人:8 个 UI 自动化深坑与修复实录

原创
作者头像
用户12861217
发布于 2026-10-30 15:40:53
发布于 2026-10-30 15:40:53
2290
举报

我是一个开广告制作公司的,不是程序员。公司里从设计到出货全是活,我每天真正能坐在电脑前的时间很碎。WorkBuddy 客户端每天有签到积分和宠物旅行积分,加起来一天 100 出头,不多,但断一天就断连续登录,连续档位越高差得越多。

于是我让 AI 帮我把这件事彻底自动化:每天 09:30 一个定时任务,客户端自己醒来、自己签到、自己派猫出门、回来自己领。听起来像十行脚本,实际做了三周,踩了八个坑,其中一个差点点进付费页面。

这篇写的是跑通之后怎么让它稳定。每个坑按"错误现象、根因、修法"记下来。

整体方案:为什么不用网页自动化

第一反应是用浏览器自动化去操作网页版。但签到入口在 Electron 桌面客户端里,网页版没有;猫猫旅行虽然在网页,但需要已登录的会话。

所以最终方案是三层:

  1. 抓图:用 PrintWindow 把客户端窗口内容抓成位图(不是屏幕截图);
  2. 识别:把关心的区域裁出来,放大后交给 Windows 自带的 OCR 引擎读中文;
  3. 动作:按识别结果决定点不点、点哪里,点完回头查日志确认是否真的生效。

三层都跑在本地,不依赖任何第三方付费服务,Windows 自带 OCR 就够用

坑 1:GDI 抓屏抓到一片白,UIA 树是空的

现象:用最常规的 GetWindowDC + BitBlt 抓 Electron 窗口,得到的图 100% 纯白;想走无障碍接口(UIA)读控件树,FindAll 返回空数组。

根因:Electron 用 GPU 合成渲染,窗口内容不在 GDI 表面上,普通 BitBlt 抓不到;同时它默认不导出无障碍树,除非显式开启。

修法:改用 PrintWindow(hwnd, hdcMem, 2),标志位 PW_RENDERFULLCONTENT = 2 才能让窗口把自己"画"到内存 DC 上。核心就这几行:

python hdc = user32.GetWindowDC(hwnd) mem = gdi32.CreateCompatibleDC(hdc) bmp = gdi32.CreateCompatibleBitmap(hdc, w, h) gdi32.SelectObject(mem, bmp) user32.PrintWindow(hwnd, mem, 2) # 关键:flag=2 gdi32.GetDIBits(mem, bmp, 0, h, buf, byref(bmi), 0)

附带坑:窗口最小化或在后台时,PrintWindow 会抓到全黑帧。所以脚本里加了一条铁律——整帧平均亮度 < 15 就当"这一帧不可信",直接跳过,绝不基于黑图做判断。宁可今天不领,也不能乱点。

坑 2:"OCR 读不出中文",其实是我解码错了

现象:OCR 结果永远是乱码,0礻见營销生成… 这种。我一度以为 Windows 中文 OCR 不行,准备换第三方接口。

根因:跟 OCR 一点关系都没有。PowerShell 往管道写 stdout 时用的是本地代码页(GBK),Python 侧按 UTF-8 解码,中文必然整片乱码。改之前我甚至已经在比价云 OCR 了。

修法:两头都钉死 UTF-8。

```powershell

ocr.ps1 开头

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8 ```

```python

Python 侧:先 UTF-8 严格解码,失败再退 GBK

try: out = out.decode("utf-8") except UnicodeDecodeError: out = out.decode("gbk", errors="replace") ```

修完之后实测,中文识别准得出乎意料——"积分包购买全面开放""做任务赢积分好礼"都能一字不差读出来。

这条教训最贵:报错信息会把人骗到完全错误的方向上。看到"乱码"先怀疑编码,别怀疑识别能力。

坑 3(最危险):同一个位置上有一颗一模一样的黑按钮

现象:脚本点位没错、点击也发出去了,但当天积分没到账。查日志发现根本没有签到请求,反而打开了一个网页。

根因:左侧栏那张卡片是一个轮播位。三屏内容共用同一位置、同一颗黑底白字按钮:

轮播屏

按钮文案

点了会怎样

每日签到

立即领取

正确,+100

做任务赢积分

立即前往

开浏览器个人中心

积分包购买

限时加购

弹付费购买面板

三颗按钮颜色、尺寸、坐标完全一致,只有文案不同。原来的做法是"找黑色实心块 → 点它",从根上就是错的:它在语义上是盲的。而且最坏情况不是少领 100 分,是误点"限时加购"。

修法:把"找按钮"降级为"读语义"。每 2 秒抓一次卡片 → 裁标题区 → 放大 3 倍 → OCR → 关键词分类:

  • claim:签到 / 打卡 / 领取 / 加油 / 每日
  • danger:购买 / 加购 / 积分包 / 任务 / 前往 / 开通 / 升级 / 续费 / 兑换 / 盲盒
  • unknown:其余

只有 claim 才点,danger 和 unknown 一律不点,继续等下一屏。因为轮播一圈大约 10 秒,所以循环上限给到 420 秒,足够覆盖多轮。

另外加了一条前置保险:先查今天到底领没领。读客户端日志里的签到成功事件,已领就直接退出,一个像素都不点。

python if already_checked_in_today(): print("OK: 今日已领取") return 0

我给这一条起了个名字:自动化里最贵的不是"能不能点对",是"点错了会怎样"。凡是可能触发付费、提交、删除的入口,必须走语义白名单,不能走位置

坑 4:点击全部落到别的窗口上

现象:脚本正常跑,截图正常,点击也"成功"了,但界面毫无反应。

根因:后台进程直接调 SetForegroundWindow 会被系统拒绝,前台窗口其实还是企业微信。所有模拟点击都落在企业微信的对应坐标上——那次运气好,落在空白区,没造成任何操作。

修法:用 ALT 键"借"一次前台权限,并且校验结果:

python user32.keybd_event(0x12, 0, 0, 0) # ALT down user32.SetForegroundWindow(hwnd) user32.keybd_event(0x12, 0, 0x0002, 0) # ALT up if user32.GetForegroundWindow() != hwnd: # 兜底:最小化再还原,实测必成 user32.ShowWindow(hwnd, 6); time.sleep(1.0) user32.ShowWindow(hwnd, 9); time.sleep(1.5) user32.ShowWindow(hwnd, 3); time.sleep(1.0)

通用教训:所有"让窗口到前台"的调用都要读回状态验证,不能假设成功。SetForegroundWindow 返回值不能信,要拿 GetForegroundWindow() 跟目标句柄比。

坑 5:窗口跑到副屏,坐标整体系偏移

现象:同样的相对坐标,有时点中有时点偏。

根因:双屏 DPI 缩放不一致。窗口停在副屏(125% 缩放)时,相对坐标换算出来的物理像素跟主屏(100%)差一截。

修法:脚本第一步就 SetProcessDpiAwareness,再把窗口先挪回主屏左上角、再最大化,然后在主屏坐标系里算点击位置。顺序不能反——先最大化再挪,最大化会发生在副屏。

坑 6:按颜色定位,被一次改版静默打穿

现象:猫猫旅行脚本突然报"未定位到宠物卡片按钮",但页面明明是好的。

根因:客户端 5.8.3 把主按钮底色从纯黑改成了中灰 #8D8D8D(亮度约 141)。旧代码判据是 亮度 < 80,全部漏检。没有报错,只是找不到——这种失败最难查。

修法:定位不再依赖具体颜色值,改成"实心 + 低饱和 + 成块":

```python

亮度足够低且 RGB 三通道极差很小 → 视为一块实心按钮

if lum < 180 and (max(r,g,b) - min(r,g,b)) < 24: ... ```

黑、中灰、深蓝都能认出来。颜色只用来判状态(亮度 > 115 视为置灰不可点),不再用来做定位。

通用教训:UI 自动化里,颜色值是最脆的耦合。改版只改配色是产品经理最常干的事,而你的脚本会静默失效

坑 7:领取是两步,点完第一步等于没领

现象:卡片一直显示"领取礼物",脚本每次点完就退出,猫再也派不出去。

根因:点"领取礼物"只是弹出信件弹窗"Buddy 满载而归啦~",真正加积分要再点弹窗里的"领取 7 积分"。而且弹窗右侧还有一颗黑色按钮"快来做同款吧",点了会打开别人的作品——又一处"看起来一样、语义完全不同"的陷阱。

修法:写一个 complete_claim_modal():定位弹窗里的青色实底按钮(判据用 G - R > 55,跟黑色按钮区分开),反色放大 4 倍 OCR 校验文案含"领取",再过一遍付费词黑名单,才点。领完递归回主流程重新判断状态。

坑 8:弹窗关不掉,必须刷新页面复位

现象:领完之后弹窗不消失,按钮变成灰色"已领取",底部 X 和 Esc 都关不掉。下一轮脚本把弹窗里的黑按钮误当成宠物按钮,OCR 读不出文案,直接放弃。

修法:领奖成功后不找关闭按钮,直接 Ctrl+R 刷新页面并等 7 秒,再递归判断。复位比重试更可靠——找关闭按钮是在跟产品设计赌运气,刷新是确定性的。

效果与几个真实数

  • 签到:每天 +100,连续登录第 18 天(截至 10-30),一次成功耗时约 20–40 秒;
  • 猫猫旅行:每趟返回 7 分左右,每天只能派一趟(实测确认,按钮置灰文案"累啦,明天再来吧");
  • 定时任务:只保留 1 条,09:30 跑完签到+旅行全流程。

第 3 条是后来才想明白的:一开始我配了三条自动化(签到、旅行早班、旅行晚班),结果发现每条自动化都要起一次独立的 Agent 会话,消耗的是会话额度,不是积分本身。合并成一条、晚班直接删掉,成本立刻降到三分之一。

三条可以带走的结论

  1. 抓图用 PrintWindow + flag=2,全黑帧一律不作判断依据。
  2. 定位看形状(实心、低饱和、成块),状态看亮度,要不要点看 OCR 出来的文字。三层分开管,产品改配色改不坏定位。
  3. 凡是有副作用的点击,必须白名单 + 点后校验。校验收口查日志/查状态,不要相信"点击函数返回 True"。

整套东西没有用任何商业 RPA,全是 Python + ctypes + Windows 自带 OCR + PowerShell,一个人边用边改,三周收敛。


原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 整体方案:为什么不用网页自动化
    • 坑 1:GDI 抓屏抓到一片白,UIA 树是空的
    • 坑 2:"OCR 读不出中文",其实是我解码错了
  • ocr.ps1 开头
  • Python 侧:先 UTF-8 严格解码,失败再退 GBK
    • 坑 3(最危险):同一个位置上有一颗一模一样的黑按钮
    • 坑 4:点击全部落到别的窗口上
    • 坑 5:窗口跑到副屏,坐标整体系偏移
    • 坑 6:按颜色定位,被一次改版静默打穿
  • 亮度足够低且 RGB 三通道极差很小 → 视为一块实心按钮
    • 坑 7:领取是两步,点完第一步等于没领
    • 坑 8:弹窗关不掉,必须刷新页面复位
    • 效果与几个真实数
    • 三条可以带走的结论
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档