一、背景:为什么一个简单的"签到"这么难
很多桌面软件(尤其是基于 Electron / Chromium 的跨平台客户端)都内置了"每日签到领积分"这样的运营功能。点一下按钮,积分到账——动作本身很简单,但当你想把它自动化时,会发现它处处是坑:
pyautogui / mouse_event 模拟点击,光标明明移到了按钮上,却毫无反应;本文记录我用 Python 把一个 Electron 客户端的"每日签到"做成稳定、幂等、可定时触发的自动化任务的完整过程。其中 90% 的工作量都不在"点按钮"本身,而在和 Windows 图形子系统"斗智斗勇"。
目标很明确:让脚本在每天固定时间,无需人工干预地完成:
技术选型:
环节 | 方案 |
|---|---|
窗口定位 | EnumWindows + 进程路径匹配 |
截图 | PrintWindow 失败回退 mss |
按钮识别 | RapidOCR 多尺度文本检测 |
鼠标点击 | PostMessage(WM_LBUTTONDOWN/UP) + 客户区坐标 |
健壮性 | 空闲检测、锁屏检测、幂等判断 |
定时触发 | 平台自动化任务(每天 02:00 单次) |
PrintWindow 为什么一片黑?最直觉的截图方式是用 PrintWindow 把窗口内容"画"到一张内存位图上:
对普通 Win32 窗口这招很好用,但对 Electron / Chromium 渲染的窗口,GPU 合成层不走 GDI,于是你拿到的是一张纯黑(或纯白)位图(均值 arr.mean() < 3 即可判定)。
解决:回退到屏幕抓取(直接读显示器帧缓冲),mss 库就能用:
坑点:
mss.grab传入的坐标必须是物理像素且落在某个显示器范围内,否则它会无限阻塞(不报错、不返回)。下一节讲怎么处理。
这是最容易让人"算错坐标"的地方。我的环境是系统缩放 150%(DPI=144)。
结论先行:GetWindowRect、mss.grab 返回的坐标,全程都是物理像素,和窗口在屏幕上的真实位置一致;而 SetCursorPos / PostMessage 客户端坐标也接受物理像素,不需要再乘 1.5。
我曾一度"自作聪明"地加了一层缩放:
验证方法很简单:用 OCR 在截图上找窗口标题栏文字,得到它的真实像素位置,再和 GetWindowRect 对比——两者一致,证明根本不存在逻辑→物理的换算,那层 ×1.5 是画蛇添足。
多显示器裁剪(关键):我的窗口矩形是 (474,129)-(3600,1965),而主屏只有 2560×1440 宽。窗口横跨到右侧副屏之外,mss.grab 因为区域超出可见范围而永久卡死。必须先把抓取区域裁到所有显示器的并集内:
再加一层线程超时,防止任何意外阻塞拖垮整个任务:
实测加上这两步后,跨屏抓取从"卡死 2 分钟"变成"约 30ms 返回"。
SetCursorPos 点了等于没点这是整个过程最关键的发现,也是本文最想分享给读者的经验。
前面所有准备就绪后,我用最"标准"的方式点击头像:
结果:光标确实挪到了头像上方,但菜单从不弹出。我扫了 20 多个坐标点(头像左缘、中心、右缘、上 5px/下 5px……)全部无效。
原因:mouse_event / SendInput 这类 "合成输入" 走的是系统的"低级输入队列",而 Electron 的渲染进程对来自输入法/辅助功能的合成事件有过滤或不响应的情况(尤其在窗口未处于"前台输入焦点"或被 accessibility 层拦截时)。
解法:绕过输入队列,直接把鼠标消息投递到目标窗口过程:
第一次尝试就弹出了头像菜单——这就是整条链路打通的瞬间。
划重点:
PostMessage用的是客户区坐标(相对窗口客户区左上角),而SetCursorPos用屏幕坐标。务必用ScreenToClient转换,否则坐标会整体偏移一个窗口边框/标题栏的尺寸。
操作 | 屏幕坐标 | 客户区坐标 |
|---|---|---|
左下角用户头像 | (574, 1912) | (100, 1783) |
菜单项「签到领积分>」 | (886, 1272) | (412, 1143) |
面板「立即领取」 | (587, 1826) | (113, 1697) |
截图有了,点在哪?用 RapidOCR 做文本检测,但要注意防误命中:
所以识别时要限定搜索区域 + 精确关键词 + 优先精确匹配:
经验:把"头像→菜单"的搜索区限制在小窗格内、用连续词「加油站」而非「签到」(后者会误中同名对话),命中率显著提升。
定时任务最大的不确定性是——触发时电脑可能在锁屏、屏保,或你正用着鼠标。
Winlogon=密码锁,Screen-saver=带密码屏保,Default=正常): 脚本稳定后,用平台自带的自动化任务能力挂上调度即可。核心配置要点:
buddy-points);(小插曲:我一度在系统里看到了两个同名签到任务——旧的残留注册没清干净,导致同一脚本被调度了两次、白天还弹窗打扰。最终删掉陈旧任务、保留唯一的 02:00 任务才干净。教训:重建自动化任务后,务必确认旧的注册被彻底移除,否则会出现"重复触发 + 记忆分裂"。)
PrintWindow 黑屏 → 用 mss 屏幕抓取。mss.grab 跨屏卡死 → 先把区域裁到显示器并集,再加线程超时。GetWindowRect/mss/PostMessage 都是物理像素,别乱乘缩放。SetCursorPos+mouse_event 点了没反应 → 换 PostMessage(WM_LBUTTONDOWN/UP) + 客户区坐标。"每日签到"看似是个体力活,真正难的从来不是"点一下",而是在 Windows 图形子系统、DPI、多显示器、Electron 渲染架构、输入安全模型之间找到那条能稳定走通的路。
本文最核心的一条经验是:对 Electron/Chromium 客户端,别迷信 pyautogui/mouse_event,PostMessage + 客户区坐标才是可靠之选。 配合 mss 截图与 OCR 定位,你就能把任意桌面客户端的"点点点"流程,改造成一个安静、幂等、凌晨自己跑的自动化任务。
祝你的积分,天天到账。🪙
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。