Facebook через IPv6 прокси: таргетинг и парсинг рекламного кабинета
Содержание
- Почему IPv6, а не IPv4-пул
- Анатомия запросов Ads Manager
- Привязка сессии к /128
- GraphQL-парсинг: что реально нужно тянуть
- Лимиты и как их обходить без бана
- Кейс: агентство с 40 кабинетами на одном /64
- Кейс: парсинг конкурентов и «тихий» бан
- Кейс: таргетинг-исследование и утечка через /64
- Прокси-слой: что важно в конфиге
- DNS и утечки
- Таргетинг: специфика работы через прокси
- Мониторинг и метрики
- Что в итоге
Рекламный кабинет Facebook — это не просто форма с кнопками. Это десятки XHR-запросов к graph.facebook.com, сотни GraphQL-вызовов к www.facebook.com/api/graphql/, плюс отдельный поток событий в Ads Manager. Всё это привязано к сессии, к IP и к отпечатку браузера. Меняешь IP слишком резко — получаешь checkpoint. Меняешь слишком мягко — получаешь один и тот же лимит на всех аккаунтах.
IPv6 здесь даёт то, чего IPv4 уже не даёт: /64-подсеть — это 18 квинтиллионов адресов в одних руках. Можно раскидать сотни сессий по разным /128 внутри одной /64, и Facebook увидит их как разные адреса, но с одинаковым префиксом. Это одновременно и удобно, и опасно. Разбираем, как с этим работать.
Почему IPv6, а не IPv4-пул
Классический IPv4-прокси-пул на 500 адресов стоит денег и всё равно быстро выжигается. Facebook помнит IP, привязывает к нему поведение, и через пару недель адрес уходит в серую зону. С IPv6 картина другая: провайдер выдаёт /64, и ты сам решаешь, сколько /128 из него использовать. Ротация становится дешёвой.
Вторая причина — ASN. Facebook смотрит не только на IP, но и на принадлежность к автономной системе. Датацентровые ASN (OVH, Hetzner, DigitalOcean) имеют повышенный риск-скор. Residential IPv6 от местного провайдера выглядит куда органичнее. Комбинация «домашний ASN + уникальный /128» — это то, что сложно отличить от реального пользователя.
Третья причина — стоимость. Один /64 у хостера стоит как один IPv4, а адресов в нём — на весь парк аккаунтов. Если у тебя 200 рекламных кабинетов, IPv4-пул обойдётся в разы дороже.
Анатомия запросов Ads Manager
Открываешь adsmanager.facebook.com — браузер делает около 40 запросов до того, как ты увидишь первую таблицу кампаний. Ключевые эндпоинты:
- `POST /api/graphql/` — основной канал, батчи по 10-30 операций
- `GET /ads/manager/accounts/` — список кабинетов
- `POST /ajax/bz` — телеметрия (это важно, её нельзя блокировать)
- `GET /privacy/consent/` — согласия, обязательны для EU
GraphQL-запросы подписаны полем `fb_dtsg` и заголовком `x-fb-lsd`. Оба токена привязаны к сессии и к IP. Если IP сменился между получением `fb_dtsg` и его использованием — запрос отвалится с ошибкой 1357001 или тихим редиректом на checkpoint.
Проверить, что прокси реально отдаёт IPv6 и не подменяет заголовки, можно так:
```bash
curl -6 -x http://[2001:db8:1234::42]:3128 \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-s https://api64.ipify.org?format=json
```
Ответ должен содержать тот самый /128, который ты выдал сессии. Если видишь IPv4 — прокси делает NAT64 или fallback, и вся затея с изоляцией рассыпается. Проверяй каждый адрес перед выдачей аккаунту.
Привязка сессии к /128
Правило простое: одна сессия — один /128 — одна cookie-банка. Не переиспользуй адрес между аккаунтами даже через сутки. Facebook хранит историю IP дольше, чем ты думаешь, и «свежий» аккаунт на адресе, где вчера был забаненный, стартует с минус двадцатью очками репутации.
Внутри /64 адреса генерируй детерминированно, чтобы не терять соответствие. Хэш от ID аккаунта — рабочий вариант:
```python
import hashlib
import ipaddress
def addr_for_account(account_id: str, prefix: str = "2001:db8:1234:5678::/64") -> str:
net = ipaddress.IPv6Network(prefix)
digest = hashlib.sha256(account_id.encode()).digest()
host = int.from_bytes(digest[:8], "big") & ((1 << 64) - 1)
return str(net.network_address + host)
```
Такой подход даёт стабильность: перезапустил прокси — тот же аккаунт получил тот же адрес. И коллизии практически исключены при разумном количестве аккаунтов (для 10 000 аккаунтов вероятность совпадения в /64 — порядка 10⁻¹¹).
Отдельный момент — reverse DNS. У residential-провайдеров PTR-записи для /128 обычно нет, и это нормально. У датацентров PTR часто содержит название хостера — это палево. Проверяй через `dig -x`.
GraphQL-парсинг: что реально нужно тянуть
Скрейпить весь GraphQL-поток бессмысленно — 80% трафика это телеметрия и heartbeat. Нужны конкретные операции. У каждой есть `friendly_name` в теле запроса, по нему и фильтруй.
Типичный список для парсинга кабинета:
- `AdsManagerCampaignsQuery` — список кампаний с бюджетом и статусом
- `AdSetsQuery` — адсеты, таргетинг, расписание
- `AdsQuery` — креативы, превью
- `AdAccountInsightsQuery` — метрики (CTR, CPM, spend)
- `TargetingSearchQuery` — поиск интересов и аудиторий
Перехватывать проще всего через CDP (Chrome DevTools Protocol), а не парсить HAR-файлы. Playwright с перехватом `page.on("response")` даёт живые данные без реконструкции подписей:
```python
import asyncio, json
from playwright.async_api import async_playwright
WANTED = {"AdsManagerCampaignsQuery", "AdAccountInsightsQuery"}
async def run(proxy_url: str, cookies: list[dict]):
async with async_playwright() as p:
browser = await p.chromium.launch(
proxy={"server": proxy_url},
args=["--disable-blink-features=AutomationControlled"],
)
ctx = await browser.new_context()
await ctx.add_cookies(cookies)
page = await ctx.new_page()
captured = []
async def on_response(resp):
if "/api/graphql/" not in resp.url:
return
try:
body = await resp.json()
except Exception:
return
name = body.get("friendly_name") or resp.request.post_data_json.get("friendly_name")
if name in WANTED:
captured.append(body)
page.on("response", on_response)
await page.goto("https://adsmanager.facebook.com/adsmanager/manage/campaigns")
await page.wait_for_timeout(8000)
await browser.close()
return captured
asyncio.run(run("http://[2001:db8:1234::42]:3128", []))
```
Важно: `friendly_name` иногда лежит в теле POST, а не в ответе. Проверяй оба места. И не забудь про `fb_dtsg` — если он протух, ответ придёт пустым с кодом 200.
Лимиты и как их обходить без бана
Facebook режет GraphQL по нескольким осям: количество запросов в минуту с одного IP, количество с одной сессии, количество с одного кабинета. Точные цифры не публикуются, но эмпирика такая: 60-80 GraphQL-запросов в минуту с одного /128 проходят стабильно, выше 150 — начинаются 429 и «Please try again later».
Раскидывать нагрузку по /128 внутри одной /64 — рабочая стратегия, но с оговоркой. Facebook группирует адреса по /64-префиксу при подсчёте некоторых лимитов. То есть 256 разных /128 из одной /64 не дадут тебе 256×60 запросов. Скорее всего получишь потолок в районе 2-3× от одиночного адреса. Проверяй на своих аккаунтах.
Ещё один слой — rate limit на уровне кабинета. Даже с идеального IP нельзя тянуть insights за год по 500 адсетам за минуту. Разбивай на чанки по 50-100 сущностей и добавляй jitter 200-800 мс между батчами.
Кейс: агентство с 40 кабинетами на одном /64
Проблема: агентство вело 40 рекламных кабинетов клиентов через IPv4-пул из 10 адресов. Каждые 2-3 дня — checkpoint на 1-2 аккаунтах. Ручной разбор через SMS и документы съедал по 4-5 часов в неделю.
Причина: Facebook видел 40 сессий на 10 адресах, причём с одинаковыми TLS-отпечатками и одинаковыми заголовками Accept-Language. Классическая корреляция.
Решение: перевели на residential IPv6 /64, каждому кабинету выдали уникальный /128, привязали к аккаунту через хэш. Плюс развели User-Agent по реальным устройствам (не рандомизация, а фиксированные профили: 15 Windows, 15 macOS, 10 Android). Через месяц частота checkpoint упала с ~12 в месяц до 1-2, и то на новых клиентах в первые дни прогрева.
Ключевое: не только IP сменили, но и перестали переиспользовать профили браузера между аккаунтами. Один профиль — один кабинет — один /128.
Кейс: парсинг конкурентов и «тихий» бан
Проблема: сервис мониторинга креативов конкурентов тянул данные через Facebook Ad Library API и через прямой парсинг adsmanager конкурентных страниц. Через 3 недели все аккаунты-краулеры получили soft block: страницы открывались, но GraphQL возвращал пустые `data` без ошибок.
Причина: парсер ходил с одинаковым интервалом ровно 60 секунд, использовал один /128 на все запросы и не открывал `ajax/bz` (телеметрию). Facebook видит паттерн «зашёл, дёрнул GraphQL, ушёл» — это сигнатура бота.
Решение: добавили человекоподобные задержки (гауссово распределение, среднее 4.2 с, σ=1.8 с), начали отправлять `ajax/bz` с реальными событиями (scroll, mousemove через CDP), развели нагрузку по 12 разным /128 из разных /64. Через 8 дней блокировки снялись сами. Пропускная способность упала на 40%, но данные пошли.
Кейс: таргетинг-исследование и утечка через /64
Проблема: команда исследовала таргетинг через `TargetingSearchQuery` — гоняла тысячи запросов на поиск интересов и поведений. Через 4 часа все /128 из одной /64 одновременно получили 429.
Причина: Facebook считает rate limit по /64-префиксу для поисковых запросов. Это явно видно по синхронности блокировок — все адреса отвалились в одну секунду.
Решение: развели таргетинг-исследование по 8 разным /64 (разные провайдеры, разные ASN), добавили кэш на 24 часа для уже найденных interest_id. Нагрузка на Facebook упала в 6 раз, блокировки прекратились. Кэш интересов — вообще обязательная штука, потому что `TargetingSearchQuery` дорогой и стабильный: interest_id не меняются годами.
Прокси-слой: что важно в конфиге
Не всякий IPv6-прокси годится. Нужен прозрачный, без инъекций заголовков, с поддержкой CONNECT и без DNS-утечек. SOCKS5 предпочтительнее HTTP: меньше шансов, что прокси подсунет свой `X-Forwarded-For`.
Минимальный конфиг nginx как forward-прокси для IPv6:
```nginx
server {
listen [2001:db8:1234::42]:3128;
resolver [2001:4860:4860::8888] ipv6=on;
proxy_connect;
proxy_connect_allow 443 80;
proxy_connect_address \$host;
proxy_set_header X-Forwarded-For "";
proxy_hide_header X-Real-IP;
}
```
Ключевое — `proxy_set_header X-Forwarded-For ""` и `proxy_hide_header X-Real-IP`. Если прокси пробрасывает реальный IP клиента, вся маскировка летит к чертям. Facebook читает эти заголовки и привязывает сессию к настоящему адресу.
Ещё момент — MTU. IPv6 не фрагментирует пакеты на маршрутизаторах, только на источнике. Если MTU на пути меньше 1280 (минимум для IPv6), соединение просто не установится. Проверяй `ping6 -M do -s 1400` до целевого хоста.
DNS и утечки
Классическая ошибка: браузер сидит на IPv6-прокси, но DNS резолвит через локальный резолвер. Facebook видит запросы с одного IP, а соединения — с другого. Несоответствие → риск-скор растёт.
Решение — либо `socks5h://` (DNS через прокси), либо настройка `network.proxy.socks_remote_dns` в Firefox, либо `--host-resolver-rules` в Chromium. В Playwright проще всего указать прокси с префиксом `socks5h`:
```python
browser = await p.chromium.launch(
proxy={"server": "socks5h://[2001:db8:1234::42]:1080"}
)
```
Проверка утечки — открыть `https://dnsleaktest.com` внутри сессии и убедиться, что резолвер тоже IPv6 и принадлежит тому же ASN, что и прокси. Если видишь Google DNS или Cloudflare — это уже подозрительно, потому что реальный пользователь из домашней сети редко сидит на публичном резолвере.
Таргетинг: специфика работы через прокси
Сам по себе таргетинг через прокси ничем не отличается от обычного, кроме одного нюанса — геолокация. Facebook сверяет IP с заявленной страной кабинета. Если кабинет зарегистрирован на Германию, а /128 принадлежит российскому провайдеру — получишь ограничение на показы.
Проверяй гео каждого /128 перед выдачей: `curl -6 https://ipapi.co/2001:db8::/json` или аналоги. И держи соответствие «страна кабинета = страна /128». Для мультигео-агентств это означает пулы по странам, а не один общий.
Второй нюанс — превью объявлений. Эндпоинт `/ads/api/preview/` чувствителен к IP и часто отдаёт 403 при смене адреса в середине сессии. Тяни превью в начале сессии, до всех остальных запросов.
Мониторинг и метрики
Без мониторинга прокси-пул деградирует тихо. Нужны как минимум три метрики: доля ответов 429 по каждому /128, средняя латентность до graph.facebook.com, и количество checkpoint-редиректов на аккаунт в сутки.
Пороговые значения, при которых стоит менять адрес: больше 3% ответов 429 за час, латентность выше 800 мс стабильно, любой checkpoint за последние 24 часа. Для сбора хватит простого логгера на стороне прокси плюс агрегация в Prometheus.
Отдельно считай «возраст» адреса — сколько дней он живёт с одним аккаунтом. Молодые адреса (меньше 3 дней) ведут себя хуже: Facebook ещё не построил профиль доверия. Не переключай аккаунт на новый /128 без причины — это сбрасывает накопленную репутацию.
Что в итоге
IPv6-прокси для Facebook — это не волшебная таблетка от бана, а инструмент изоляции. Работает он только в связке: уникальный /128 на аккаунт, стабильная привязка через хэш, отдельный профиль браузера, реальный ASN, отсутствие утечек DNS и заголовков. Без этой обвязки даже самый чистый residential-адрес спалится за неделю.
Сервисы вроде lexic.ml выдают /64-подсети с 2015 года, и за это время накопилась статистика по разным ASN — какие проходят для Ads Manager, какие нет. Но даже с хорошим пулом главное правило остаётся: одна сессия — один адрес — один профиль, и никаких резких телодвижений. Facebook прощает медленные изменения и не прощает скачков.