• Trang web
  • Trạng thái ứng dụng
  • Tài liệu API
  • Blog
Telegram Icon Cộng đồng
VI
Русский
Português
English
中文 (中国)
Tiếng Việt
Đăng nhập Thử miễn phí
  • Trang web
  • Trạng thái ứng dụng
  • Tài liệu API
  • Blog
  • Cộng đồng








  • Vietnamese









    Русский










    Português










    English










    中文 (中国)










    Tiếng Việt








Đăng nhập Thử miễn phí
search icon

Hãy thử từ khóa khác hoặc kiểm tra lỗi chính tả. Nếu bạn vẫn cần trợ giúp, hãy liên hệ bộ phận hỗ trợ.

  • Trang chủ
  • breadcrumb separator bar
  • Cách tự động đăng bài trên Pinterest bằng điện thoại đám mây với Appium

Cách tự động đăng bài trên Pinterest bằng điện thoại đám mây với Appium

Được viết bởi Anton L ( Cập nhật vào Ngày 17 tháng bảy năm 2026 )

Cập nhật vào Ngày 17 tháng bảy năm 2026

Multilogin điện thoại đám mây hỗ trợ ADB, vì vậy bạn có thể kết nối chúng với Android như Appium.

Trong ví dụ này, một script Python xử lý công việc lặp đi lặp lại trên Pinterest qua nhiều điện thoại đám mây. Nó kết nối với từng điện thoại, đăng nhập vào tài khoản Pinterest của nó, di chuyển các hình ảnh được chỉ định, thực hiện một vài hành động mẫu trên nguồn cấp, và xuất bản các hình ảnh dưới dạng Pins.

➡️ Bạn không cần phải mở từng điện thoại đám mây và lặp lại các hành động giống nhau một cách thủ công. Thêm điện thoại và tài khoản của bạn vào devices.txt, khởi động script, và để nó làm việc qua danh sách cho bạn.

Script xử lý các điện thoại đám mây từng cái một. Nó hoàn thành toàn bộ quy trình trên một điện thoại trước khi chuyển sang cái tiếp theo.

Đây là một script minh họa. Nó cho thấy những gì bạn có thể xây dựng với Multilogin điện thoại đám mây, ADB và Appium. Tự động hóa Pinterest không được tích hợp trong Multilogin.

 
Your browser does not support HTML5 video.

Script làm gì

Khi mọi thứ đã sẵn sàng, script:

  • Kết nối với một điện thoại đám mây qua ADB
  • Cài đặt Pinterest từ một tệp APK
  • Di chuyển hình ảnh từ máy tính của bạn đến điện thoại đám mây
  • Đăng nhập vào tài khoản Pinterest mà bạn cung cấp
  • Chạy một vài tương tác mẫu trên nguồn cấp để cho thấy việc điều hướng ứng dụng tự động
  • Xuất bản các hình ảnh được chỉ định
  • Chuyển sang điện thoại đám mây tiếp theo trong danh sách

Mỗi điện thoại đám mây có thể sử dụng tài khoản Pinterest và phạm vi hình ảnh riêng. Điều này giúp dễ dàng tổ chức nội dung cho nhiều hồ sơ xã hội từ một thiết lập.

Các tương tác mẫu trên nguồn cấp cũng có thể được điều chỉnh cho quy trình làm ấm tài khoản. Kiểm tra Cách làm ấm một hồ sơ trong Multilogin để biết thêm chi tiết.

Hãy nhớ rằng một vài tương tác tự động không đảm bảo độ tin cậy của tài khoản hoặc ngăn chặn các hạn chế của nền tảng. Luôn tuân theo quy tắc của Pinterest và chỉ tự động hóa các tài khoản mà bạn được phép quản lý.

 

Trước khi bắt đầu

Bạn sẽ cần:

  • Appium và UiAutomator2 được cài đặt trên máy tính của bạn: Tài liệu Appium
  • Một điện thoại đám mây Multilogin đã khởi động với ADB được bật: Cách sử dụng ADB trong điện thoại đám mây
  • Một tài khoản thử nghiệm Pinterest và thông tin đăng nhập của nó
  • Tệp APK của Pinterest
  • Một thư mục tên là images với các hình ảnh bạn muốn xuất bản
  • Một tệp văn bản tên là devices.txt

Giữ script, tệp APK, devices.txt, và images trong cùng một thư mục dự án. Điều này giúp script tìm mọi thứ tự động.

 

Thiết lập devices.txt

Thêm một điện thoại đám mây mỗi dòng theo định dạng này:

IP:PORT ADB_PASSWORD PINTEREST_EMAIL PINTEREST_PASSWORD IMAGE_RANGE

Ví dụ:

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

Dưới đây là ý nghĩa của từng giá trị:

  • IP:PORT: địa chỉ ADB của điện thoại đám mây
  • ADB_PASSWORD: mật khẩu được sử dụng để kết nối qua ADB
  • PINTEREST_EMAIL: email tài khoản Pinterest
  • PINTEREST_PASSWORD: mật khẩu tài khoản Pinterest
  • IMAGE_RANGE: các hình ảnh được gán cho tài khoản đó

Ví dụ, 1-3 cho script biết sử dụng ba hình ảnh đầu tiên từ thư mục images đã được sắp xếp theo thứ tự chữ cái.

Bạn có thể chia hình ảnh giữa nhiều điện thoại đám mây:

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

Điện thoại đầu tiên nhận các hình ảnh 1–3. Điện thoại thứ hai nhận các hình ảnh 4–5.

Bỏ qua phạm vi hình ảnh nếu bạn muốn một điện thoại sử dụng mọi hình ảnh trong thư mục.

Giữ thông tin đăng nhập của bạn an toàn. devices.txt lưu trữ mật khẩu và thông tin nhạy cảm khác dưới dạng văn bản thuần túy. Sử dụng tài khoản thử nghiệm, giữ tệp riêng tư, không bao giờ tải nó lên kho công khai, và xóa nó khi bạn không còn cần nữa.

 

Chạy script

  1. Bắt đầu mọi điện thoại đám mây được bao gồm trong devices.txt
  2. Bật ADB trên mỗi điện thoại đám mây
  3. Bắt đầu máy chủ Appium trên máy tính của bạn
  4. Kiểm tra rằng script, tệp APK, devices.txt, và images thư mục nằm ở cùng một vị trí
  5. Lưu script dưới đây với tên appium_script.py

appium_script.py

"""

Cách sửdụng
-----
Chạy script này và bao gồm nó trong thư mục với hai tệp: devices.txt và thư mục images .

devices.txt   -- một điện thoại mỗi dòng:  IP:PORT TOKEN USERNAME PASSWORD [IMAGE_RANGE]
                IMAGE_RANGE là một số hình ảnh được sử dụng cho tài khoản cụ thể, ví dụng "1-3"
                vào danh sách sắp xếp theo thứ tự chữ của các hình ảnh trong images/
                Bỏ qua nó để cho điện thoại sử dụng tất cả hình ảnh trong thư mục. Ví dụ, chia 5 hình ảnh cho 2
                điện thoại (điện thoại 1 nhận các hình ảnh 1-3, điện thoại 2 nhận các hình ảnh 4-5):
                 199.190.44.226:25922 j3fYxT user1 pass1 1-3
                    199.190.44.227:25922 k9pQrS user2 pass2 4-5
images/       -- một thư mục hình ảnh (jpg/png), hoặc một tệp hình ảnh đơn.

Cần:  pip install Appium-Python-Client
          Máy chủ Appium đang chạy cục bộ (appium) và ANDROID adb trên PATH.

Script cài đặt APK và các hình ảnh trên điện thoại, mở Pinterest, nhập thông tin đăng nhập của tài khoản, chạy một vài tương tác mẫu trên nguồn cấp, và xuất bản các hình ảnh được chỉ định.
"""

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

# Giới hạn làm ấm
WARM_UP_MIN_ACTIONS = 3
WARM_UP_MAX_ACTIONS = 5

# Thư mục chúng tôi đẩy hình ảnh vào
REMOTE_IMAGE_DIR = "/sdcard/Pictures/bulk_publisher"



# chức năng kết nối và đăng nhập

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:
    """Kết nối ADB, sử dụng tên/ địa chỉ điện thoại và glogin."""
    print(f"   [KẾTNỐI] {serial}: đang kết nối qua 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"   [KẾTNỐI] {serial}: chưa trực tuyến chưa (attempt {attempt}/{CONNECT_RETRIES}, state={state or 'unknown'}), đang thửlại...")
        adb("disconnect", serial)
    time.sleep(CONNECT_RETRY_SLEEP)
    else:
       print(f"   [KẾTNỐITHẤTBẠI] {serial}")
    return False

    print(f"   [KẾTNỐITHÀNHCÔNG] {serial}: trực tuyến, đang đăng nhập (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}: đã đăng nhập")
        return True
    print(f"   [GLOGINTHẤTBẠI] {serial}: {result.stdout or 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)
        nếu "thànhcông" trong result.stdout.lower():
            trảvề True
       time.sleep(3)
    print(f"   [INSTALL_FAIL] {serial}: {result.stdout hoặc result.stderr}")
    trảvề False


def grant_full_media_access(serial: str):
    """Cấp quyền truycập thư viện hình ảnh/video hoànchỉnh qua 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:
    """Bản đồ tên (nếu kịchbản không tìm thấy hình ảnh mặc định)"""
    stem, suffix = Path(name).stem, Path(name).suffix
    trảvề re.sub(r"[^A-Za-z0-9._-]", "_", stem) + suffix


def push_images(serial: str, image_paths, remote_dir=REMOTE_IMAGE_DIR) -> list:
    """Đẩy hình ảnh cụcbộ lên điện thoại và kích hoạt quét media"""
    adb("shell", "mkdir", "-p", remote_dir, serial=serial)
    remote_paths = []
    used_names = set()
    total = len(image_paths)
    cho i, local_path trong enumerate(image_paths, start=1):
        safe_name = sanitize_filename(Path(local_path).name)
        candidate, n = safe_name, 1
       trongkhi candidate trong used_names:  # hai tên gốc đã được chuẩnhóa thành cùng một tên
        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}: đang đẩy hình ảnh {i}/{total}: {Path(local_path).name}")
       push_result = adb("push", local_path, remote_path, serial=serial)
        nếu push_result.returncode != 0 hoặc "lỗi" trong push_result.stdout.lower():
            print(f"   [PUSH_FAIL] {serial}: {Path(local_path).name}: "
                  f"{push_result.stdout.strip() hoặc push_result.stderr.strip()}")
            tiếptục

        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,
        )
        nếu scan_result.returncode != 0:
            print(f"   [SCAN_FAIL] {serial}: {Path(local_path).name}: "
                  f"{scan_result.stdout.strip() hoặc scan_result.stderr.strip()}")
        remote_paths.append(remote_path)
    trảvề remote_paths


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



def build_driver(serial: str):
    """Khởi động phiên Appium sau đó khởi động 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)
    trảvề driver


def find_any(driver, candidates, timeout=20, poll=0.5, require_enabled=False):
    """Thử mỗi (theo, giátrị) định vị các ứng cử trong thứtự cho đến khi một cái được giảiquyết,
    quét cho đến hết thờigian thay vì thấtbại trên cái đầutiên không trúng."""
    deadline = time.time() + timeout
    last_err = None
    trongkhi time.time() < deadline:
    cho by, value trong candidates:
            cốgắng:
                el = driver.find_element(by=by, value=value)
                 nếu el.is_displayed() và (không require_enabled hoặc el.get_attribute("enabled") == "true"):
                    trảvề el
            ngoạitrừ Exception như exc:  # noqa: BLE001 - cố gắng nhiều chiếnlược có mụcđích
                last_err = exc
        time.sleep(poll)
    raise TimeoutError(f"Không có định vị nào khớp trong {timeout}s: {candidates}") từ 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:
    """Giống tap_any, nhưng trả về False thay vì nâng khi không có gì khớp
    -- cho các popups chỉ thỉnh thoảng xuấthiện."""
    cốgắng:
        tap_any(driver, candidates, timeout=timeout)
        return True
    except TimeoutError:
        return False

# Giai đoạn 1: Màn hình đăng nhập chỉ 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"),
]

# Giai đoạn 2: Màn hình nhập email + mật khẩu, sau đó "Đăng nhập".
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"),
]

# Các popup có thể xuất hiện tại các điểm không lường sau khi đăng nhập.
EMAIL_UPDATE_DISMISS = [
    (AppiumBy.ID, "com.pinterest:id/actionPromptDismissButton"),  # "Đây là email của bạn?" -> đóng
]
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"),
]

# Mở "Bắt đầu tạo ngay" sheet cuối. Các script tìm các selector tùy thuộc vào độ phân giải và kích thước
CREATE_ICON_BUTTON = [
    (AppiumBy.XPATH,
     '//android.widget.Button[@content-desc="Tạo"]'
     '/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="Tạo"]'
     '/android.widget.LinearLayout[@resource-id="com.pinterest:id/tab_content"]'),
]

# Tab nav "Trang chủ" 
HOME_TAB_BUTTON = [
    (AppiumBy.ACCESSIBILITY_ID, "Trangchủ"),
    (AppiumBy.ID, "com.pinterest:id/bottom_nav_home_icon"),
]

# "Bắt đầu tạo ngay" sheet xuất hiện sau khi chạm Tạo -- chọn "Ghim"
PIN_ACTION_BUTTON = [
    (AppiumBy.XPATH, '//android.widget.Button[@content-desc="Ghim"]'),
]

# Trình chọn medias "Tiếp theo" button (được kích hoạt sau khi chọn một hình ảnh).
GALLERY_NEXT_BUTTON = [
    (AppiumBy.ID, "com.pinterest:id/end_container_text_button"),
]

# Gửi cuối trên màn hình chitiết Ghim -- được đánh dấu "Tạo".
CREATE_PIN_SUBMIT_BUTTON = [
    (AppiumBy.ID, "com.pinterest:id/create_gestalt_button"),
]

# --- Khởi động các locator (duyệt feed trang chủ: mở một ghim, thích/lưu/bình luận) ---

# Generic home-feed pin card. Script tìm các ghim trên màn hình cho điện thoại/tài khoản/session.
PIN_CARD = [
    # Tìm một match có thể nhấn. Fallback đến các match nếu không có match nhấn nào được tìm, trong trường hợp một
    (AppiumBy.XPATH, '//*[contains(@content-desc, "Ghim từ") và @clickable="true"]'),
    (AppiumBy.XPATH, '//*[contains(@content-desc, "Ghim từ")]'),
]
REACT_BUTTON = [
    (AppiumBy.XPATH, '//android.widget.ImageView[@content-desc="Phản ứng với ghim này"]'),
]
SAVE_BUTTON = [
    (AppiumBy.ID, "com.pinterest:id/save_pinit_bt"),
    (AppiumBy.XPATH, '//android.widget.Button[@content-desc="Lưu"]'),
]
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, "Quaylại"),
]


def dismiss_popups(driver):
    """Xóa prompt 'Đây là email của bạn?' và các hộp thông báo Android (thông báo, quyền truy cập ảnh) nếu bất kỳ cái nào trên màn hình."""
    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):
    """Xác nhận biểu tượng Tạo (trang chủ) đang hiện, chủ động đi
    lùi nếu không."""
    for _ in range(max_attempts):
        try:
            find_any(driver, CREATE_ICON_BUTTON, timeout=4)
            return
        except TimeoutError:
            pass
        if not tap_if_present(driver, HOME_TAB_BUTTON, timeout=3):
            try:
                driver.back()
            except Exception:  # noqa: BLE001 - tái cố nỗ lực, tiếp tục thử
                pass
        time.sleep(settle_seconds)

    # Kiểm tra tính hợp lý nếu ứng dụng đang bị thu nhỏ
   driver.activate_app(PINTEREST_PACKAGE)
    try:
        find_any(driver, CREATE_ICON_BUTTON, timeout=10)
        return
    except TimeoutError:
        pass
    raise TimeoutError("Không thể quay trở về feed trang chủ Pinterest (biểu tượng Tạo không xuất hiện)")


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

    try:
        find_any(driver, EMAIL_ONLY_FIELD, timeout=15).send_keys(username)
        tap_any(driver, CONTINUE_BUTTON)
    except TimeoutError:
        pass  # đã ở trên màn hình nhập email+password kếthợp trước

    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)
    # Các phiên làm việc có thể bỏ qua tờ "Bắt đầu tạo bây giờ" và trực tiếp
    # đến trình chọn ảnh, vì vậy việc thiếu tờ này không phải là thảm họa.
    tap_if_present(driver, PIN_ACTION_BUTTON, timeout=6)
    dismiss_popups(driver)  # hộp thông báo quyền truy cập ảnh thường xuất tại đây

    # Khớp với "<thư mục tải>/<tên_tập_tin>", không chỉ tên_tập_tin -- một điện thoại đã
    # có file cùng tên ở nơi khác (vídụ. Tải xuống/).
    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)  # lưới thư viện có thể mất một thời gian để điền
    # "Tiếp" bắt đầu vô hiệu cho đến khi việc chọn được đăng ký -- chờ cho enabled=true,
    # không chỉ hiện, hoặc nhấn im trên một nút bị vô hiệu.
    tap_any(driver, GALLERY_NEXT_BUTTON, timeout=15, require_enabled=True)

    # 45s ở đây bởi Pinterest phải hoàn tất việc tải/xử lý
    # hình ảnh trước khi nút này hiện.
    tap_any(driver, CREATE_PIN_SUBMIT_BUTTON, timeout=45)
    ensure_on_home_feed(driver)
    dismiss_popups(driver)


def _open_random_pin(driver) -> bool:
    """Nhấn một pin ngẫu nhiên trên thời gian chính ."""
    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 - thử các ứng viên tiếp
           continue
       if not cards:
           continue
        try:
           random.choice(cards).click()
           return True
        except Exception:  # noqa: BLE001 - một nhấn bị bỏ qua chỉ có nghĩa "thử cái khác"
           continue
    return False


def _close_pin(driver):
    """Đóng pin góc nhìn."""
    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 - cố gắng khôi phục
           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 - cố gắng khôi phục
        pass
    return True


def _scroll_feed(driver):
    """Kéo thời gian trong thời gian ngẫu nhiên, giả lập cuộn nhàn thay vì chỉ nhấn vào pin đầu tiên hiện ra."""
    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):
    """Chạy một số hành động duy nhất trong thời gian chính (mở một pin,
    rồi thích/lưu/xem bình luận, rồi quay lại) ngay sau khi đăng nhập và trước
    khi có pin nào được xuất bản"""
    ensure_on_home_feed(driver)
    dismiss_popups(driver)

    action_count = random.randint(min_actions, max_actions)
    print(f"   [WARMUP] đang chạy {action_count} hành động khởi động(s)")

    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)  # không có gì để nhấn đến -- tải thêm nhiều nội dung của luồng
            time.sleep(1)
            continue
        time.sleep(random.uniform(1.5, 2.5))  # để cho góc nhìn hoàn tất tải
        dismiss_popups(driver)
        random.choice(WARM_UP_PIN_ACTIONS)(driver)
        time.sleep(random.uniform(1, 2))
        _close_pin(driver)
        dismiss_popups(driver)
        # Kiểmtralại sau mỗi lầnlặp.
       try:
           ensure_on_home_feed(driver)
       except TimeoutError as exc:
           print(f"   [WARMUP] đãmất nềntảng trangchủ giữa khởiđộng, dừng sớm: {exc}")
            break
       time.sleep(random.uniform(0.5, 1.5))
       done += 1

    ensure_on_home_feed(driver)
    dismiss_popups(driver)

# Tải hình

def parse_image_range(range_str: str, line_no: int):
    """Phântích một phạmvi như '1-3' hoặc một chỉsố đơn như '4'."""
    try:
        if "-" in range_str:
            start_str, end_str = range_str.split("-", 1)
        start, end = int(start_str), int(end_str)
       else:
            start = end = int(range_str)
    except ValueError:
        raise ValueError(f"devices.txt dòng {line_no}: phạmvi hình khônghợplệ {range_str!r} (mongđợi vídụ: '1-3' hoặc '4')")
    if start < 1 or end < start:
        raise ValueError(f"devices.txt dòng {line_no}: phạmvi hình khônghợplệ {range_str!r} (mongđợi vídụ: '1-3' hoặc '4')")
    return start, end


def parse_devices(path: str):
    devices = []
    for line_no, raw_line in enumerate(Path(path).read_text().splitlines(), start=1):
        line = raw_line.strip()
       if not line or line.startswith("#"):
            continue
    parts = line.split()
        if len(parts) == 4:
           serial, token, username, password = parts
           image_range = None  # -> lấy tấtcả hình trong thưmục, xem main()
        elif len(parts) == 5:
           serial, token, username, password, range_str = parts
           image_range = parse_image_range(range_str, line_no)
        else:
           raise ValueError(
               f"devices.txt dòng {line_no}: mongđợi 4 hoặc 5 trường "
                f"(IP:PORT TOKEN USERNAME PASSWORD [IMAGE_RANGE]), nhận {len(parts)}: {raw_line!r}"
            )
        devices.append(dict(serial=serial, token=token, username=username, password=password, image_range=image_range))
    return devices


def resolve_images(images_arg: str):
    """Trảvề tấtcả hình đượctìmthấy, sắpxếp theo chữcái. Mỗithiếtbị IMAGE_RANGE
    trường trong devices.txt chỉmục vào cùng danhsách sắpxếpnày."""
    p = Path(images_arg)
    if p.is_file():
        return [str(p)]
    return sorted([str(f) for f in p.iterdir() if f.suffix.lower() in (".jpg", ".jpeg", ".png")])

# Tựđộngpháthiện: nếu một thamsố được bỏqua, tìm nó tiếptheo tới kịchbản

def autodiscover_devices_file(base: Path) -> Path:
    candidate = base / "devices.txt"
    if candidate.is_file():
        return candidate
    raise FileNotFoundError(f"Không tìmthấy devices.txt trong {base}")


def autodiscover_apk(base: Path) -> Path:
    apks = sorted(base.glob("*.apk"))
    if not apks:
        raise FileNotFoundError(f"Không tìmthấy tậptin .apk trong {base}")
    if len(apks) > 1:
       print(f">> nhiều .apk tậptin đượctìmthấy trong {base}, đangsửdụng {apks[0].name}")
    return apks[0]


def autodiscover_images(base: Path) -> Path:
    images_dir = base / "images"
    if images_dir.is_dir():
        return images_dir
    loose = [f for f in base.iterdir() if f.suffix.lower() in (".jpg", ".jpeg", ".png")]
    if loose:
        return base
    raise FileNotFoundError(f"Không tìmthấy thưmụcimages/ hoặc tậptin.jpg/.png trong {base}")


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

    if not connect_and_login(serial, device["token"]):
        return
    if not install_apk(serial, apk_path):
        disconnect(serial)
        return

    grant_full_media_access(serial)
    remote_images = push_images(serial, image_paths)

    driver = None
    try:
        driver = build_driver(serial)
        login(driver, device["username"], device["password"])
       try:
            warm_up(driver)
        except Exception as exc:  # noqa: BLE001 - warm-up là nỗlựctốtnhất, xuấtbản vẫn quantrọng
        print(f"   [WARMUP_FAILED] {serial}: {exc}")
       published = 0
       total = len(remote_images)
       for i, remote_image in enumerate(remote_images, start=1):
            print(f"   [PUBLISHING] {serial}: pin {i}/{total}: {Path(remote_image).name}")
           try:
                publish_pin(driver, remote_image)
                published += 1
            except Exception as exc:  # noqa: BLE001 - một pin chậm/thấtbại không nên làm tốn thời gian
                print(f"   [PIN_FAILED] {serial}: {Path(remote_image).name}: {exc}")
        time.sleep(2)
       print(f"   [DONE] {serial}: đãxuấtbản {published}/{len(remote_images)} pin(s)")
    except Exception as exc:  # noqa: BLE001 - tiếp tục đến điệnthoại tiếp theo cách nào
        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], một đường trên mỗi hàng "
                           f"(mặcđịnh: devices.txt tiếp theo kịchbản này, tứclà {script_dir})")
    parser.add_argument("apk_path", nargs="?", default=None,
                       help="Đườngdẫn đến APK Pinterest để cài đặt (mặcđịnh: file .apk tìm thấy tiếp theo kịchbản này)")
    parser.add_argument("images", nargs="?", default=None,
                           help="Thưmục hình ảnh, hoặc một file hình ảnh đơn "
                                 "(mặcđịnh: thư hình ảnh/ hoặc các file jpg/.png không có chủ đề, tiếp theo kịchbản này)")
    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}\nVuilòng điền devices_file / apk_path / images một cách rõràng, hoặc đặt chúng tiếp theo kịchbản.")

       print(f">> file devices: {devices_file}")
    print(f">> apk: {apk_path}")
    print(f">> hìnhảnh: {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"Không tìm thấy hình ảnh nào tại {images_arg}")

    print(f">> {len(devices)} điệnthoại(s) đãđược xếphàng, {len(all_images)} hìnhảnh(s) cósẵn:")
    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']}: phạmvi hìnhảnh {start}-{end} vượtquá "
                  f"các {len(all_images)} hìnhảnh(s) cósẵn")
           continue
        image_subset = all_images[start - 1:end]
       run_device(device, str(apk_path), image_subset)


if __name__ == "__main__":
    main()
 
 
  1. MởIDE hoặc terminal và chạy:
    python appium_script.py

Kịch bản sẽ hiển thị tiến trình của nó trong terminal. Nó sẽ kết nối với điện thoại đầu tiên, hoàn thành quy trình làm việc của Pinterest, ngắt kết nối, và sau đó tiếp tục với điện thoại tiếp theo.

Bạn muốn thử một ứng dụng Android khác? Đây là một kịch bản trình diễn, không phải là công cụ hoàn chỉnh cho mọi dịch vụ. Bạn có thể điều chỉnh nó, nhưng mỗi ứng dụng sử dụng các nút và bộ định vị phần tử khác nhau. Sử dụng Appium Inspector để tìm các bộ định vị đúng và cập nhật kịch bản cho ứng dụng của bạn.

 

Bài viết này có hữu ích không?

Góp ý về bài viết này

Cộng đồng Multilogin

Hãy cập nhật thông tin, chia sẻ ý kiến của bạn và kết nối với người khác!

Telegram Icon Tham gia Telegram

Đọc thêm về chủ đề này

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

Bài viết liên quan

  • Cách sử dụng cookie được tạo sẵn trong Multilogin
  • Phông chữ của tôi có nên thay đổi trong Multilogin không?
  • Cách proxy và Multilogin thay đổi những gì trang web nhìn thấy
  • Cách tự động khởi chạy ứng dụng máy tính để bàn trong Multilogin
  • Cách khắc phục sự cố khởi động hồ sơ và kết nối proxy

Điện thoại

  • Điện thoại đám mây
  • Điện thoại ảo
  • Điện thoại từ xa
  • Farm điện thoại
  • Điện thoại chạy trên đám mây
  • Trình giả lập Android đám mây
  • TỰ Động Hóa Hành Động Nhanh AI

Đa tài khoản

  • Tạo nhiều tài khoản trên Instagram
  • Tạo nhiều tài khoản trên TikTok
  • Tạo nhiều tài khoản trên Reddit
  • Tạo nhiều tài khoản trên Telegram
  • Tạo nhiều tài khoản Facebook
  • Tạo nhiều tài khoản trên YouTube
  • Tạo nhiều tài khoản LinkedIn

SO SÁNH

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

Proxy nền tảng

  • Proxy di động
  • Proxy cho Reddit
  • Proxy cho Instagram
  • Proxy SOCKS5
  • Proxy cho TikTok
  • Proxy cho Facebook
  • Proxy cho YouTube
  • Proxy cho LinkedIn

Mục đích sử dụng

  • Điện thoại đám mây cho TikTok
  • Điện thoại đám mây cho Instagram
  • Điện thoại đám mây cho Reddit
  • Điện thoại đám mây cho Facebook
  • Điện thoại đám mây cho Youtube
  • Điện thoại đám mây cho mạng xã hội

Nguồn

  • Kiến thức cơ bản
  • Tài liệu API
  • Bảng chú giải
  • Blog
  • Trạng thái ứng dụng
  • Ghi chú phát hành

CÔNG CỤ MIỄN PHÍ

  • Tính thu nhập từ lượt xem YouTube
  • Tính thu nhập từ Instagram
  • URL trực tuyến Để Chuyển Đổi Văn Bản
  • Kiểm tra SERP Google Local
  • Tạo địa chỉ ngẫu nhiên

Liên hệ với chúng tôi

  • Hỗ trợ 24/7
    support@multilogin.com
  • Chương trình giới thiệu
  • Chương trình liên kết
  • Trang giá
  • Nghề nghiệp
GDPR Compliant

© 2026 Multilogin. All rights reserved.

  • Privacy policy
  • Terms of service
  • Cookie policy
Multilogin watermark
  • ĐIỆN THOẠI

    • Điện thoại đám mây
    • Điện thoại ảo
    • Điện thoại từ xa
    • Farm điện thoại
    • Điện thoại chạy trên đám mây
    • Trình giả lập Android đám mây
    • TỰ Động Hóa Hành Động Nhanh AI
  • ĐA TÀI KHOẢN

    • Tạo nhiều tài khoản trên Instagram
    • Tạo nhiều tài khoản trên TikTok
    • Tạo nhiều tài khoản trên Reddit
    • Tạo nhiều tài khoản trên Telegram
    • Tạo nhiều tài khoản Facebook
    • Tạo nhiều tài khoản trên YouTube
    • Tạo nhiều tài khoản LinkedIn
  • SO SÁNH

    • Multilogin vs. MoreLogin
    • Multilogin vs. FlashID
    • Multilogin vs. DuoPlus
    • Multilogin vs. VMOS cloud
    • Multilogin vs. Octo Browser
    • Multilogin vs. AdsPower
    • Multilogin vs. GoLogin
  • PROXY NỀN TẢNG

    • Proxy di động
    • Proxy cho Reddit
    • Proxy cho Instagram
    • Proxy SOCKS5
    • Proxy cho TikTok
    • Proxy cho Facebook
    • Proxy cho YouTube
    • Proxy cho LinkedIn
  • MỤC ĐÍCH SỬ DỤNG

    • Điện thoại đám mây cho TikTok
    • Điện thoại đám mây cho Instagram
    • Điện thoại đám mây cho Reddit
    • Điện thoại đám mây cho Facebook
    • Điện thoại đám mây cho Youtube
    • Điện thoại đám mây cho mạng xã hội
  • NGUỒN

    • Kiến thức cơ bản
    • Tài liệu API
    • Bảng chú giải
    • Blog
    • Trạng thái ứng dụng
    • Ghi chú phát hành
  • CÔNG CỤ MIỄN PHÍ

    • Tính thu nhập từ lượt xem YouTube
    • Tính thu nhập từ Instagram
    • URL trực tuyến Để Chuyển Đổi Văn Bản
    • Kiểm tra SERP Google Local
    • Tạo địa chỉ ngẫu nhiên
  • LIÊN HỆ VỚI CHÚNG TÔI

    • Hỗ trợ 24/7
      support@multilogin.com
    • Chương trình giới thiệu
    • Chương trình liên kết
    • Trang giá
    • Nghề nghiệp
GDPR Compliant
  • Privacy policy
  • Terms of service
  • Cookie policy

© 2026 Multilogin. All rights reserved.

Expand