• Site
  • Status do aplicativo
  • Documentação da API
  • Blog
Telegram Icon Comunidade
PT
Русский
Português
English
中文 (中国)
Tiếng Việt
Fazer login Teste grátis
  • Site
  • Status do aplicativo
  • Documentação da API
  • Blog
  • Comunidade








  • Portuguese (Brazil)









    Русский










    Português










    English










    中文 (中国)










    Tiếng Việt








Fazer login Teste grátis

Scripts Python personalizados

Crie fluxos de automação poderosos com Python e a API do Multilogin. Crie, gerencie e execute perfis de navegador com scripts totalmente personalizáveis.

search icon

Tente uma palavra-chave diferente ou verifique se há erros de digitação. Se ainda precisar de ajuda, entre em contato com o suporte.

  • Começando com a automação do Multilogin
  • Automação básica com CLI
  • Automação low-code com Postman
  • Executor de scripts e scripts pré-definidos
  • Puppeteer, Selenium e Playwright
  • Scripts Python personalizados
  • Soluções rápidas com as Ferramentas de Desenvolvedor
  • Ferramentas de automação externas
  • Início
  • breadcrumb separator bar
  • Multilogin
  • breadcrumb separator bar
  • Automação de tarefas com API
  • breadcrumb separator bar
  • Scripts Python personalizados
  • breadcrumb separator bar
  • Como automatizar postagens no Pinterest em telefones em nuvem com Appium

Como automatizar postagens no Pinterest em telefones em nuvem com Appium

Escrito por Anton L ( Atualizado em 17 de Julho de 2026 )

Atualizado em 17 de Julho de 2026

Telefones em nuvem Multilogin suportam ADB, então você pode conectá-los a ferramentas de automação Android como o Appium.

Neste exemplo, um script em Python lida com trabalho repetitivo no Pinterest em vários telefones em nuvem. Ele se conecta a cada telefone, faz login na sua conta do Pinterest, move as imagens designadas, executa algumas ações de feed de exemplo e publica as imagens como Pins.

➡️ Você não precisa abrir cada telefone em nuvem e repetir as mesmas ações manualmente. Adicione seus telefones e contas a devices.txt, inicie o script e deixe-o trabalhar na lista para você.

O script processa os telefones em nuvem um por vez. Ele completa o fluxo de trabalho completo em um telefone antes de passar para o próximo.

Este script é uma prova de conceito. Ele mostra o que você pode construir com Multilogin telefones em nuvem, ADB e Appium. A automação do Pinterest não está integrada ao Multilogin.

 
Your browser does not support HTML5 video.

O que o script faz

Uma vez que tudo esteja pronto, o script:

  • Conecta a um telefone em nuvem através do ADB
  • Instala o Pinterest a partir de um arquivo APK
  • Muda imagens do seu computador para o telefone em nuvem
  • Faz login na conta do Pinterest que você fornecer
  • Executa algumas interações de feed de exemplo para mostrar a navegação automatizada no aplicativo
  • Publica as imagens designadas
  • Passa para o próximo telefone em nuvem na lista

Cada telefone em nuvem pode usar sua própria conta do Pinterest e faixa de imagens. Isso facilita a organização de conteúdo para vários perfis sociais a partir de uma única configuração.

As interações de feed de exemplo também podem ser adaptadas para um fluxo de trabalho de aquecimento de conta. Confira Como aquecer um perfil no Multilogin para mais detalhes.

Lembre-se de que algumas interações automatizadas não garantem a confiança da conta ou previnem restrições da plataforma. Sempre siga as regras do Pinterest e automatize apenas contas que você está autorizado a gerenciar.

 

Antes de começar

Você precisará de:

  • Appium e UiAutomator2 instalados no seu computador: Documentação do Appium
  • Um telefone em nuvem Multilogin iniciado com ADB habilitado: Como usar ADB em telefones em nuvem
  • Uma conta de teste do Pinterest e seus detalhes de login
  • O arquivo APK do Pinterest
  • Uma pasta chamada images com as imagens que você deseja publicar
  • Um arquivo de texto chamado devices.txt

Mantenha o script, o arquivo APK, devices.txt, e a pasta images no mesmo diretório do projeto. Isso ajuda o script a encontrar tudo automaticamente.

 

Configurando devices.txt

Adicione um telefone em nuvem por linha usando este formato:

IP:PORT ADB_PASSWORD PINTEREST_EMAIL PINTEREST_PASSWORD IMAGE_RANGE

Exemplo:

199.190.44.226:25922 j3fYxT email@email.com password 1-3

Aqui está o que cada valor significa:

  • IP:PORT: o endereço ADB do telefone em nuvem
  • ADB_PASSWORD: a senha usada para conectar via ADB
  • PINTEREST_EMAIL: o email da conta do Pinterest
  • PINTEREST_PASSWORD: a senha da conta do Pinterest
  • IMAGE_RANGE: as imagens atribuídas àquela conta

Por exemplo, 1-3 informa ao script para usar as três primeiras imagens da pasta images ordenadas alfabeticamente.

Você pode dividir imagens entre vários telefones em nuvem:

199.190.44.226:25922 j3fYxT user1@email.com password1 1-3
199.190.44.227:25922 k9pQrS user2@email.com password2 4-5

O primeiro telefone recebe as imagens 1–3. O segundo telefone recebe as imagens 4–5.

Deixe a faixa de imagens de fora se você quiser que um telefone use todas as imagens na pasta.

Mantenha seus detalhes de login seguros. devices.txt armazena senhas e outras informações sensíveis como texto simples. Use contas de teste, mantenha o arquivo privado, nunca o faça upload para um repositório público e delete-o quando não precisar mais.

 

Executando o script

  1. Inicie cada telefone em nuvem incluído em devices.txt
  2. Ative o ADB em cada telefone em nuvem
  3. Inicie o servidor Appium no seu computador
  4. Verifique se o script, o arquivo APK, devices.txt, e a pasta images estão no mesmo local
  5. Salve o script abaixo como appium_script.py

appium_script.py

"""

Como usar
-----
Execute este script e inclua no diretório com dois arquivos: devices.txt e pasta images devices.txt   -- um telefone por linha:  IP:PORT TOKEN USERNAME PASSWORD [IMAGE_RANGE]
                IMAGE_RANGE é uma quantidade de imagens que serão usadas para uma conta específica, ex. "1-3"
               para indicar as três primeiras imagens da lista ordenada alfabeticamente de imagens encontradas na pasta images/
               Deixe de fora a faixa de imagens se você quiser que um telefone use todas as imagens na pasta. Exemplo, dividindo 5 imagens entre 2
                telefones (telefone 1 recebe as imagens 1-3, telefone 2 recebe as imagens 4-5):
                      199.190.44.226:25922 j3fYxT user1 pass1 1-3
                    199.190.44.227:25922 k9pQrS user2 pass2 4-5
images/       -- uma pasta de imagens (jpg/png), ou um único arquivo de imagem.

Requer:  pip install Appium-Python-Client
          Servidor Appium executando localmente (appium) e E adb no PATH.

O script instala o APK e imagens no telefone, abre o Pinterest, insere as credenciais da conta, executa algumas interações de feed de exemplo, e publica as imagens designadas.
"""

import argparse
import random
import re
import subprocess
import sys
import time
from pathlib import Path
from urllib.parse import quote as urlquote

from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy

APPIUM_SERVER_URL = "http://localhost:4723"
PINTEREST_PACKAGE = "com.pinterest"

CONNECT_RETRIES = 6
CONNECT_RETRY_SLEEP = 2
INSTALL_RETRIES = 3

# Limite de aquecimento
WARM_UP_MIN_ACTIONS = 3
WARM_UP_MAX_ACTIONS = 5

# Pasta para onde enviamos imagens REMOTE_IMAGE_DIR = "/sdcard/Pictures/bulk_publisher"



# função de conexão adb

def adb(*args, serial=None, timeout=60):
    cmd = ["adb"] + (["-s", serial] if serial else []) + list(args)
    return subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)


def connect_and_login(serial: str, token: str) -> bool:
    """Conexão ADB, usa o nome/endereço do telefone e glogin."""
    print(f"   [CONECTANDO] {serial}: conectando via adb...")
    for attempt in range(1, CONNECT_RETRIES + 1):
        adb("connect", serial)
    state = adb("get-state", serial=serial).stdout.strip()
    if state == "device":
        break
        print(f"   [CONECTANDO] {serial}: não está online ainda (tentativa {attempt}/{CONNECT_RETRIES}, estado={state ou 'desconhecido'}), tentando...")
    adb("disconnect", serial)
    time.sleep(CONNECT_RETRY_SLEEP)
    else:
        print(f"   [FALHA_DE_CONEXÃO] {serial}")
        return False

    print(f"   [CONECTADO] {serial}: online, fazendo login (glogin)...")
    result = adb("shell", "glogin", token, serial=serial)
    output = (result.stdout + result.stderr).lower()
    if "success" in output or "already logged" in output:
        print(f"   [OK] {serial}: logado ")
        return True
    print(f"   [FALHA_DE_GLOGIN] {serial}: {result.stdout ou result.stderr}")
    return False


def install_apk(serial: str, apk_path: str) -> bool:
  for attempt in range(INSTALL_RETRIES):
        result = adb("install", "-r", apk_path, serial=serial, timeout=180)
        se "sucesso" em result.stdout.lower():
            retornar True
       time.sleep(3)
    print(f"   [INSTALL_FAIL] {serial}: {result.stdout ou result.stderr}")
    retornar False


def grant_full_media_access(serial: str):
    """Concede acesso total à biblioteca de fotos/vídeos via ADB """
    adb("shell", "pm", "grant", PINTEREST_PACKAGE, "android.permission.READ_MEDIA_IMAGES", serial=serial)
    adb("shell", "pm", "grant", PINTEREST_PACKAGE, "android.permission.READ_MEDIA_VIDEO", serial=serial)


def sanitize_filename(name: str) -> str:
    """Mapeamento de nomes (se o script não conseguir encontrar a imagem por padrão)"""
    stem, suffix = Path(name).stem, Path(name).suffix
    retornar re.sub(r"[^A-Za-z0-9._-]", "_", stem) + suffix


def push_images(serial: str, image_paths, remote_dir=REMOTE_IMAGE_DIR) -> list:
    """Envie imagens locais para o telefone e inicie uma verificação de mídia"""
    adb("shell", "mkdir", "-p", remote_dir, serial=serial)
    remote_paths = []
    used_names = set()
    total = len(image_paths)
    para i, local_path em enumerate(image_paths, start=1):
        safe_name = sanitize_filename(Path(local_path).name)
        candidate, n = safe_name, 1
       enquanto candidate em used_names:  # dois originais sanitizados para o mesmo nome
             candidate = f"{Path(safe_name).stem}_{n}{Path(safe_name).suffix}"
           n += 1
       used_names.add(candidate)
       remote_path = f"{remote_dir}/{candidate}"
       print(f"   [UPLOADING] {serial}: enviando imagem {i}/{total}: {Path(local_path).name}")
        push_result = adb("push", local_path, remote_path, serial=serial)
        se push_result.returncode != 0 ou "erro" em push_result.stdout.lower():
            print(f"   [PUSH_FAIL] {serial}: {Path(local_path).name}: "
                 f"{push_result.stdout.strip() ou push_result.stderr.strip()}")
            continue

        scan_uri = "file://" + urlquote(remote_path)
        scan_result = adb(
            "shell", "am", "broadcast", "-a",
            "android.intent.action.MEDIA_SCANNER_SCAN_FILE",
            "-d", scan_uri,
           serial=serial,
        )
        se scan_result.returncode != 0:
           print(f"   [SCAN_FAIL] {serial}: {Path(local_path).name}: "
                  f"{scan_result.stdout.strip() ou scan_result.stderr.strip()}")
        remote_paths.append(remote_path)
    retornar remote_paths


def disconnect(serial: str):
    adb("disconnect", serial)



def build_driver(serial: str):
    """Inicie a sessão do Appium e então inicie o Pinterest. """
    capabilities = dict(
        platformName="Android",
        automationName="uiautomator2",
        udid=serial,
        newCommandTimeout=180,
        noReset=True,
        language="en",
        locale="US",
    )
    driver = webdriver.Remote(APPIUM_SERVER_URL, options=UiAutomator2Options().load_capabilities(capabilities))
    driver.activate_app(PINTEREST_PACKAGE)
    retornar driver


def find_any(driver, candidates, timeout=20, poll=0.5, require_enabled=False):
    """Tente cada (por, valor) localizador candidato em ordem até um resolva,
    fazendo polling até o tempo limite emvez de falhar na primeira falta. """
    deadline = time.time() + timeout
    last_err = None
    enquanto time.time() < deadline:
    para by, value em candidates:
            tente:
                 el = driver.find_element(by=by, value=value)
                se el.is_displayed() e (not require_enabled ou el.get_attribute("enabled") == "true"):
                     retornar el
            exc:  # noqa: BLE001 - tentando múltiplas estratégias de propósito
                last_err = exc
        time.sleep(poll)
    raise TimeoutError(f"Nenhum localizador candidato correspondido dentro {timeout}s: {candidates}") de last_err


def tap_any(driver, candidates, timeout=20, require_enabled=False):
    find_any(driver, candidates, timeout=timeout, require_enabled=require_enabled).click()


def tap_if_present(driver, candidates, timeout=3) -> bool:
    """Como tap_any, mas retorna False emvez de levantar quando nada corresponde
    -- para pop-ups que só aparecem às vezes."""
    tente:
        tap_any(driver, candidates, timeout=timeout)
        return True
    exceto TimeoutError:
        return False

# Etapa 1: Primeira tela de login apenas email.
EMAIL_ONLY_FIELD = [
    (AppiumBy.XPATH, "//*[@resource-id='com.pinterest:id/email_address']//android.widget.EditText"),
]
CONTINUE_BUTTON = [
    (AppiumBy.ID, "com.pinterest:id/continue_email_bt"),
]

# Etapa 2: tela combinada email + senha, então "Log In".
LOGIN_EMAIL_FIELD = [
    (AppiumBy.XPATH, "//*[@resource-id='com.pinterest:id/email']//android.widget.EditText"),
]
LOGIN_PASSWORD_FIELD = [
    (AppiumBy.XPATH, "//*[@resource-id='com.pinterest:id/password']//android.widget.EditText"),
]
LOGIN_SUBMIT_BUTTON = [
    (AppiumBy.ID, "com.pinterest:id/login_bt"),
]

# Pop-ups que podem aparecer em pontos imprevisíveis após login.
EMAIL_UPDATE_DISMISS = [
    (AppiumBy.ID, "com.pinterest:id/actionPromptDismissButton"),  # "Este é seu email?" -> fechar
]
NOTIFICATIONS_PERMISSION_ALLOW = [
    (AppiumBy.ID, "com.android.permissioncontroller:id/permission_allow_button"),
]
PHOTOS_PERMISSION_ALLOW_ALL = [
    (AppiumBy.ID, "com.android.permissioncontroller:id/permission_allow_all_button"),
]

# Abre a "Comece a criar agora" ficha inferior. Scripts procurando os seletores dependendo da resolução e tamanho
CREATE_ICON_BUTTON = [
    (AppiumBy.XPATH,
     '//android.widget.Button[@content-desc="Create"]'
     '/android.widget.FrameLayout[@resource-id="com.pinterest:id/icon_button_container"]'
     '/android.widget.Button[@resource-id="com.pinterest:id/icon_button"]'),
    (AppiumBy.XPATH,
     '//android.widget.FrameLayout[@content-desc="Create"]'
     '/android.widget.LinearLayout[@resource-id="com.pinterest:id/tab_content"]'),
]

# Navegação inferior "Home" aba 
HOME_TAB_BUTTON = [
    (AppiumBy.ACCESSIBILITY_ID, "Home"),
    (AppiumBy.ID, "com.pinterest:id/bottom_nav_home_icon"),
]

# "Comece a criar agora" ficha que aparece após tocar em Criar -- escolha "Pin"
PIN_ACTION_BUTTON = [
    (AppiumBy.XPATH, '//android.widget.Button[@content-desc="Pin"]'),
]

# Selecionador de mídia "Próximo" botão (habilitado uma vez que uma foto é selecionada).
GALLERY_NEXT_BUTTON = [
    (AppiumBy.ID, "com.pinterest:id/end_container_text_button"),
]

# Envio final na tela de detalhes do Create Pin -- rotulado "Criar".
CREATE_PIN_SUBMIT_BUTTON = [
    (AppiumBy.ID, "com.pinterest:id/create_gestalt_button"),
]

# --- Aquecendo localizadores (navegando no feed inicial: abrindo um pin, curtir/salvar/comentar) ---

# Genérico carta de pin do feed. O script encontra qualquer pin na tela para aquele telefone/conta/sessão.
PIN_CARD = [
    # Encontra um match realmente clicável. Retorna qualquer match se nenhum clicável for encontrado, caso um
    (AppiumBy.XPATH, '//*[contains(@content-desc, "Pin from") e @clickable="true"]'),
    (AppiumBy.XPATH, '//*[contains(@content-desc, "Pin from")]'),
]
REACT_BUTTON = [
    (AppiumBy.XPATH, '//android.widget.ImageView[@content-desc="React to this Pin"]'),
]
SAVE_BUTTON = [
    (AppiumBy.ID, "com.pinterest:id/save_pinit_bt"),
    (AppiumBy.XPATH, '//android.widget.Button[@content-desc="Save"]'),
]
COMMENTS_MODULE = [
    (AppiumBy.ID, "com.pinterest:id/new_comments_module_container"),
]
CLOSEUP_BACK_BUTTON = [
    (AppiumBy.ID, "com.pinterest:id/closeup_back_button"),
    (AppiumBy.ACCESSIBILITY_ID, "Back"),
]


def dismiss_popups(driver):
    """Limpa o 'Este é seu email?' prompt e ambos Android permissões
    diálogos (notificações, acesso a fotos) se algum delas estiver na tela."""
    tap_if_present(driver, EMAIL_UPDATE_DISMISS, timeout=2)
    tap_if_present(driver, NOTIFICATIONS_PERMISSION_ALLOW, timeout=2)
    tap_if_present(driver, PHOTOS_PERMISSION_ALLOW_ALL, timeout=2)


def ensure_on_home_feed(driver, max_attempts=6, settle_seconds=1.5):
    """Confirma que o ícone Create (feed inicial) está visível, navegando ativamente de volta se não."""
    for _ in range(max_attempts):
         try:
            find_any(driver, CREATE_ICON_BUTTON, timeout=4)
            return
        exceto TimeoutError:
            pass
        se não tap_if_present(driver, HOME_TAB_BUTTON, timeout=3):
            try:
                driver.back()
            exceto Exception:  # noqa: BLE001 - recuperação de melhor-esforço, continuar tentando
               pass
        time.sleep(settle_seconds)

    # Verificação de sanidade se o app estiver minimizado
    driver.activate_app(PINTEREST_PACKAGE)
    try:
        find_any(driver, CREATE_ICON_BUTTON, timeout=10)
        return
    exceto TimeoutError:
        pass
    raise TimeoutError("Não foi possível voltar para o feed inicial do Pinterest (ícone de criação nunca reapareceu)")


def login(driver, username: str, password: str):

    try:
        find_any(driver, EMAIL_ONLY_FIELD, timeout=15).send_keys(username)
    tap_any(driver, CONTINUE_BUTTON)
    exceto TimeoutError:
       pass  # já na tela combinada email+senha screen

    email_field = find_any(driver, LOGIN_EMAIL_FIELD, timeout=20)
    email_field.clear()
    email_field.send_keys(username)
    find_any(driver, LOGIN_PASSWORD_FIELD).send_keys(password)
    tap_any(driver, LOGIN_SUBMIT_BUTTON)
    dismiss_popups(driver)
    ensure_on_home_feed(driver)
    dismiss_popups(driver)


def publish_pin(driver, remote_image_path: str):

    ensure_on_home_feed(driver)
    dismiss_popups(driver)
    tap_any(driver, CREATE_ICON_BUTTON, timeout=10)
    dismiss_popups(driver)
    # Algumas sessões podem pular a planilha "Comece a criar agora" e ir direto
    # para o seletor de foto, então uma planilha faltando aqui não é fatal.
    tap_if_present(driver, PIN_ACTION_BUTTON, timeout=6)
    dismiss_popups(driver)  # o diálogo de permissão de acesso à foto geralmente aparece aqui

    # Corresponder a "<upload folder>/<filename>", não apenas o nome do arquivo -- um telefone que
    # já tem um arquivo com o mesmo nome em outro lugar (por exemplo, Download/).
    unique_suffix = f"{Path(REMOTE_IMAGE_DIR).name}/{Path(remote_image_path).name}"
    thumbnail = [(AppiumBy.XPATH, f"//*[contains(@content-desc, '{unique_suffix}')]")]
    tap_any(driver, thumbnail, timeout=25)  # a grade de galeria pode demorar um pouco para carregar
    # "Próximo" começa desativado até que a seleção seja registrada -- aguarde até enabled=true,
    # não apenas presente, ou o clique silenciosamente não executa em um botão desativado.
    tap_any(driver, GALLERY_NEXT_BUTTON, timeout=15, require_enabled=True)

    # 45s aqui porque Pinterest tem que terminar de carregar/processar
    # a imagem antes que este botão seja renderizado.
    tap_any(driver, CREATE_PIN_SUBMIT_BUTTON, timeout=45)
    ensure_on_home_feed(driver)
    dismiss_popups(driver)


def _open_random_pin(driver) -> bool:
    """Toca em um pin visível aleatório na feed principal."""
    for by, value in PIN_CARD:
        try:
            cards = [c for c in driver.find_elements(by=by, value=value) if c.is_displayed()]
        except Exception:  # noqa: BLE001 - tente o próximo candidato
            continue
        if not cards:
           continue
        try:
           random.choice(cards).click()
           return True
        except Exception:  # noqa: BLE001 - um toque perdido significa "tente algo diferente"
           continue
    return False


def _close_pin(driver):
    """Fecha a visualização detalhada do pin."""
    try:
        if find_any(driver, CREATE_ICON_BUTTON, timeout=1).is_displayed():
            return
    except TimeoutError:
        pass
    if not tap_if_present(driver, CLOSEUP_BACK_BUTTON, timeout=3):
        try:
           driver.back()
        except Exception:  # noqa: BLE001 - recuperação de esforço
           pass


def _like_current_pin(driver) -> bool:
    return tap_if_present(driver, REACT_BUTTON, timeout=4)


def _save_current_pin(driver) -> bool:
    return tap_if_present(driver, SAVE_BUTTON, timeout=4)


def _view_comments(driver) -> bool:
    if not tap_if_present(driver, COMMENTS_MODULE, timeout=4):
        return False
    time.sleep(random.uniform(1, 2))
    try:
        driver.back()
    except Exception:  # noqa: BLE001 - recuperação de esforço
       pass
    return True


def _scroll_feed(driver):
    """Desliza a feed por uma quantidade aleatória, imitando um scroll ocioso em vez de
    apenas tocar no primeiro pin que aparece visível."""
    size = driver.get_window_size()
    x = int(size["width"] * 0.5)
    start_y = int(size["height"] * random.uniform(0.55, 0.7))
    end_y = int(size["height"] * random.uniform(0.2, 0.35))
    driver.swipe(x, start_y, x, end_y, random.randint(400, 900))


WARM_UP_PIN_ACTIONS = [_like_current_pin, _save_current_pin, _view_comments]


def warm_up(driver, min_actions=WARM_UP_MIN_ACTIONS, max_actions=WARM_UP_MAX_ACTIONS):
    """Executa um conjunto aleatório de ações de navegação na feed principal (abrir um pin,
    então curtir/salvar/ver seus comentários, depois voltar) logo após o login e antes
    que qualquer pin seja publicado"""
    ensure_on_home_feed(driver)
    dismiss_popups(driver)

    action_count = random.randint(min_actions, max_actions)
    print(f"   [WARMUP] executando {action_count} ação(s) de aquecimento")

    done = 0
    attempts = 0
    while done < action_count and attempts < action_count * 3:
        attempts += 1
        if random.random() < 0.3:
            _scroll_feed(driver)
           time.sleep(random.uniform(1, 2))
           continue
         if not _open_random_pin(driver):
            _scroll_feed(driver)  # nada tátil ainda -- carregar mais da feed
            time.sleep(1)
            continue
        time.sleep(random.uniform(1.5, 2.5))  # deixe a visualização detalhada terminar de carregar
        dismiss_popups(driver)
        random.choice(WARM_UP_PIN_ACTIONS)(driver)
        time.sleep(random.uniform(1, 2))
        _fechar_pin(driver)
        desfazer_popups(driver)
        # Verifique novamente após cada iteração.
       tente:
            garantir_no_feed_inicial(driver)
       exceto TimeoutError como exc:
            print(f"   [AQUECIMENTO] perdeu o feed inicial no aquecimento, parando cedo: {exc}")
            break
        time.sleep(random.uniform(0.5, 1.5))
       feito += 1

    garantir_no_feed_inicial(driver)
    desfazer_popups(driver)

# Upload de imagem

def analisar_intervalo_imagem(intervalo_str: str, linha_no: int):
    """Analisa um intervalo como '1-3' ou um índice único como '4'."""
    tente:
        se "-" em intervalo_str:
            start_str, end_str = intervalo_str.split("-", 1)
        start, end = int(start_str), int(end_str)
        senão:
            start = end = int(intervalo_str)
    exceto ValueError:
        levantar ValueError(f"devices.txt linha {linha_no}: intervalo de imagem inválido {intervalo_str!r} (esperado por exemplo '1-3' ou '4')")
    se start < 1 ou end < start:
       levantar ValueError(f"devices.txt linha {linha_no}: intervalo de imagem inválido {intervalo_str!r} (esperado por exemplo '1-3' ou '4')")
    retornar start, end


def analisar_dispositivos(caminho: str):
    dispositivos = []
    para linha_no, linha_raw em enumerate(Path(caminho).read_text().splitlines(), start=1):
        linha = linha_raw.strip()
       se não linha ou linha.startswith("#"):
            continuar
       partes = linha.split()
        se len(partes) == 4:
           serial, token, username, password = partes
           imagem_range = None  # -> obtém todas as imagens na pasta, veja main()
        elif len(partes) == 5:
           serial, token, username, password, range_str = partes
            imagem_range = analisar_intervalo_imagem(range_str, linha_no)
        senão:
            levantar ValueError(
                f"devices.txt linha {linha_no}: esperado 4 ou 5 campos "
                f"(IP:PORT TOKEN USERNAME PASSWORD [IMAGE_RANGE]), obtido {len(partes)}: {linha_raw!r}"
             )
        dispositivos.append(dict(serial=serial, token=token, username=username, password=password, imagem_range=imagem_range))
    retornar dispositivos


def resolver_imagens(imagens_arg: str):
    """Retorna todas as imagens encontradas, ordenadas alfabeticamente. Pordispositivo IMAGE_RANGE
    campos em devices.txt índice nesta mesma lista ordenada."""
    p = Path(imagens_arg)
    se p.is_file():
       retornar [str(p)]
    retornar sorted([str(f) para f em p.iterdir() se f.suffix.lower() em (".jpg", ".jpeg", ".png")])

# Auto-descoberta: se um argumento for omitido, procure por ele ao lado do script

def autodiscover_dispositivos_arquivo(base: Path) -> Path:
    candidato = base / "devices.txt"
    se candidato.is_file():
       retornar candidato
    levantar FileNotFoundError(f"Nenhum devices.txt encontrado em {base}")


def autodiscover_apk(base: Path) -> Path:
    apks = sorted(base.glob("*.apk"))
    se not apks:
        levantar FileNotFoundError(f"Nenhum .apk arquivo encontrado em {base}")
    se len(apks) > 1:
        print(f">> vários .apk arquivos encontrados em {base}, usando {apks[0].name}")
    retornar apks[0]


def autodiscover_imagens(base: Path) -> Path:
    imagens_dir = base / "imagens"
    se imagens_dir.is_dir():
        retornar imagens_dir
    solto = [f para f em base.iterdir() se f.suffix.lower() em (".jpg", ".jpeg", ".png")]
    se solto:
       retornar base
    levantar FileNotFoundError(f"Nenhuma imagens/ pasta ou .jpg/.png arquivos encontrados em {base}")


def executar_dispositivo(dispositivo: dict, apk_path: str, image_paths: list):
    serial = dispositivo["serial"]
    print(f"\n=== {serial} ===")

    se não conectar_e_login(serial, dispositivo["token"]):
       retornar
    se não instalar_apk(serial, apk_path):
       desconectar(serial)
        retornar

    conceder_acesso_completo_a_midia(serial)
    imagens_remotas = enviar_imagens(serial, image_paths)

    driver = None
    tente:
        driver = construir_driver(serial)
        login(driver, dispositivo["username"], dispositivo["password"])
        tente:
            warm_up(driver)
        except Exception as exc:  # noqa: BLE001 - warm-up é um esforço máximo, publicar ainda é importante
        print(f"   [WARMUP_FAILED] {serial}: {exc}")
       publicados = 0
       total = len(remote_images)
       for i, imagem_remota in enumerate(remote_images, start=1):
           print(f"   [PUBLISHING] {serial}: pin {i}/{total}: {Path(remote_image).name}")
           try:
                publish_pin(driver, imagem_remota)
                 publicados += 1
            except Exception as exc:  # noqa: BLE001 - um pin lento/falhado não deveria custar o restante
                print(f"   [PIN_FAILED] {serial}: {Path(remote_image).name}: {exc}")
           time.sleep(2)
        print(f"   [DONE] {serial}: publicados {published}/{len(remote_images)} pin(s)")
    except Exception as exc:  # noqa: BLE001 - continuar para o próximo telefone de qualquer forma
        print(f"   [ERROR] {serial}: {exc}")
    finally:
       if driver:
            driver.quit()
        disconnect(serial)


def main():
    script_dir = Path(__file__).resolve().parent

    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("devices_file", nargs="?", default=None,
                     help=f"IP:PORT TOKEN USERNAME PASSWORD [IMAGE_RANGE], um por linha "
                             f"(padrão: devices.txt próximo a este script, ou {script_dir})")
    parser.add_argument("apk_path", nargs="?", default=None,
                      help="Caminho para o APK do Pinterest para instalar (padrão: o .apk encontrado próximo a este script)")
    parser.add_argument("images", nargs="?", default=None,
                          help="Pasta de imagens, ou um arquivo de imagem única "
                               "(padrão: uma pasta images/ ou arquivos soltos .jpg/.png próximo a este script)")
    args = parser.parse_args()

    try:
        devices_file = Path(args.devices_file) if args.devices_file else autodiscover_devices_file(script_dir)
        apk_path = Path(args.apk_path) if args.apk_path else autodiscover_apk(script_dir)
       images_arg = Path(args.images) if args.images else autodiscover_images(script_dir)
    except FileNotFoundError as exc:
        sys.exit(f"{exc}\nPasse devices_file / apk_path / imagens explicitamente, ou coloque elas próximo a o script.")

     print(f">> arquivo devices: {devices_file}")
    print(f">> apk: {apk_path}")
    print(f">> imagens: {images_arg}")

    try:
        devices = parse_devices(devices_file)
    except ValueError as exc:
        sys.exit(str(exc))

    all_images = resolve_images(images_arg)
    if not all_images:
        sys.exit(f"Nenhuma imagem encontrada em {images_arg}")

    print(f">> {len(devices)} telefone(s) na fila, {len(all_images)} imagem(ns) disponível(is):")
    for i, image_path in enumerate(all_images, start=1):
        print(f"     {i}: {Path(image_path).name}")

    for device in devices:
        start, end = device["image_range"] or (1, len(all_images))
        if end > len(all_images):
        print(f"   [SKIP] {device['serial']}: intervalo de imagem {start}-{end} excede "
                 f"as {len(all_images)} imagem(ns) disponível(is)")
           continue
        image_subset = all_images[start - 1:end]
       run_device(device, str(apk_path), image_subset)


if __name__ == "__main__":
    main()
 
 
  1. Abra seu IDE ou terminal e execute:
    python appium_script.py

O script mostrará seu progresso no terminal. Ele se conectará ao primeiro telefone, completará o fluxo de trabalho do Pinterest, desconectará e, em seguida, continuará com o próximo telefone.

Quer tentar outro aplicativo Android? Este é um script de demonstração, não uma ferramenta pronta para todos os serviços. Você pode adaptá-lo, mas cada aplicativo usa botões e localizadores de elementos diferentes. Use Appium Inspector para encontrar os localizadores corretos e atualizar o script para o seu aplicativo.

 

Este artigo foi útil?

Dê seu feedback sobre este artigo

Neste artigo

  • O que o script faz
  • Antes de começar
  • Configurando devices.txt
  • Executando o script
  • appium_script.py

Comunidade Multilogin

Mantenha-se informado, compartilhe suas opiniões e envolva-se com os outros!

Telegram Icon Junte-se a nós no Telegram

Leia mais sobre o assunto

Blog Post Img

10 Best Datacenter Proxies for Web Scraping (2025 Edition)

Apr 2, 2025 5 min read
Google SERP Img

What is a Google SERP Proxy and Why Should You Care?

Apr 1, 2025 6 min read
UK Proxy Img

What Are Dedicated UK Proxies? Everything You Need to Know

Apr 1, 2025 6 min read
Related Article Title Icon

Artigos relacionados

  • Como automatizar o login em um site
  • Fazendo login no Multilogin automaticamente
  • Como recuperar tokens API com Python
  • Exportando as credenciais de proxy dos perfis

Soluções Mobile

  • Telefone na nuvem
  • Celular virtual
  • Celular remoto
  • Fazenda de celulares
  • Celular na nuvem
  • Emulador android na nuvem
  • Automação de ações rápidas de IA

Multi-contabilidade

  • Crie várias contas no Instagram
  • Criar várias contas no TikTok
  • Criar várias contas no Reddit
  • Criar várias contas no Telegram
  • Crie várias contas no Facebook
  • Criar várias contas no YouTube
  • Crie várias contas no LinkedIn

Comparação

  • Multilogin vs. MoreLogin
  • Multilogin vs. FlashID
  • Multilogin vs. DuoPlus
  • Multilogin vs. VMOS Cloud
  • Multilogin vs. Octo Browser
  • Multilogin vs. AdsPower
  • Multilogin vs. GoLogin

Proxies de plataforma

  • Proxy móvel
  • Proxy Reddit
  • Proxy do Facebook
  • Proxy SOCKS5
  • Proxy do Instagram
  • Proxy OnlyFans
  • Proxy do LinkedIn

Casos de Uso

  • Celular na nuvem para TikTok
  • Celular na nuvem para Instagram
  • Celular na nuvem para Reddit
  • Celular na nuvem para Facebook
  • Celular na nuvem para YouTube

Recursos

  • Central de conhecimentos
  • Documentação da API
  • Glossário
  • Blog
  • Status do aplicativo
  • Notas de versão

FERRAMENTAS GRATUITAS

  • YouTube views to money calculator
  • Instagram money calculator
  • Conversor online de URL para texto
  • Google local SERP checker
  • Random address generator

Entre em contato

  • Suporte 24/7
    support@multilogin.com
  • Programa de indicação
  • Programa de afiliados
  • Página de preços
  • Carreiras
GDPR Compliant

© 2026 Multilogin. All rights reserved.

  • Privacy policy
  • Terms of service
  • Cookie policy
Multilogin watermark
  • SOLUÇÕES MOBILE

    • Telefone na nuvem
    • Celular virtual
    • Celular remoto
    • Fazenda de celulares
    • Celular na nuvem
    • Emulador android na nuvem
    • Automação de ações rápidas de IA
  • MULTI-CONTABILIDADE

    • Crie várias contas no Instagram
    • Criar várias contas no TikTok
    • Criar várias contas no Reddit
    • Criar várias contas no Telegram
    • Crie várias contas no Facebook
    • Criar várias contas no YouTube
    • Crie várias contas no LinkedIn
  • COMPARAÇÃO

    • Multilogin vs. MoreLogin
    • Multilogin vs. FlashID
    • Multilogin vs. DuoPlus
    • Multilogin vs. VMOS Cloud
    • Multilogin vs. Octo Browser
    • Multilogin vs. AdsPower
    • Multilogin vs. GoLogin
  • PROXIES DE PLATAFORMA

    • Proxy móvel
    • Proxy Reddit
    • Proxy do Facebook
    • Proxy SOCKS5
    • Proxy do Instagram
    • Proxy OnlyFans
    • Proxy do LinkedIn
  • CASOS DE USO

    • Celular na nuvem para TikTok
    • Celular na nuvem para Instagram
    • Celular na nuvem para Reddit
    • Celular na nuvem para Facebook
    • Celular na nuvem para YouTube
  • RECURSOS

    • Central de conhecimentos
    • Documentação da API
    • Glossário
    • Blog
    • Status do aplicativo
    • Notas de versão
  • FERRAMENTAS GRATUITAS

    • YouTube views to money calculator
    • Instagram money calculator
    • Conversor online de URL para texto
    • Google local SERP checker
    • Random address generator
  • ENTRE EM CONTATO

    • Suporte 24/7
      support@multilogin.com
    • Programa de indicação
    • Programa de afiliados
    • Página de preços
    • Carreiras
GDPR Compliant
  • Privacy policy
  • Terms of service
  • Cookie policy

© 2026 Multilogin. All rights reserved.

Expand