# LD플레이어 멀티 매크로와 블루스택 멀티 인스턴스 동기화 실행

> MAS에서 BlueStacks나 LDPlayer 인스턴스를 포트별로 추가하고 기기 그룹으로 묶어, 기기마다 프로필과 프록시를 주고 한 번에 실행하는 멀티 인스턴스 자동화 안내입니다.

Source: https://automationmacro.com/ko/docs/guides/multi-instance (가이드, updated 2026-09-05)

매크로 하나, 계정 다섯 개, 클릭 한 번. 그것이 LD플레이어 멀티 매크로이자 블루스택 멀티 인스턴스 자동화입니다. Macro Automation Studio(MAS)는 모든 에뮬레이터 인스턴스를 별도 기기로 취급합니다. 그러면 기기 그룹이 LD플레이어 멀티 매크로를 모든 인스턴스에서 실행하며, 기기별 인수, 기기마다 프록시 하나, 그리고 카운터가 절대 섞이지 않는 스토리지를 제공합니다. 이 가이드는 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 창 두 개는 **Port** 목록에 항목 두 개로 나타납니다. 기기는 포트이고, 그룹은 포트 목록입니다.

| 에뮬레이터 | MAS가 인정하는 포트 |
|---|---|
| BlueStacks | 5555부터 8500까지 |
| LDPlayer | 5555부터 5599까지의 홀수 포트 |
| MuMu Player | 5555부터 5599까지의 홀수 포트, 7555, 16384부터 16576까지, 26624부터 27264까지 |
| MEmu | 21503과 그 위로 3으로 끝나는 30000 미만의 모든 포트 |

추가하는 동안 모든 인스턴스를 열어 두세요. 멈춘 인스턴스에는 찾을 포트가 없습니다. 전체 검색 규칙은 [기기](/docs/devices) 페이지에 있습니다.

## 기기 그룹 만들기

1. **Device Groups**를 열고 **Create New Group**을 클릭합니다.
2. **Name**을 입력하고 유형은 **Local**로 둔 채 **Create Group**을 클릭합니다.
3. 그룹을 열고 **Add Device**를 클릭합니다.
4. 어느 계정인지 알 수 있는 **Device Name**을 입력합니다. 예를 들어 `farm-alt-2`입니다.
5. **Port**에서 인스턴스의 포트를 고릅니다. 없으면 **Refresh**를 클릭하거나 **Custom Port**를 선택해 입력합니다.
6. **Add Device**를 클릭하고 모든 인스턴스에 대해 반복합니다.
7. 각 기기 카드의 **Macro** 선택기에서 매크로를 고릅니다.

포트 하나는 기기 하나에만 속할 수 있습니다. 두 번 추가하면 "Port is already in use"로 실패합니다. [기기 그룹](/docs/device-groups)을 참고하세요.

## 기기마다 자기 인수 주기

대부분의 봇에는 계정마다 다른 값 하나가 필요합니다: 이름, 목표, 회차 수. 넘기는 방법은 두 가지입니다.

**설정 프로필(코드 변경 없음).** 매크로에 UI Builder로 만든 인수 폼이 있으면 각 기기 카드에 **Edit Arguments**가 있습니다. 대화 상자에 **Profile** 바가 있습니다: 값을 입력하고 **Save current values as a new profile**을 선택해 이름을 짓고 **Create profile**을 클릭합니다. 기기는 프로필 하나를 따르며, 프로필을 편집하면 그것을 따르는 모든 기기가 다음 실행에서 갱신됩니다. "Default"는 항상 존재하며 삭제할 수 없습니다. [설정 프로필](/docs/settings-profiles)과 [UI Builder](/docs/ui-builder)를 참고하세요.

**코드의 포트.** 스크립트가 자기가 어느 인스턴스에 있는지 물어보고 자기 설정을 고를 수 있습니다. 폼이 필요 없으며 끝의 예제가 이 방식입니다.

```python
import mas

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

값이 자주 바뀌거나 다른 사람이 봇을 실행할 때는 프로필을, 대응이 고정되어 코드와 함께 살 때는 포트를 쓰세요.

## 기기마다 자기 프록시 주기

IP를 공유하는 계정은 운명도 공유합니다. MAS는 기기마다 프록시 하나를 연결하고 그 인스턴스의 트래픽을 프록시로 보냅니다.

1. **Proxies**를 엽니다. **Add a proxy** 아래에서 **Gateway account** 탭을 채우거나, 본인 프록시(`http`, `https` 또는 `socks5`)를 위한 **Another provider** 탭을 채우고 **Add & test**를 클릭합니다.
2. 테스트를 기다립니다. 프록시가 응답하는지 확인하고 국가를 감지합니다. **Test**가 나중에 반복합니다.
3. **Assign**을 클릭해 기기를 고르거나, **Device Groups**에서 기기 카드를 열고 **Proxy** 아래에서 프록시를 고릅니다.

기기 카드는 실행마다 프록시 상태를 보여 주고, 실행 웹훅에는 `proxy_exit_ip`와 `proxy_country`가 담깁니다. 기기는 프록시 하나를 가지며, 다른 것을 연결하면 대체됩니다. [프록시](/docs/proxies)를 참고하세요.

## Start All: LDPlayer나 BlueStacks의 멀티 인스턴스 동기화 실행

그룹 위쪽의 **Start All**을 클릭합니다. MAS가 기기를 0.5초 간격으로 차례로 시작하며, 각각 자기 실행, 자기 로그, 자기 종료 코드를 가집니다. 매크로가 없는 기기는 오류와 함께 건너뛰고 나머지는 계속됩니다. **Stop All**은 그룹의 모든 실행을 끝냅니다. 각 카드의 **Logs** 탭은 그 기기만 보여 주고, **Dashboard** 탭은 매크로에 런타임 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)를 참고하세요.

## 그룹별 웹훅

어느 계정이든 끝나거나 실패할 때 알림을 받으려면 **Webhooks** 페이지에서 엔드포인트를 추가하고, **Send me** 아래에서 이벤트를 고르고, **Send me events from** 아래에서 **Specific groups**를 선택해 그룹을 체크합니다. 나중에 그룹에 추가한 기기도 포함됩니다. 각 이벤트에는 `device_name`, `device_port`, `group_id`, `status`, `exit_code`가 담기므로 엔드포인트 하나로 계정을 구별할 수 있습니다. [웹훅](/docs/webhooks)을 참고하세요.

## 블루스택 봇 팜의 자원 한계 확인

모든 인스턴스는 완전한 Android 시스템입니다. 늘리기 전에 세 가지를 확인하세요:

- **인스턴스당 CPU와 RAM.** 각 인스턴스는 에뮬레이터 인스턴스 설정의 코어와 메모리를 차지합니다. 모두 더하고, 같은 컴퓨터에서 모든 인스턴스의 템플릿 매칭과 OCR을 실행하는 MAS를 위한 여유를 남기세요. 탭이 늦게 떨어지기 시작하면 한계를 넘은 것입니다.
- **요금제의 기기 수.** 요금제는 본인 기기 몇 대를 동시에 실행할 수 있는지로 정해집니다. **Start All**은 요금제가 허용하는 만큼 실행합니다. [결제](/docs/billing)와 [가격 페이지](/pricing)를 참고하세요.
- **예약된 작업.** 스케줄러는 포트당 하나씩 최대 20개 작업을 동시에 실행합니다.

깨끗하게 돌아가는 적은 인스턴스가 템플릿을 놓치는 많은 인스턴스보다 낫습니다.

## 그룹 예약

스케줄러는 작업마다 포트 하나를 대상으로 하므로 그룹 예약은 기기마다 작업 하나입니다.

1. **Scheduler**를 열고 **Create New Schedule**을 클릭합니다.
2. **Macro**를 고르고 **Emulator Port** 아래의 **Scan**을 클릭해 첫 인스턴스를 선택합니다.
3. **Date**, **Time**, **Recurrence**를 설정하고 **Create Schedule**을 클릭합니다.
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

내 그룹의 다른 기기가 그 포트를 갖고 있습니다. 포트는 한 번만 추가할 수 있습니다. 기존 항목을 제거하거나 다른 인스턴스의 포트를 고르세요.

### 인스턴스 하나만 실행되고 나머지는 Connecting에 머묾

나머지 인스턴스는 ADB가 꺼져 있거나, 아직 부팅 중이거나, 에뮬레이터 범위 밖 포트로 수신합니다. 각 인스턴스의 설정을 열어 ADB와 포트를 확인하고 **Add Device**에서 **Refresh**를 클릭하세요. 포트가 위 표 밖이면 **Custom Port**를 쓰세요. [ADB 문제 해결](/docs/adb-troubleshooting)을 참고하세요.

### 템플릿이 한 인스턴스에서만 일치함

그 인스턴스의 해상도나 DPI가 다릅니다. 템플릿 매칭은 픽셀 기반입니다. 에뮬레이터 인스턴스 설정에서 모든 인스턴스를 캡처 해상도로 맞추고 다시 시작하세요. 나머지는 [이미지 인식 가이드](/docs/guides/image-recognition-macros)에서 설명합니다.

### 모든 인스턴스가 카운터 하나를 공유함

스크립트가 `mas.save`에 명시적인 `port`나 `machine_id`를 넘기거나, 스토리지 대신 디스크의 파일을 씁니다. 기본값으로 `mas.save(TASK, data)`를 호출하세요. 현재 포트는 자동으로 추가됩니다.

100% 안전한 자동화 도구는 없으므로, 책임감을 가지고 본인의 판단에 따라 자동화하세요.
