# 模拟器多开挂机脚本：雷电、蓝叠多开同步运行

> MAS 中的模拟器多开挂机：按端口添加每个 BlueStacks 或 LDPlayer 实例，编成设备组，给每台设备自己的设置方案和代理，一键同步运行。

Source: https://automationmacro.com/zh-CN/docs/guides/multi-instance (教程, updated 2026-09-05)

一个宏、五个账号、一次点击：这就是模拟器多开挂机。Macro Automation Studio（MAS）把每个模拟器实例当作一台单独的设备。设备组随后在所有实例上运行同一个宏，每台设备有自己的参数、各自的代理，以及永远不会混淆计数器的存储。这篇指南在 BlueStacks、LDPlayer、MuMu Player 或 MEmu 上搭好这套多开挂机方案。适合运行不止一个账号的人。

## 开始之前

- 已安装并登录 MAS，套餐的设备数覆盖你想同时运行的实例数。参见[计费](/docs/billing)。
- 模拟器的多开管理器已经创建了实例，每个实例都开启了 ADB。[BlueStacks](/docs/bluestacks-setup-guide)、[LDPlayer](/docs/ldplayer-setup-guide)、[MuMu Player](/docs/mumu-setup-guide) 和 [MEmu](/docs/memu-setup-guide) 指南说明设置和端口在哪里。
- 每个实例使用与你截取模板时相同的分辨率和 DPI。
- 宏已经能从 Code Editor 在一个实例上工作。参见[循环指南](/docs/guides/loops-and-scheduling)。

## 多开模拟器的每个实例都是一台设备

每个模拟器实例在自己的本地 TCP 端口上监听 adb。MAS 通过扫描运行中的进程并保留各模拟器已知范围内的端口来发现实例。所以两个 BlueStacks 窗口会在**端口**列表中显示为两项。设备就是端口；组就是端口的列表。

| 模拟器 | MAS 接受的端口 |
|---|---|
| BlueStacks | 5555 到 8500 |
| LDPlayer | 5555 到 5599 之间的奇数端口 |
| MuMu Player | 5555 到 5599 的奇数端口、7555、16384 到 16576、26624 到 27264 |
| MEmu | 21503 及其之上、30000 以下所有以 3 结尾的端口 |

添加时保持每个实例打开；已停止的实例没有可供发现的端口。完整的发现规则见[设备](/docs/devices)页。

## 创建设备组

1. 打开**设备组**，点击**新建组**。
2. 输入**名称**，类型保持**本地**，点击**创建组**。
3. 打开这个组，点击**添加设备**。
4. 输入一个能说明是哪个账号的**设备名称**，例如 `farm-alt-2`。
5. 在**端口**下选择实例的端口。缺失时点击**刷新**，或选择**自定义端口**并输入。
6. 点击**添加设备**，对每个实例重复。
7. 在每张设备卡片上，在**宏**选择器中选择宏。

一个端口只能属于一台设备。重复添加会失败并提示 "Port is already in use"。参见[设备组](/docs/device-groups)。

## 给每台设备自己的参数

大多数脚本需要一个按账号不同的值：一个名字、一个目标、一个轮次数。有两种传递方式。

**设置方案（不改代码）。** 如果宏有 UI Builder 做的参数表单，每张设备卡片都有**编辑参数**。对话框带一个**方案**栏：输入值，选择**将当前值另存为新方案**，命名并点击**创建方案**。一台设备跟随一个方案，编辑方案会在下一次运行时更新所有跟随它的设备。"Default" 始终存在且不能删除。参见[设置方案](/docs/settings-profiles)和 [UI Builder](/docs/ui-builder)。

**代码中的端口。** 脚本可以询问自己在哪个实例上，并选择自己的设置。这不需要表单，结尾的示例就是这么做的。

```python
import mas

port = mas.get_current_device_port()   # 5555, 5557, 5559 ...
```

值经常变化或者由别人运行脚本时用方案；映射固定且和代码放在一起时用端口。

## 给每台设备自己的代理

共用一个 IP 的账号共担同一个命运。MAS 为每台设备绑定一个代理，并把该实例的流量路由过去。

1. 打开**代理**。在**添加代理**下，填写**网关账户**标签页，或者用**其他提供商**标签页填入你自己的代理（`http`、`https` 或 `socks5`），点击**添加并测试**。
2. 等待测试。它确认代理有响应并检测它的国家；之后可以用**测试**重复。
3. 点击**分配**并选择设备，或者在**设备组**中打开设备卡片，在**代理**下选择代理。

设备卡片在每次运行时显示代理状态，运行 Webhook 携带 `proxy_exit_ip` 和 `proxy_country`。一台设备持有一个代理；绑定另一个会替换它。参见[代理](/docs/proxies)。

## 全部启动：LDPlayer 或 BlueStacks 的多开同步运行

点击组顶部的**全部启动**。MAS 依次启动设备，间隔半秒，每台都有自己的运行、自己的日志和自己的退出码。没有宏的设备会带着错误被跳过，其余的继续。**全部停止**结束组里的每次运行。每张卡片上的**日志**标签页只显示这台设备，宏有运行时 UI 的话，**仪表盘**标签页会显示它。

## 用存储保存按设备的状态

`mas.save`、`mas.retrieve` 和 `mas.clear` 的键由这台电脑、设备的端口和任务名组成。所以运行同一个宏的两个实例无需额外代码就保持各自独立的计数器。要把它们加起来，`mas.retrieve_all(task_name)` 为每个端口返回一个条目，带 `machine_id`、`port`、`data` 和 `updated_at`。

```python
TASK = "collector"

mine = mas.retrieve(TASK)                          # this instance only
everyone = mas.retrieve_all(TASK)                  # every instance on this computer
total = sum(entry["data"].get("done", 0) for entry in everyone)
```

参见[存储](/docs/sdk/storage)。

## 按组设置 Webhook

要在任何账号完成或失败时得到通知，在 **Webhook** 页添加一个端点，在**发送给我**下选择事件，在**接收来自以下来源的事件**下选择**指定组**并勾选这个组。之后加入组的设备也会被覆盖。每个事件携带 `device_name`、`device_port`、`group_id`、`status` 和 `exit_code`，所以一个端点就能区分账号。参见 [Webhook](/docs/webhooks)。

## 注意蓝叠多开挂机的资源限制

每个实例都是一个完整的安卓系统。扩大规模之前，检查三件事：

- **每个实例的 CPU 和内存。** 每个实例占用模拟器实例设置中设定的核心和内存。把它们加起来，并给 MAS 留出余量，因为它在同一台机器上为每个实例做模板匹配和 OCR。当点击开始延迟落地时，你就超了。
- **套餐的设备数。** 套餐按你自己的设备可以同时运行多少台计量。**全部启动**只运行套餐允许的数量；参见[计费](/docs/billing)和[价格页](/pricing)。
- **定时任务。** 定时任务最多同时运行 20 个任务，每个端口一个。

干净运行的少数实例，胜过匹配不上模板的更多实例。

## 为组设置定时

定时任务每个任务针对一个端口，所以组的定时就是每台设备一个任务。

1. 打开**定时任务**，点击**新建计划**。
2. 选择**宏**，在**模拟器端口**下点击**扫描**并选择第一个实例。
3. 设置**日期**、**时间**和**重复**，然后点击**创建计划**。
4. 对每个端口重复。给这些任务相同的时间；不同端口永远不会冲突。

定时运行使用每张设备卡片上保存的参数，或设备跟随的方案，所以按账号的值会带过去。应用必须保持打开。参见[定时任务](/docs/scheduler)和[循环指南](/docs/guides/loops-and-scheduling)。

## 一个完整的示例

这个宏读取自己的端口，选择该账号的设置，保存按端口的计数器，并在结束时记录所有实例的总数。把端口和图片 ID 换成你的。

```python
import random
import sys
import time

import mas

images = mas.images({
    "collect": 501,
    "close_popup": 502,
})

SETTINGS = {   # per instance, keyed by the emulator's adb port
    5555: {"account": "main", "rounds": 20},
    5557: {"account": "alt-1", "rounds": 10},
    5559: {"account": "alt-2", "rounds": 10},
}
DEFAULT = {"account": "unknown", "rounds": 5}
TASK = "collector"
TIME_BUDGET_S = 15 * 60


def main():
    port = mas.get_current_device_port()
    settings = SETTINGS.get(port, DEFAULT)
    account = settings["account"]
    mas.log(f"Port {port}: account {account}, {settings['rounds']} rounds")

    done = mas.retrieve(TASK).get("done", 0)
    started = time.monotonic()
    misses = 0

    while done < settings["rounds"]:
        if time.monotonic() - started > TIME_BUDGET_S:
            mas.log(f"{account}: time budget reached", level="warning")
            break
        popup = mas.find_object(images.close_popup)
        if popup:
            mas.click(popup.x, popup.y, delay_ms=800)
            continue
        button = mas.find_object_retry(images.collect, total_tries=3, time_sleep=2.0)
        if button is None:
            misses += 1
            if misses >= 5:
                mas.log(f"{account}: five misses, giving up", level="error")
                sys.exit(2)
            continue
        misses = 0
        mas.click(button.x, button.y, delay_ms=1000)
        done += 1
        mas.save(TASK, {"done": done, "account": account})
        time.sleep(random.uniform(0.8, 2.5))

    total = sum(entry["data"].get("done", 0) for entry in mas.retrieve_all(TASK))
    mas.log(f"{account}: {done} rounds done; {total} across all instances")
    mas.webhook("account.finished", {"account": account, "port": port, "done": done})


if __name__ == "__main__":
    main()
```

`mas.webhook` 向订阅了自定义事件的端点发送一个 `custom.account.finished` 事件；没有端点的话去掉这一行。映射变多时，把它移进参数表单，改读 `args.<tab>.<key>` 而不是字典。

## 可能出什么问题

### Port is already in use

你的某个组里已有另一台设备占用了这个端口。每个端口只能添加一次；删除旧条目，或改选另一个实例的端口。

### 一个实例在运行，其他的停在连接中

其他实例的 ADB 是关闭的、还在启动，或者监听的端口在模拟器范围之外。打开每个实例的设置，确认 ADB 和端口，在**添加设备**中点击**刷新**。端口在上表之外时，用**自定义端口**。参见 [ADB 故障排查](/docs/adb-troubleshooting)。

### 模板只在一个实例上匹配

那个实例的分辨率或 DPI 不同。模板匹配基于像素；在模拟器的实例设置中把每个实例设为截取时的分辨率并重启。其余内容见[图像识别指南](/docs/guides/image-recognition-macros)。

### 所有实例共用一个计数器

脚本向 `mas.save` 传入了显式的 `port` 或 `machine_id`，或者用磁盘文件代替了存储。用默认参数调用 `mas.save(TASK, data)`；当前端口会自动加上。

没有任何自动化工具是 100% 无风险的，请负责任地使用自动化，并自行斟酌决定。
