← Назад в базу знаний

LinkedIn через IPv6 прокси: парсинг профилей и B2B-аналитика

LinkedIn через IPv6 прокси: парсинг профилей и B2B-аналитика

Почему LinkedIn и IPv6 — странная пара

LinkedIn крутит Cloudflare и собственный антибот уже лет десять. Их edge-инфраструктура сидит на dual-stack, но клиентские подсети, которые приходят по IPv6, они давно разметили. Особенно датацентровые диапазоны. Обычный /64 из OVH или Hetzner отлетает в 403 на первом же запросе к `/voyager/api/`. Проверено на 2000 профилей в феврале.

При этом IPv6 — это не панацея. Это инструмент ротации и распределения нагрузки. Если вы парсите B2B-данные в объёме, IPv4-пул упирается в лимиты провайдера за неделю. С IPv6 вы получаете /64 — это 18 квинтиллионов адресов. Даже с учётом того, что LinkedIn режет по /64-префиксу, а не по адресу, у вас остаётся 2^16 подсетей, из которых можно крутить ротацию.

Разберём, как это работает в реальной задаче — сбор профилей, парсинг заголовков должностей, выгрузка в аналитику.

Как LinkedIn детектит прокси на сетевом уровне

Первый слой — TLS-фингерпринт. JA3/JA4. Python `requests` даёт один хеш, `curl` другой, headless Chrome третий. Если вы гоняете запросы через IPv6-прокси с дефолтным `requests`, Cloudflare видит несоответствие: адрес из «домашней» сети, а TLS-стек — библиотечный. Плюс HTTP/2-фрейминг отличается от браузерного.

Второй слой — поведенческий. LinkedIn считает частоту запросов на сессию, порядок эндпоинтов, наличие `csrf-token`, `li_at` cookie. Если вы дёргаете `/voyager/api/identity/profiles/{id}/profileView` без предварительного захода на главную — это красный флаг, даже с чистого IPv6.

Третий слой — репутация префикса. Датацентровые /64 у LinkedIn в чёрных списках. Жилые и мобильные — в белых. IPv6 от residential-провайдера проходит, IPv6 от DigitalOcean — нет.

Отсюда вывод: IPv6-прокси нужен не «чтобы обойти бан», а чтобы распределить нагрузку по подсетям и не выжечь один адрес за час.

Что реально даёт /64 и /48

Провайдер выдаёт вам /64. Внутри — 2^64 адресов. Но LinkedIn (и Cloudflare перед ним) агрегирует статистику по /64-префиксу. Значит, все адреса из одного /64 для них — один клиент. Ротация внутри /64 не помогает.

Нужен /48. Это 65536 подсетей /64. Каждая — отдельный «клиент» с точки зрения rate-limit. Если у вас /48 от провайдера, вы можете держать 65536 параллельных «сессий» без пересечения по префиксу.

На практике: /48 от residential-провайдера стоит денег. /48 от датацентра бесполезен — весь AS в чёрном списке. Компромисс — /64 от residential с ротацией по адресам внутри (работает, если LinkedIn не агрегирует по /64, а иногда не агрегирует — зависит от их текущей политики).

Смотрим реальную картину на тесте: 500 запросов к `/voyager/api/graphql` с одного /64 — 403 после 180-го. С /48 с ротацией по /64 — 500 запросов, 0 блокировок. Разница в 2.7 раза по объёму до первого бана.

Настройка IPv6-прокси в curl

Простейший сценарий — HTTP-прокси поверх IPv6. `curl` поддерживает это из коробки, но есть нюансы с резолвом.

```bash

curl -6 -x "http://[2001:db8:1::1]:3128" \

-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \

-H "Accept: application/vnd.linkedin.normalized+json+2.1" \

--compressed \

-o profile.json \

"https://www.linkedin.com/voyager/api/identity/profiles/john-doe"

```

Флаг `-6` заставляет curl резолвить DNS через AAAA. Без него curl может уйти по IPv4, и вы не заметите, что прокси не работает. Проверяйте через `curl -6 -x ... https://ifconfig.co` — должен вернуться IPv6-адрес прокси, а не ваш.

Для SOCKS5 поверх IPv6:

```bash

curl -x "socks5h://[2001:db8:1::1]:1080" \

--resolve "www.linkedin.com:443:185.199.108.0" \

"https://www.linkedin.com/feed/"

```

`--resolve` тут — костыль для случаев, когда DNS на стороне прокси не резолвит. `socks5h` (с `h`) передаёт резолв на сторону прокси, это правильнее для ротации.

Python: ротация по /64 с проверкой бана

Реальный парсер на 5000 профилей. Ротация по пулу IPv6, детект 403 и 999, бэкофф.

```python

import httpx

import asyncio

import random

from itertools import cycle

PROXIES = [

"http://[2001:db8:1::1]:3128",

"http://[2001:db8:2::1]:3128",

"http://[2001:db8:3::1]:3128",

]

HEADERS = {

"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "

"AppleWebKit/537.36 (KHTML, like Gecko) "

"Chrome/124.0.0.0 Safari/537.36",

"Accept": "application/vnd.linkedin.normalized+json+2.1",

"Accept-Language": "en-US,en;q=0.9",

"x-restli-protocol-version": "2.0.0",

}

async def fetch_profile(client, urn, proxy):

url = f"https://www.linkedin.com/voyager/api/identity/profiles/{urn}"

try:

r = await client.get(url, headers=HEADERS, timeout=15.0)

if r.status_code in (403, 999, 429):

return {"urn": urn, "status": r.status_code, "proxy": proxy}

return {"urn": urn, "status": r.status_code, "data": r.json()}

except httpx.TimeoutException:

return {"urn": urn, "status": "timeout", "proxy": proxy}

async def main(urns):

proxy_pool = cycle(PROXIES)

async with httpx.AsyncClient(http2=True, follow_redirects=True) as client:

for urn in urns:

proxy = next(proxy_pool)

result = await fetch_profile(client, urn, proxy)

if isinstance(result["status"], int) and result["status"] >= 400:

await asyncio.sleep(random.uniform(2.0, 5.0))

else:

await asyncio.sleep(random.uniform(0.5, 1.5))

if __name__ == "__main__":

asyncio.run(main(["john-doe", "jane-smith"]))

```

Ключевые моменты. `http2=True` — обязателен, LinkedIn режет HTTP/1.1 быстрее. `x-restli-protocol-version` — заголовок Voyager API, без него часть эндпоинтов отдаёт 400. Пауза 0.5–1.5 секунды между успешными запросами — это не паранойя, это чтобы не выжечь префикс за 10 минут.

Прокси в `httpx` передаётся через `proxy=` в `AsyncClient` или в конкретный запрос. Для IPv6-адресов скобки в URL обязательны: `http://[2001:db8::1]:3128`.

Парсинг заголовков должностей: что реально достаётся

Voyager API отдаёт JSON с полями `headline`, `geoLocationName`, `industryName`. Из `headline` вытаскиваете должность через regex или ML. Простой regex на 2000 профилей дал точность 68% — остальное «Founder | Building things | ex-Google» и прочий шум.

Для B2B-аналитики важнее не должность, а комбинация: `companyName` + `geoLocationName` + `industryName`. Это три поля, которые Voyager отдаёт стабильно. Агрегация по ним даёт нормальную воронку: сколько людей в финтехе в Берлине работают в компаниях 50–200 человек.

Один нюанс: `geoLocationName` приходит на языке профиля. У немецкого профиля будет «Berlin, Deutschland», у американского — «Berlin, Germany». Нормализуйте через справочник стран и городов, иначе аналитика развалится на дубликатах.

Кейс: 403 после 180 запросов с одного /64

Проблема: парсер на 5000 профилей, пул из одного /64 (256 адресов), ротация по адресам. Через 180 запросов — 403 на всех адресах сразу.

Причина: Cloudflare агрегирует статистику по /64-префиксу. Все 256 адресов для него — один клиент. 180 запросов за 4 минуты — это 45 rpm, выше их порога для «новых» префиксов.

Решение: переход на /48 с ротацией по /64. Плюс снижение темпа до 20 rpm на /64. После этого — 5000 профилей за 6 часов без единого бана. Прокси-пул: 12 /64 из /48, ротация round-robin, пауза 3 секунды между запросами внутри одного /64.

Кейс: TLS-фингерпринт выдал парсер

Проблема: запросы идут с residential IPv6, User-Agent браузерный, но 100% запросов — 403.

Причина: JA3-хеш от `requests` не совпадает с хешем Chrome 124. Cloudflare видит: «браузерный UA + библиотечный TLS + residential IP» — несоответствие.

Решение: переход на `curl_cffi` с `impersonate="chrome124"`. Это библиотека на базе curl-impersonate, которая подделывает TLS-фингерпринт Chrome. После замены — 403 исчезли полностью на том же пуле прокси.

```python

from curl_cffi import requests as cffi_requests

r = cffi_requests.get(

"https://www.linkedin.com/voyager/api/identity/profiles/john-doe",

headers=HEADERS,

proxies={"https": "http://[2001:db8:1::1]:3128"},

impersonate="chrome124",

timeout=15,

)

```

Разница в JA3 между `requests` и `chrome124` — 14 полей в порядке cipher suites и extensions. Cloudflare это ловит без вариантов.

Кейс: DNS-резолв утёк мимо прокси

Проблема: прокси настроен, но `ifconfig.co` возвращает ваш реальный IP.

Причина: `curl` без `-6` резолвит `www.linkedin.com` через A-запись (IPv4) и идёт напрямую, минуя IPv6-прокси. Прокси в конфиге есть, но не используется.

Решение: `-6` для curl, `socks5h://` для SOCKS (h = hostname resolve на стороне прокси), `httpx` с явным `proxy=` и проверка через тестовый запрос к `https://api64.ipify.org?format=json` перед стартом парсера. Если вернулся не прокси-адрес — падаем с ошибкой, не начинаем работу.

Кейс: LinkedIn отдал 999 вместо 403

Проблема: часть запросов возвращает HTTP 999. Парсер его не ловит, потому что проверяет только `>= 400`.

Причина: 999 — нестандартный код LinkedIn, означает «заблокировано, но не 403». Приходит с пустым телом и заголовком `x-li-fabric: prod-lva1`. Встречается на 5–8% запросов при работе через IPv6-прокси с датацентровых префиксов.

Решение: добавить 999 в список блокирующих кодов наравне с 403 и 429. Плюс логировать `x-li-fabric` — по нему видно, какой датацентр LinkedIn обработал запрос, это помогает понять паттерн блокировок.

Прокси-менеджер: как не утонуть в пуле

Когда у вас 12 /64 и 5000 профилей, нужен менеджер пула. Простая логика: каждый /64 — отдельный «слот» с лимитом 20 rpm и счётчиком ошибок. При 3 ошибках подряд слот уходит в карантин на 10 минут. При 5 ошибках — на час.

Таблица реальных порогов, снятых на тестах в марте 2024:

| Тип префикса | Порог rpm до 403 | Время до первого бана |

|---|---|---|

| Датацентр /64 | 15 | 2–4 минуты |

| Residential /64 | 45 | 8–12 минут |

| Residential /48 (ротация по /64) | 20 на /64 | нет бана за 6 часов |

Цифры зависят от текущей политики LinkedIn и времени суток. Ночью по UTC пороги выше — их антибот тоже спит.

Куда смотреть дальше

IPv6-прокси — это инфраструктурный слой. Без правильного TLS-фингерпринта и поведенческой эмуляции он не спасёт. Без ротации по /48 — упрётесь в лимит /64 за час.

Порядок работ: сначала TLS-имперсонация (`curl_cffi`), потом прокси-пул с /48, потом поведенческая эмуляция (паузы, порядок эндпоинтов, cookie-сессия). Каждый слой добавляет 30–50% к выживаемости. Пропустите первый — остальные два не дадут ничего.

Для распределённых задач с большим пулом адресов и ротацией по подсетям есть смысл смотреть в сторону провайдеров с /48 на residential-AS. Если нужен промежуточный вариант — IPv6-прокси с ротацией по /64 внутри /48 работает в большинстве сценариев B2B-парсинга, включая LinkedIn, при условии, что TLS-слой настроен правильно.

✔️Купить прокси