В предыдущей статье я затрагивал тонкие места в вопросах безопасности при арбитраже трафика, и как это обычно происходит, коснулся темы, ну а дальше понеслась. В процессе написания одной статьи родилось еще две других, но выкладывать их сразу одним махом очень не хочется, поэтому я немного растянул удовольствие.

В общем, копаясь в недрах арбитража (для настоящих гуру эти недра конечно могут показаться сильно на поверхности) я запилил скрипт по автоматическому созданию профилей в антидетект браузере через АПИ. Звучит угрожающе? Ну давайте не будем торопиться с выводами.

Итак, тестируя различные моменты, я подготовил два скрипта, которые, вероятно, могли бы быть полезны для начинающих арбитражников, либо для более продвинутых начинающих арбитражников, которые погружаются в автоматизацию и работу через АПИ (like a boss).

Первый скрипт позволяет в автоматическом режиме генерить сотни профилей, как с автоматически генерируемыми отпечатками браузера, так и со своими собственными (но тогда скрипт нужно будет немного допилить и добавить в папку дополнительный файл с отпечатками).

Второй скрипт позволяет запускать созданные профили не из консоли антидетект браузера, а из терминала. В целом, сам факт запуска конечно мало чем может быть полезным, но это первый шаг на пути к автоматизированному фармингу аккаунтов. Но фарминг аккаунтов это немного другая тема, под которую нужна другая статья. В качестве основы можно как раз и использовать второй скрипт.

Ну а теперь, давайте разбираться более подробно.

Зачем автоматизировать создание профилей в антидетект браузере?

Работа с десятками (а то и сотнями) аккаунтов через антидетект-браузер требует много сил ивремени — даже при хорошем интерфейсе. Ручное создание профилей, настройка прокси, импорт куки для каждого — задачи однообразные и, как оказалось, легко автоматизируемые. Конечно, когда у тебя 10–15 профилей, нет смысла заморачиваться со скриптом (хотя если он уже готовый — почему бы и нет, правда?) Но когда профилей нужно кратно больше? Можно конечно воспользоваться массовым созданием, но кому то (например мне) проще, интереснее, да в конце-концов разумнее написать скрипт, который сделает всё за вас.

Кроме экономии времени, автоматизация снижает вероятность ошибок. Банальный человеческий фактор. Человек может случайно поставить не ту метку или ошибиться при настройке прокси, а правильно написанный скрипт с вами так никогда не поступит и таких промахов не допустит.

Контекст мультиаккаунтинга: Я не устаю твердить в каждой своей статье, где затрагивается тема мультиаккаунтинга, что Антидетект‑браузеры это маст хэв инструмент для данной ниши. Они решают проблему проблему блокировок аккаунтов при массовой работе в сетях и сервисах. Они создают изолированные профили с уникальными параметрами окружения — так, будто у вас десятки разных устройств и браузеров. Можно параллельно вести сотни рекламных кабинетов или аккаунтов в соцсетях с одной машины. И естественно резидентные прокси. Это второй по значимости инструмент для любого автоматизатора. В контексте данного материала буду работать с API антидетект браузера Octo Browser и резидентными проксями 2prx. У первого у меня есть платный доступ (тариф Base который дает доступ к АПИ), а у второго осталось несколько ГБ трафика под резидентные прокси. В общем все сложилось максимально экономически выгодно для семейного бюджета и статьи.

Тандем — резидентные прокси + антидетект‑браузер — является золотым стандартом для большинства задач. Прокси скрывают реальный IP и обходят ограничения, а антидетект маскирует более 50 параметров браузера (Canvas, WebGL, часовой пояс, язык системы, тип устройства и т. д.), но самое главное — он создает изолированный профиль (изолированный в глазах сервиса под который этот профиль и создается). В результате можно эмулировать реальные условия серфинга и решать много других задач..

Задача: упростить и ускорить массовое создание профилей через API. Допустим: нам нужно создать 100 профилей, каждый с уникальным отпечатком браузера и уникальным прокси — вручную эта задача будет решаться долго (я бы точно ковырялся пол дня). Но есть антидетект браузеры, которые предоставляют открытое API. В частности Octo Browser с помощью которого я и буду это делать.

Подготовка: API и исходные данные для автоматизации при работе с антидетект браузером

Чтобы скрипт мог генерить профили ему потребуется ключ API-ключ (токен). Не особо удобно это реализовано в Octo (хотя кому то может наоборот, чистая вкусовщина) — ключ АПИ можно забрать только в десктопном приложении. По мне так проще было бы его на сайте брать, без установки на комп софт. Токен будет нужен для авторизации API-запросов.

Кроме API, понадобятся данные для заполнения профилей:

Список прокси-серверов – прокси я взял на сервисе 2prx. Хорошо там есть пакетная выгрузка, ставишь количество проксей и получаешь прокси в нужном формате. Прокси помещаем в файл proxies.csv в таком формате — тип (HTTP/HTTPS или SOCKS5), хост, порт, логин и пароль . Каждая строка – отдельный прокси. Пример: HTTP,192.0.2.10,24000,mylogin,mypass. Не забудьте указать корректный тип прокси, иначе Octo Browser не примет данные (если не указать тип, запрос создания профиля вернётся с ошибкой 400).

Cookie-файлы (опционально) — если есть необходимость сразу импортировать куки в новые профили (например, чтобы не проходить повторный вход в аккаунт), готовим файл cookies.json. Формат: JSON-объект, где ключи — порядковые номера профилей (начиная с 0), а значения — списки куки для соответствующего профиля. Каждый cookie задаётся со стандартными полями (name, value, domain, path, expirationDate и т.д.). Пример структуры:

{
    "0": [ { "<cookie1>": "..." }, { "<cookie2>": "..." } ],
    "1": [ { /* cookies for profile #2 */ } ],
    "...": ...
}

 Если файл с куки не требуется, скрипт просто пропустит этот шаг.

Переменные окружения — создаем файл .env в папке скрипта. В нём указываем:

  • OCTO_API_TOKEN=<ваш токен> — токен для доступа к Octo API.

  • PROXY_FILE=proxies.csv — имя файла с прокси (если отличается от дефолтного).

  • COOKIE_FILE=cookies.json — имя файла с куки (если используете и имя отличается).

  • PROFILE_COUNT=0 — число профилей для создания. 0 означает «создать по числу прокси в списке». Можно явно указать количество. Например, если прокси 10, а PROFILE_COUNT=20, скрипт создаст 20 профилей, прокси начнут повторяться циклически после десяти.

Python окружение — Также нужно будет установить необходимые библиотеки:

pip install requests python-dotenv

Полный скрипт со всеми необходимыми файлами выложил в свой Гитхаб — Octo Browser Profile Creator

Как работает скрипт для автоматического создания профилей в антидетект браузере

Собственно сам скрипт, ниже расскажу как он работает и что делает:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Batch create Octo Browser profiles.

• Проксипараметры читаются из proxies.csv
• Опциональные cookies — из cookies.json
• Octo сам генерирует fingerprint по "заглушке"
• Кол‑во профилей задаётся PROFILE_COUNT (по‑умолчанию = кол‑во прокси)
"""

import csv
import json
import os
import sys
import time
import itertools
from pathlib import Path
from typing import Any, Dict, List

import requests
from dotenv import load_dotenv

# ───────────────────────── CONFIG ──────────────────────────
API_BASE = "https://app.octobrowser.net/api/v2/automation"
REQ_TIMEOUT = 30  # секунд
DEFAULT_FP = {"os": "win", "screen": "1920x1080"}

BASE_DIR = Path(__file__).parent.resolve()
load_dotenv()  # загружаем .env

TOKEN = os.getenv("OCTO_API_TOKEN", "")
if not TOKEN:
    sys.exit("? Specify OCTO_API_TOKEN in .env")

HEADERS = {"X-Octo-Api-Token": TOKEN}

PROXY_CSV = BASE_DIR / os.getenv("PROXY_FILE", "proxies.csv")
COOKIE_JSON = BASE_DIR / os.getenv("COOKIE_FILE", "cookies.json")
PROFILE_COUNT = int(os.getenv("PROFILE_COUNT", "0"))  # 0 → len(proxies)

# ──────────────────────── UTILITIES ────────────────────────
def sniff(path: Path) -> csv.Dialect:
    sample = path.read_text(encoding="utf-8")[:1024]
    return csv.Sniffer().sniff(sample, delimiters=",;\t ")


def load_proxies(path: Path) -> List[Dict[str, Any]]:
    if not path.exists():
        sys.exit(f"? Proxies file not found: {path}")

    dialect = sniff(path)
    proxies: List[Dict[str, Any]] = []

    with path.open(encoding="utf-8", newline="") as f:
        reader = csv.DictReader(f, dialect=dialect)
        for row in reader:
            p = {k.strip(): v.strip() for k, v in row.items() if v and k}
            try:
                # Octo API принимает строку; int оставляем как валидацию на «число»
                p["port"] = str(p["port"])
            except Exception:
                sys.exit(f"? Invalid proxy row: {row}")
            proxies.append(p)

    if not proxies:
        sys.exit("? No proxies loaded")

    return proxies


def load_cookies(path: Path) -> Dict[str, List[Dict[str, Any]]]:
    if not path.exists():
        return {}

    data = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(data, dict):
        sys.exit("? cookies.json must be a JSON object")

    return data


def api_post(endpoint: str, payload: Dict[str, Any]) -> Dict[str, Any]:
    url = f"{API_BASE}/{endpoint.lstrip('/')}"
    resp = requests.post(url, json=payload, headers=HEADERS, timeout=REQ_TIMEOUT)
    resp.raise_for_status()
    check_limits(resp)  # динамическая пауза по квотам
    return resp.json()["data"]


def check_limits(response: requests.Response) -> None:
    """
    Анализирует заголовки X‑RateLimit и, при необходимости, ставит паузу.

    • rpm  (X‑RateLimit‑Remaining) — остаток запросов в минуту
    • rph  (X‑RateLimit‑Remaining‑Hour) — остаток запросов в час
    При rpm < 10 ждём 60 с, при rph < 10 — 3600 с.
    """

    rpm = int(response.headers.get("x-ratelimit-remaining", 0))
    rph = int(response.headers.get("x-ratelimit-remaining-hour", 0))
    print(f"RPM remaining: {rpm} | RPH remaining: {rph}")

    if rpm < 10:
        print("⚠ Почти упёрлись в лимит RPM, спим минуту…")
        time.sleep(60)
    if rph < 10:
        print("⚠ Почти упёрлись в лимит RPH, спим час…")
        time.sleep(3600)

# ─────────────────────────── MAIN ──────────────────────────
def main() -> None:
    proxies = load_proxies(PROXY_CSV)
    cookies_map = load_cookies(COOKIE_JSON)

    # сколько профилей создаём
    total = PROFILE_COUNT if PROFILE_COUNT > 0 else len(proxies)
    proxy_cycle = itertools.cycle(proxies)

    for idx in range(1, total + 1):
        proxy = next(proxy_cycle)
        title = f"BatchProfile_{idx}"
        cookies = cookies_map.get(str(idx - 1))

        payload: Dict[str, Any] = {
            "title": title,
            "proxy": proxy,
            "fingerprint": DEFAULT_FP,
        }
        if cookies:
            payload["cookies"] = cookies

        try:
            data = api_post("profiles", payload)
            print(f"✅ Profile #{idx} created → UUID {data['uuid']}")
        except requests.HTTPError as e:
            print(f"❌ HTTP error for profile #{idx}: {e}")
            print("   Server response:", e.response.text)

# ────────────────────────── ENTRYPOINT ─────────────────────
if __name__ == "__main__":
    main()

Загрузка конфигурации. Скрипт читает .env и извлекает токен API и настройки файлов. Без токена он работать небудет — при отсутствии переменной OCTO_API_TOKEN скрипт сразу завершится с ошибкой.

Чтение списка прокси. Функция load_proxies() открывает proxies.csv и считывает прокси. Тут используется csv.Sniffer для автоматического определения разделителя (запятая, точка с запятой, табуляция — неважно, скрипт сам распознает) и csv.DictReader, чтобы получить словарь для каждой строки. В результате имеем список словарей proxies, где каждый содержит поля прокси (host, port, etc.). Для удобства валидации порт можно привести к int, в этом скрипте я оставляю его строкой (str) — Octo API принимает оба варианта, так что выбор типа чистая вкусовщина. Если в CSV порт указан некорректно (не числом), вы получите сообщение об ошибке и скрипт остановится: ? Invalid proxy row. Также проверяется, что хотя бы один прокси успешно загружен, иначе дальнейшая работа смысла не имеет.

Чтение куки-файла. Функция load_cookies() загружает cookies.json, если он есть. Она ожидает, что в файле лежит JSON-объект (типа dict в Python). Если внезапно окажется не объект (например, массив или некорректный JSON), скрипт сообщит об этом и остановится: ? cookies.json must be a JSON object. При корректной загрузке мы получаем словарь cookies_map, где ключи – это строки (номера профилей в виде текста), а значения — списки cookie‑диктов для профилей. Если файл отсутствует, cookies_map просто будет пустым — скрипт это учитывает.

Циклический выбор прокси для профилей. Мы определяем, сколько профилей создавать через переменную total = PROFILE_COUNT, если явно указано число, иначе берётся длина списка прокси. PROFILE_COUNT можно оставить 0, тогда профилей будет столько же, сколько прокси. Если профилей нужно больше, чем уникальных прокси: скрипт использует itertools.cycle(proxies) — это кольцевой итератор, который по завершении списка начинает сначала. Таким образом, если прокси меньше, чем требуемых профилей, они будут повторно назначены на новые профили по кругу. Естественно, стоит понимать риски: несколько профилей с одним IP могут вызвать подозрения на некоторых платформах, но технически это возможно и иногда нужно. Если же прокси больше, чем профилей (например, вы указали PROFILE_COUNT=10, а прокси в файле 50), то просто не все прокси будут использованы.

Формирование и отправка API-запросов. Основной цикл for idx in range(1, total+1) пробегается по номерам профилей. Для каждого:

  • Выбирается очередной прокси из cycle.

  • Формируется заголовок профиля title = BatchProfile_<номер>. Потом их можно переименовать если нужно.

Составляется объект payload с данными профиля:

payload = {
    "title": title,
    "proxy": proxy,
    "fingerprint": DEFAULT_FP
}

Здесь proxy — это словарь с параметрами прокси из CSV (авторизация, хост, порт, тип). А fingerprint — важный момент: мы не задаём полный отпечаток браузера вручную, а используем константу DEFAULT_FP = {"os": "win", "screen": "1920x1080"}. То есть, указываем только базовые параметры: операционную систему (Windows) и разрешение экрана. Остальные ~50 параметров отпечатка Octo Browser заполнит автоматически, сгенерировав реалистичный fingerprint под нужную ОС. Технически можно полностью передавать все значения отпечатков браузера, я об этом писал выше. API требует указать минимум OS (win или mac) — это обязательное поле. Все остальные характеристики (User‑Agent, список шрифтов, WebGL‑рендерер, часовой пояс и т. д.) можно не передавать — Octo подберёт случайный отпечаток, соответствующий выбранной ОС и экрану. Это очень упрощает массовое создание профилей, ведь не нужно самостоятельно генерировать каждое поле отпечатка.

Если для данного профиля в cookies_map найдена запись (ключ равен idx-1, потому что в JSON мы нумеровали с 0, а профили в цикле с 1), то эти куки добавляются в payload: payload["cookies"] = <список куки>. Octo API позволяет при создании профиля сразу импортировать cookie-файлы — этим мы и пользуемся. Например, можно заранее сохранить авторизационные куки вашего аккаунта, чтобы новый профиль сразу «помнил» сессию.

Вызывается вспомогательная функция api_post("profiles", payload), которая делает HTTP POST запрос к адресу https://app.octobrowser.net/api/v2/automation/profiles с нашим payload в формате JSON. Токен авторизации передается через заголовок X-Octo-Api-Token. Эта функция обёрнута в try/except на случай HTTP-ошибки.

Обработка ответа: Если запрос успешен (код 200), Octo вернёт в поле «data» информацию о созданном профиле, включая его уникальный идентификатор uuid. Скрипт выводит в консоль: ✅ Profile #X created → UUID <...>. Если же произошла ошибка (например, неверный формат данных), requests.post вызовет исключение. Мы его ловим и печатаем сообщение об ошибке: ❌ HTTP error for profile #X: <текст>, а также выводим ответ сервера (e.response.text) для диагностики. После каждого запроса скрипт делает небольшую паузу time.sleep(PAUSE). По умолчанию PAUSE = 0.5 секунды, чтобы слишком часто не стучаться в API.

Результат. В процессе выполнения в консоли вы будете видеть, как профили создаются один за другим. Пример вывода:

✅ Profile #1 created → UUID 123e4567-e89b-12d3-a456-426614174000
✅ Profile #2 created → UUID 123e4567-e89b-12d3-a456-42661417400a
...
✅ Profile #10 created → UUID 123e4567-e89b-12d3-a456-426614174009

В конце получите N строк (по числу профилей). Теперь можно открыть Octo Browser — новые профили появятся в списке. Все они уже сконфигурированы: привязаны к своим прокси, имеют свежесгенерированный отпечаток под нужную ОС, и содержат куки (если вы их задавали). 

Скрипт выше написан на Python, точно такая же логика но под Node.JS сделал у себя, чтобы не перегружать ленту Хабра однотипными статьями, так как логика там точно такая же, меняется лишь язык - скрипт для автоматизации мультиаккаунтинга.

Автоматическое открытие профилей антидетект браузера из терминала

С профилями разобрались, теперь расскажу про второй скрипт. Вот он

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Запускает профили Octo Browser по их UUID, подключается через Selenium
и проверяет IP-адрес каждого профиля.
"""
import os
import sys
import argparse
import requests
from dotenv import load_dotenv
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# --- КОНФИГУРАЦИЯ ---
load_dotenv()
PORT = os.getenv("OCTO_LOCAL_PORT", "58888")
API_TOKEN = os.getenv("OCTO_API_TOKEN")
BASE_URL = f"http://127.0.0.1:{PORT}"
HEADERS = {"X-Octo-Api-Token": API_TOKEN, "Content-Type": "application/json"}
REQ_TIMEOUT = 15  # Таймаут для API запросов в секундах

def start_profile(uid: str) -> int:
    """Запускает профиль через Local API и возвращает debug-порт."""
    url = f"{BASE_URL}/api/profiles/start"
    payload = {"uuid": uid, "headless": False, "debug_port": True}
    
    print(f"▶️  Профиль {uid}: запуск...")
    resp = requests.post(url, json=payload, headers=HEADERS, timeout=REQ_TIMEOUT)
    
    if resp.status_code == 400:
        print(f"⛔️ [400 Bad Request] Проверьте, что профиль не запущен. Ответ API: {resp.text}")
    resp.raise_for_status() # Вызовет исключение для других HTTP-ошибок
    
    debug_port = resp.json().get("debug_port")
    if not debug_port:
        raise ValueError("API не вернул debug_port")
        
    print(f"✅ Профиль {uid}: запущен на порту {debug_port}")
    return debug_port

def attach_to_profile(port: int) -> webdriver.Chrome:
    """Подключается к запущенному профилю Chrome через Selenium."""
    opts = Options()
    opts.add_experimental_option("debuggerAddress", f"127.0.0.1:{port}")
    # Убедитесь, что chromedriver находится в системном PATH или укажите путь:
    # driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=opts)
    return webdriver.Chrome(options=opts)

def check_ip(driver: webdriver.Chrome) -> str:
    """Переходит на сайт и получает IP-адрес."""
    driver.get("https://httpbin.org/ip")
    # Ожидаем появления элемента <pre> на странице
    pre_element = WebDriverWait(driver, 10).until(
        EC.visibility_of_element_located((By.TAG_NAME, "pre"))
    )
    # Извлекаем IP-адрес из JSON-ответа
    ip_data = pre_element.text
    return ip_data.strip()

def main(uids: list[str]):
    """Главная функция для итерации по списку UUID профилей."""
    for uid in uids:
        driver = None
        try:
            debug_port = start_profile(uid)
            driver = attach_to_profile(debug_port)
            
            ip_address = check_ip(driver)
            print(f"   IP-адрес: {ip_address}\n")
            
        except requests.RequestException as e:
            print(f"⛔️ Ошибка API для профиля {uid}: {e}\n")
        except Exception as e:
            print(f"⛔️ Непредвиденная ошибка для профиля {uid}: {e}\n")
        finally:
            if driver:
                driver.quit()

if __name__ == "__main__":
    if not API_TOKEN:
        sys.exit("⛔️ OCTO_API_TOKEN не задан в .env файле. Пожалуйста, настройте его.")
        
    parser = argparse.ArgumentParser(
        description="Запускает профили Octo Browser и проверяет их IP."
    )
    parser.add_argument(
        'profile_uids', 
        nargs='+', 
        metavar='UUID', 
        help='Один или несколько UUID профилей для запуска'
    )
    args = parser.parse_args()
    
    main(args.profile_uids)

Если совсем коротко, этот скрипт читает из переменных окружения порт и токен API Octo Browser, принимает на вход UUID профилей, для каждого через HTTP‑запрос запускает профиль, подключается к нему через Selenium Remote Debugging и проверяет внешний IP на httpbin.org. Это простейший алгоритм от которого в принципе можно оттолкнуться в автоматическом фарминге. Понятно, что проверка ip я реализовал в рамках академического интереса. 

Теперь по шагам, с конкретными деталями

Загрузка конфигурации

С помощью dotenv.load_dotenv() скрипт читает .env из текущей директории и забирает оттуда OCTO_LOCAL_PORT (порт, на котором слушает API Octo, по умолчанию 58888) и OCTO_API_TOKEN.
Формируется базовый URL API BASE_URL = http://127.0.0.1:<PORT>, а также HTTP‑заголовки HEADERS с ключом X-Octo-Api-Token.

Парсинг аргументов командной строки

Через argparse скрипт принимает один или несколько UUID профилей (параметр profile_uids).  То есть скрипт запускается такой командой:

python check_ip.py uuid1 uuid2 uuid3

То есть вы в стартовой команде прописываете UUIDы, которые нужно запустить.

Функция start_profile(uid: str) -> int формирует POST‑запрос к http://127.0.0.1:<PORT>/api/profiles/start с JSON‑телом:

{ "uuid": "<UID>", "headless": false, "debug_port": true }

Опция debug_port: true просит API вернуть порт удалённой отладки.

При статусе 400 (“Bad Request”) выводит текст ответа API, чтобы было понятно, почему профиль не стартует.

Метод raise_for_status() генерирует исключение для любых других HTTP‑ошибок. Из JSON‑ответа берёт поле debug_port и возвращает его как целое число.

Функция attach_to_profile(port: int) -> webdriver.Chrome создаёт ChromeOptions() и устанавливает опцию debuggerAddress на 127.0.0.1:<port>. Запускает совместимый chromedriver, который автоматически подключается к уже запущенному браузеру Octo по указанному порту отладки. Возвращает объект driver, через который можно управлять страницей.

Где взять нужный chromedriver

Проще всего использовать - это использовать пакет webdriver‑manager.

from webdriver_manager.chrome import ChromeDriverManager
driver_path = ChromeDriverManager().install()  # скачает версию, подходящую под локальный Chrome
svc = webdriver.chrome.service.Service(driver_path)
driver = webdriver.Chrome(service=svc, options=opts)

Либо просто напрямую скачать тут - https://googlechromelabs.github.io/chrome-for-testing/, но важно положить его в Path.

Функция check_ip(driver) -> str открывает в браузере страницу https://httpbin.org/ip. С помощью WebDriverWait ждёт появления тега <pre>, в котором httpbin отдаёт JSON с IP.
Читает текст этого элемента (пример { "origin": "X.X.X.X" }) и возвращает строку, обрезав лишние пробелы.

Главная функция main(uids: list[str])перебирает список переданных UUID. Для каждого UUID: Вызывает start_profile, получает debug_port. Подключается к профилю через attach_to_profile. Получает внешний IP через check_ip и выводит в консоль. В блоке finally гарантированно закрывает браузер, даже если была ошибка.

Точка входа if name == "__main__" -проверяет, что API_TOKEN действительно задан, иначе завершает программу с сообщением. Создает парсер аргументов и вызывает main с переданными в командной строке UUID.

Внутренняя архитектура этого скрипта может служить шаблоном для других проектов. Ее можно легко адаптировать под другие антидетект-браузеры, которые поддерживают API или расширить функционал готового скрипта (сразу присваивать тег новым профилям, сохранять их UUID в базу данных, автоматически запускать профили и выполнять сценарии, вариантов оптимизации куча, главное не переоптимизировать, в попытке все автоматизировать).

Автоматизация на базовой версии Selenium может обнаруживаться некоторыми антифрод системами. Рекомендую использовать решения вроде undetected-chromedriver с патчами закрывающими уязвимости.

Комментарии (0)