教程
Python 找图点击脚本:蓝叠与雷电模拟器图像识别循环
在 Asset Lab 中截取模板,在 BlueStacks 或 LDPlayer 上匹配,并循环等待某个状态出现,做出一个带图像识别的 Python 找图点击脚本。
- Windows
- Mac
- 模拟器
- 云手机
- 手机
- Studio
- Python SDK
本页目录
一个带图像识别的 Python 找图点击脚本在行动之前先看屏幕。它点按钮是因为按钮在那里,关弹窗是因为弹窗出现了,“体力不足”提示出现时它就停下。这篇指南在 Macro Automation Studio(MAS)中、在 BlueStacks 或 LDPlayer 上做出这样一个脚本,从第一次裁剪模板到一个等待某个状态的循环。适合已经用不惯模拟器自带录制器的人。
为什么录制器不会分支
BlueStacks 或 LDPlayer 的宏录制器保存点击和时间,然后重放。它从不看屏幕。它无法知道弹窗盖住了按钮、今天加载页面花了更长时间,或者计数器归零了。每一个录制的宏都是一条直线。
MAS 宏是一段 Python 脚本。每次 mas.find_object 调用都截一张图,用 OpenCV 模板匹配在其中搜索模板图片。最佳匹配达到阈值(默认 threshold=0.8)时,调用返回它的中心。否则返回 None。正是这一个 if,让宏能够分支、等待、重试和停止。
开始之前
- 已安装 MAS 并用试用或套餐登录。试用从免费下载开始;参见安装 MAS。
- BlueStacks 或 LDPlayer 正在运行、已开启 ADB,并已添加为设备。参见 BlueStacks 或 LDPlayer 指南。
- 模拟器使用一个你会一直保持的固定分辨率。各指南以竖屏 540x960、240 DPI 为基准。
- Code Editor 中打开了一个代码型项目。参见快速开始。
在 Asset Lab 中截取模板
- 把应用切到有你要找的按钮的屏幕。
- 在 Code Editor 中打开资源面板,点击打开资源助手。Asset Lab 会带着你设备的实时画面打开。
- 紧贴按钮裁一个矩形并保存。裁剪会进入你的图片库并获得一个数字 ID。
- 对宏必须识别的每个状态重复:弹窗的关闭按钮、确认按钮、“完成”提示。
- 在资源面板中对每张图片点击复制 ID,在脚本顶部声明它们。
import mas
images = mas.images({
"play_button": 201,
"close_popup": 202,
"out_of_energy": 203,
})mas.images 给每个 ID 一个可读的名字,并告诉打包器发布时要捆绑哪些图片。模板可以是 jpg、png、gif 或 webp,单张最大 10 MB。参见 Asset Lab。
选择合适的调用
| 调用 | 适用场景 | 返回 |
|---|---|---|
find_object(image) | 你只想看一眼当前屏幕 | ObjectMatch 或 None |
find_object_retry(image, total_tries=3, time_sleep=2.0) | 目标可能需要一会儿才出现 | ObjectMatch,或最后一次尝试后返回 None |
find_any_object([a, b, c]) | 几个模板都可以接受:弹窗变体、两套主题 | 第一个匹配,带 matched_template_id |
find_objects(image, max_matches=10) | 同一个图标出现多次,你全都要 | 按置信度排序的列表 |
find_object_retry 是本站惯用的等待原语:它最多调用 find_object total_tries 次,失败的尝试之间固定停顿 time_sleep,最后一次之后不再停顿。其他所有关键字(threshold、search_region、screenshot)都会转发给 find_object。find_any_object_retry 对列表做同样的事。优先用这些,而不是为旧宏保留的 wait_for_object。
match = mas.find_object_retry(images.play_button, total_tries=5, time_sleep=1.5)
if match:
mas.click(match.x, match.y, delay_ms=1000)
else:
mas.log("Play button did not appear", level="warning")ObjectMatch 有 x、y、center 和 matched_template_id。它不带分数,所以请在调用上调整阈值,而不是事后读取置信度。
用 search_region 缩小搜索范围
模板匹配会把模板滑过整张截图。Region 把滑动限制在一个矩形内,这样更快,也避免屏幕其他位置的相似图案。坐标是相对左上角的像素。
from mas import Region
TOP_BAR = Region(x1=0, y1=0, x2=540, y2=120)
energy_icon = mas.find_object(images.energy_icon, search_region=TOP_BAR)只要你知道元素在哪里,就用区域:计数器在顶栏,操作按钮在底部,对话框在中间。从 Asset Lab 读取区域;坐标拾取器会在实时画面上显示像素坐标。
调整阈值
threshold 的范围是 0.0 到 1.0,默认 0.8。匹配必须达到它才算数。
- 当正确的模板因为抗锯齿、轻微的缩放变化或按钮上的发光效果一直匹配不上时,降到 0.7。
- 当模板匹配到错误的位置时,例如一排两个相似的图标,或者一个也会以灰色禁用态出现的按钮,提高到 0.9。
- 降到 0.7 以下之前先重新裁剪。那么低的阈值几乎会接受任何同色的东西。
enabled = mas.find_object(images.claim_enabled, threshold=0.9) # strict: disabled and enabled look alike
glowing = mas.find_object(images.reward_icon, threshold=0.7) # lenient: the icon has a pulsing glow用 find_any_object 处理弹窗
弹窗是录制宏失效的原因。给宏一个认识所有弹窗的函数,在每一轮循环的开头调用它。
POPUPS = [images.close_popup, images.confirm_button, images.later_button]
def clear_popups():
popup = mas.find_any_object(POPUPS)
if popup is None:
return False
mas.log(f"Closing popup {popup.matched_template_id}")
mas.click(popup.x, popup.y, delay_ms=800)
return Truefind_any_object 接受一个列表并返回第一个匹配。search_strategy="best_match" 改为在整个列表中选最高分,"priority_order" 按你列出的顺序尝试模板。返回 True 让循环 continue,这样下一轮看到的是干净的屏幕。
雷电模拟器脚本循环:等待某个状态
等待就是一个带截止时间的循环。find_object_retry 覆盖短等待;对于可能长达一分钟的加载页面,自己写循环,这样可以记录进度并干净地放弃。
import time
def wait_for(image, timeout_s=60, every_s=2.0):
deadline = time.monotonic() + timeout_s
while time.monotonic() < deadline:
match = mas.find_object(image)
if match:
return match
time.sleep(every_s)
return Nonetime.monotonic() 按秒计数,时钟变化时也不会跳变,这让它成为截止时间的正确计时器。每个等待都必须有终点:返回 None,让调用方决定是否停止运行。
一个完整的蓝叠图像识别脚本
这个脚本打开一个屏幕、清理弹窗、在按钮可用时点击它,并在”体力不足”模板出现或时间预算用完时停止。把图片 ID 替换成你图片库里的。
import random
import sys
import time
import mas
from mas import Region
images = mas.images({
"play_button": 201,
"close_popup": 202,
"confirm_button": 203,
"out_of_energy": 204,
"home_screen": 205,
})
BOTTOM = Region(x1=0, y1=700, x2=540, y2=960)
TIME_BUDGET_S = 15 * 60
def clear_popups():
popup = mas.find_any_object([images.close_popup, images.confirm_button])
if popup is None:
return False
mas.click(popup.x, popup.y, delay_ms=800)
return True
def main():
home = mas.find_object_retry(images.home_screen, total_tries=10, time_sleep=3.0)
if home is None:
mas.log("Home screen never appeared", level="error")
sys.exit(2)
started = time.monotonic()
taps = 0
while time.monotonic() - started < TIME_BUDGET_S:
if clear_popups():
continue
if mas.find_object(images.out_of_energy):
mas.log("Out of energy, done")
break
button = mas.find_object_retry(images.play_button, total_tries=3, time_sleep=2.0, search_region=BOTTOM)
if button is None:
mas.log("Play button not found, looking again", level="warning")
continue
mas.click(button.x, button.y, delay_ms=1000)
taps += 1
time.sleep(random.uniform(0.8, 2.0))
mas.log(f"Finished after {taps} taps")
if __name__ == "__main__":
main()在 Code Editor 中用运行(F5)运行它并观察控制台。模板匹配不上时,先修裁剪,再调阈值。
故障排查
模板永远匹配不上
裁剪时的分辨率或 DPI 和设备现在运行的不同、裁剪包含了已经变化的背景,或者元素是动画的。对照截取时的设置检查模拟器的显示设置,裁得更紧,并试试 threshold=0.7。元素有几种样子时,每种都截下来,用 find_any_object。mas.ImageNotFoundError 表示这个 ID 根本不在你的图片库里;从资源面板重新复制。
匹配到错误的东西
模板太通用:一个普通箭头、一块单色方块、一个出现两次的词。改裁它旁边独特的东西,把阈值提高到 0.9,或者传入 search_region 让搜索留在元素所在的位置。find_objects 会显示模板得分超过阈值的每个位置,相似图案一眼就能看出来。
在一个实例上能用,另一个不行
第二个模拟器实例运行在另一种分辨率或 DPI。模板匹配基于像素,所以在 540x960 截取的模板在 720x1280 或不同的 DPI 下不会匹配。把每个实例设为相同的显示设置并重启。在云设备上,用你截取时的显示预设创建设备。如果实例必须不同,为每种分辨率截一套模板,用 mas.get_screen_size() 选择。
没有任何自动化工具是 100% 无风险的,请负责任地使用自动化,并自行斟酌决定。
下一步
相关页面
谢谢。如果有哪里不对,请在 Discord 告诉我们。
有疑问? 在 Discord 提问