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

Задача

Карточка товара на крупном маркетплейсе в 2026 году отдаётся через несколько слоёв защиты, и ни один из них давно не смотрит только на IP. Это первое, что нужно принять, если вы проектируете парсер или, наоборот, ловите чужие парсеры. Я работаю на стороне инфраструктуры мобильных прокси, через которую такие парсеры ходят, и у меня нет данных о том, сколько капч выдал конкретный маркетплейс конкретному клиенту. Зато есть логи за реальные сутки, из которых видно, как ротация IP устроена на практике. Задача статьи узкая. Взять сутки логов смен IP по всей сети, посмотреть, как люди на самом деле крутят адреса, и сопоставить это с тем, что про такую ротацию способен понять антифрод площадки. Без обещаний «99% успеха», потому что у меня нет способа честно это измерить.

Если вы на стороне защиты, читайте как разведку по чужим привычкам.

Что изменилось в механике защиты

Коротко, базу аудитория знает. Лет пять назад главным сигналом была репутация IP и частота запросов с него. Сейчас репутация адреса у мобильных сетей для площадки почти бесполезна, потому что за одним внешним IP через CGNAT оператора сидят тысячи обычных абонентов, и блок по адресу режет живых покупателей вместе с ботом. Вес сместился в отпечаток клиента (TLS ClientHello, порядок и параметры HTTP/2 фреймов, набор и порядок заголовков), в непрерывность сессии (cookie, локальное хранилище, токены приложения) и в поведение внутри сессии. Для парсера из этого следует неприятная вещь. Смена IP перестала быть «сбросом» состояния. Если адрес меняется каждые две минуты, а cookie и TLS-профиль остаются прежними, с точки зрения антибота это один и тот же клиент, который зачем-то телепортируется между базовыми станциями. Что именно площадка с этим делает, я достоверно не знаю. Доступа к их моделям у меня нет, и любой, кто утверждает обратное, либо работает там, либо фантазирует.

Что продолжает работать, тоже понятно из механики. Трафик, который неотличим от трафика обычного приложения или браузера на смартфоне и идёт через оператора, за которым стоят живые люди.

Вопрос только в том, как не испортить его самой ротацией.

За одним внешним адресом оператора одновременно сидят тысячи абонентов, поэтому площадка платит за блок по IP живыми покупателями.
За одним внешним адресом оператора одновременно сидят тысячи абонентов, поэтому площадка платит за блок по IP живыми покупателями.

Данные и методология

Источник один. Телеметрия событий смены IP в сети mobileproxy.space за сутки 5 октября 2026 года, это понедельник. Каждое событие содержит идентификатор модема, прокси-порт, способ инициации (таймер или ссылка/API), время команды, время появления нового адреса и сам адрес. Публичные размеры сети на 22 сентября того же года: около 39 000 модемов, около 1 250 серверов, 53 страны и 252 города. Суммарные цифры ниже считаны по всей сети, разложение по операторам сделано только для российских. Активных в выборке заметно меньше, чем модемов в сети. За сутки IP меняли 8 878 модемов и 7 202 прокси-порта. Модем и порт у нас не одно и то же, поэтому чисел два, а как именно они соотносились в тот конкретный день, я не раскладывал.

Модем и порт в нашей схеме не связаны один к одному, поэтому цифр две.
Модем и порт в нашей схеме не связаны один к одному, поэтому цифр две.

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

Сколько адресов реально крутится

2 039 364 смены IP за сутки.

Медиана уникальных IP на один модем за те же сутки 95. Типичный модем за день показывает площадкам почти сотню разных адресов, и это медиана, половина модемов выше. Уникальных, не смен. Один и тот же адрес у оператора может вернуться к модему несколько раз за день, долю таких повторов я в этой выборке не считал, и это, пожалуй, главная дыра в разборе.

По операторам картина неровная.

Оператор

Уникальных IP за сутки, шт

Билайн

106 789

МегаФон

87 920

Tele2

58 561

Yota

35 518

МТС

35 100

Числа не нормированы на количество активных модемов, сравнивать операторов между собой по ним нельзя.
Числа не нормированы на количество активных модемов, сравнивать операторов между собой по ним нельзя.

Из таблицы тянет сделать вывод «Билайн лучше всех для парсинга». Не делайте его. Число уникальных адресов зависит от того, сколько модемов на этом операторе было активно, от размера пула у оператора в регионе и от того, как часто порты дёргали. Нормировать на модемы я не стал, потому что состав активных модемов менялся в течение суток и честного знаменателя у меня нет. Единственное, что из таблицы следует без оговорок, это что дневной пул адресов у каждого из пяти операторов исчисляется десятками тысяч, и площадке бессмысленно вести по ним чёрный список. Для стороны защиты это объяснение, почему IP-блэклисты мобильных диапазонов перестали что-либо ловить. Для стороны парсинга объяснение, почему проблема вообще не в количестве адресов.

Таймер против ссылки

Способов сменить адрес на порту два. По таймеру с заданным интервалом или дёрнув ссылку, тот же эндпоинт доступен и как API.

На таймер за сутки пришлось 60,2% всех смен, остальное инициировали скрипты или руки.

Теперь самое интересное. Среди таймерных интервалов 92,2% кратны ровно минуте, а медианный интервал 120 секунд.

Читаю я это так. Большинство людей, настраивая ротацию, открывают параметры порта, пишут «2» в поле минут и больше туда не возвращаются. Интервал никак не связан с тем, что в этот момент делает парсер. Он может оборвать сессию посреди пагинации, может сменить адрес, пока парсер спит, может сменить его три раза за время одной капчи. Антибот при этом видит клиента с неизменным отпечатком, у которого адрес прыгает строго по часам. Живой смартфон так себя не ведёт. Он меняет адрес при переезде между сотами, при пересборке PDP-контекста, при засыпании, и ничто из этого не происходит по cron.

Видит ли площадка в периодичности сигнал? Не знаю. Шаг ровно в 120 секунд сам по себе признак слабый, потому что через CGNAT за тем же адресом в ту же секунду сидят другие люди, и площадке надо сначала выделить ваш поток из общего. Но если поток уже выделен по cookie или TLS, строгая периодичность добавляется к уликам.

Отсюда первый практический тезис. Ротацию стоит привязать к состоянию сессии парсера, а не к часам.

Каркас, который я бы делал в 2026 году для карточек маркетплейса, выглядит примерно так.

import time
import requests

PROXY = 'http://user:pass@proxy-host:HTTP_PORT'
CHANGE_URL = 'https://.../change-ip?token=...'   # ссылка смены IP именно для этого порта
WHOAMI = 'https://api.ipify.org'
PROFILE_HEADERS = {}   # заполните под свой клиент

def current_ip(session):
    return session.get(WHOAMI, timeout=10).text.strip()

def rotate(session, old_ip, wait=8.0):
    # одна команда на одну смену, без ретраев в лоб
    requests.get(CHANGE_URL, timeout=15)
    deadline = time.monotonic() + wait
    while time.monotonic() < deadline:
        time.sleep(1.0)
        try:
            new_ip = current_ip(session)
        except requests.RequestException:
            continue
        if new_ip != old_ip:
            return new_ip
    return None   # не сменился за wait секунд, решает вызывающий

def new_session():
    s = requests.Session()
    s.proxies = {'http': PROXY, 'https': PROXY}
    s.headers.update(PROFILE_HEADERS)
    return s

def crawl(urls, per_ip):
    s = new_session()
    ip = current_ip(s)
    used = 0
    for u in urls:
        r = s.get(u, timeout=20)
        blocked = r.status_code in (403, 429) or 'captcha' in r.text.lower()
        used += 1
        if blocked or used >= per_ip:
            s.close()
            s = new_session()   # новая cookie вместе с новым адресом
            ip = rotate(s, ip) or current_ip(s)
            used = 0
        yield u, r.status_code

Значение per_ip я намеренно не подставил. Сколько запросов с одного адреса переживает конкретная площадка в конкретный день, у меня не измерено, и любое число здесь было бы выдумкой. Подбирается оно у вас по доле блоков, которую ваш же парсер и считает. Два места в коде опираются на телеметрию. Первое, почему rotate() ждёт и опрашивает адрес вместо того, чтобы сразу продолжать. Второе, почему там нет ретрая команды смены.

Сколько длится смена и почему нельзя стрелять дважды

Средняя длительность смены от команды до появления нового адреса 4,67 секунды. В 5 секунд укладываются 73,1% смен. Остальные живут в хвосте, длина которого зависит от оператора и от того, как быстро модем поднимет новую сессию. Этот хвост в сегодняшнем срезе я отдельно не раскладывал.

Вывод для парсера простой. Если после дёрганья ссылки сразу слать запрос к площадке, в заметной доле случаев (всё, что не уложилось в несколько секунд) он уйдёт ещё со старого адреса, а следующий уже с нового, и вы сами склеите для антибота два адреса в одну сессию. Отсюда опрос адреса в цикле и пересоздание сессии только после подтверждения смены.

Теперь про дубли. Среди смен по ссылке 0,8% записей идут парами: тот же порт, интервал между командами не больше 2 секунд, в итоге тот же IP. Оператор тут ни при чём, это клиентский код. Кто-то поставил ретрай на любой ответ эндпоинта смены, кроме 200. Кто-то вызвал смену из двух потоков. Кто-то повесил её и на таймер, и на обработчик капчи одновременно. По клиентам я не разбирал и ни одну из трёх гипотез доказать не могу.

Самоирония момента в том, что первая версия функции rotate(), которую я написал для этой статьи, делала ровно это. Ретрай при таймауте запроса к ссылке. Поймал себя на том же, за что ругаю, и переписал на ожидание с опросом. Вторая команда, прилетевшая через секунду после первой, в лучшем случае ничего не меняет, и данные это показывают, тот же IP на выходе. Что происходит в худшем случае, зависит от модема и оператора. Утверждать здесь что-либо без отдельного замера я не стану.

Отступление про порты

Деталь, которая на главный тезис не работает, но экономит час отладки. У каждого порта есть HTTP-вход и SOCKS5-вход, и номер SOCKS5-порта всегда равен номеру HTTP-порта плюс один. Почему исторически выбрали плюс один, а не отдельную таблицу соответствий, я не знаю, это решение старше моего прихода в команду. Следствие практическое. Playwright, которому удобнее SOCKS5, и requests, который ходит по HTTP, можно повесить на один и тот же модем без второго порта, и внешний адрес у них будет общий. Проверить внешний адрес можно запросом к одному сервису определения IP через каждый из двух входов. Между запросами не запускайте смену IP.

Что видит другая сторона

Переобуюсь в антифрод. Что остаётся у площадки, если по IP мобильный трафик от живого не отличить?

• Согласованность отпечатка. User-Agent заявляет мобильный Chrome, а ClientHello и порядок заголовков от python-requests, и это ловится без всякого IP и без поведенческого анализа.

• Непрерывность сессии поверх прыжков адреса. Одна cookie, один токен, один набор заголовков, а IP за это время сменился несколько раз, у живого телефона такое бывает, но редко и без расписания.

• Регулярность во времени. Запросы и смены адреса с одинаковыми интервалами, и чем ближе интервал к кратности минуте, тем подозрительнее.

• Поведение внутри страницы. Карточка запрошена, а ни один ресурс со страницы, ни один скрипт, ни одно событие рендера не пришли.

Ровно по этим пунктам ротация «раз в 120 секунд, сессия та же» и палится, и ровно поэтому код выше пересоздаёт сессию вместе с адресом. Невидимым трафик это не делает, поведенческие признаки ни один прокси не чинит, а какой из признаков в 2026 году весит больше, я не знаю и подозреваю, что у разных площадок по-разному.

Если вы на стороне защиты и можете сопоставлять сессии с частотой смены адреса, медиана 120 секунд и 92,2% кратных минуте интервалов это готовый фильтр грубой силы. Что он даст по ложным срабатываниям, вам виднее.

Чего нет в этом разборе

Список честный и длинный.

• Один день, понедельник. Суточную и недельную сезонность я не смотрел и не исключаю, что в выходные доля таймерных смен другая.

• Нет ответов площадок. Измерить долю капч на своей стороне нельзя без разбора клиентского трафика, а собственный стенд дал бы цифры только про мой стенд.

• Повторы IP у одного модема в пределах суток не считаны. Медиана 95 уникальных адресов это нижняя оценка разнообразия, не число смен.

• Доля смен, закончившихся ошибкой или тем же адресом, не считана. Среднее 4,67 с по определению метрики включает только события, где новый адрес появился.

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

• Дубли выделены эвристикой (интервал не больше 2 с, тот же IP). Она может захватывать легитимные повторы и пропускать ретраи с большей паузой.

Что из этого делать

Адресов у операторов много, и это давно не узкое место. Узкое место в том, как парсер обращается с сессией вокруг смены адреса.

1. Привяжите смену IP к событиям парсера, будь то исчерпанный лимит запросов на адрес, полученный блок или конец логической порции работы. Таймер оставьте только как страховку.

2. После команды дождитесь подтверждения. Средние 4,67 с и 73,1% в пятисекундном окне означают, что паузу придётся закладывать, иначе часть запросов уйдёт со старого адреса.

3. Одна команда на одну смену. Без ретраев в лоб и без параллельных вызовов из таймера и обработчика ошибок.

4. Вместе с адресом пересоздавайте сессию, то есть cookie, заголовки и при необходимости TLS-профиль. Иначе смена адреса работает против вас.

Осталось непонятным одно. Если 92,2% таймерных интервалов кратны минуте, может ли антибот по одной строгой периодичности выделить поток ещё до того, как связал его по cookie, или без привязки к сессии это тонет в шуме CGNAT?

Стенда, чтобы это проверить, у меня нет, а у тех, кто читает это с другой стороны, он есть. Напишите, если проверяли.

Телеметрия взята из сети mobileproxy.space (https://mobileproxy.space/).