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

Microsoft Azure через IPv6 прокси: парсинг документации и API

Microsoft Azure через IPv6 прокси: парсинг документации и API

Зачем вообще проксировать Azure через IPv6

Azure API имеет rate limits на уровне подписки и IP. Стандартный лимит для ARM (Azure Resource Manager) — 1200 запросов в час на подписку, но при парсинге документации с docs.microsoft.com всё интереснее: там нет жёсткого лимита, зато есть агрессивная защита от ботов на уровне edge-инфраструктуры. Один IP начинает получать 429 после 200-300 запросов в минуту. А если параллелить — бан прилетает ещё быстрее.

IPv6 здесь даёт не только обход лимитов. У Azure есть регионы, где IPv6-маршруты идут заметно короче. Например, из Франкфурта до eastus2 через IPv4 — 11 хопов и 98 мс, через IPv6 — 8 хопов и 74 мс. Разница вроде небольшая, но при парсинге 50 000 страниц она превращается в часы.

Прокси-сервер на IPv6 позволяет держать пул адресов, каждый из которых выглядит как отдельный клиент. Это критично, когда нужно спарсить всю документацию Azure SDK или собрать метрики по регионам.

Как Azure видит IPv6-клиента

Azure Front Door и edge-узлы Microsoft принимают IPv6 с 2016 года. Но есть нюанс: не все сервисы одинаково реагируют на IPv6-клиентов. `management.azure.com` работает через IPv6 нормально, а вот некоторые региональные endpoints (например, `*.azurewebsites.net` в отдельных регионах) до сих пор отдают 403 при обращении по IPv6, если не указан корректный `Host` заголовок.

Проверить доступность конкретного endpoint по IPv6 можно так:

```bash

curl -6 -sS -o /dev/null -w "%{http_code} %{remote_ip} %{time_total}\n" \

https://management.azure.com/subscriptions?api-version=2022-12-01 \

-H "Authorization: Bearer \$TOKEN"

```

Если `remote_ip` начинается с `2600:` или `2a01:` — вы идёте через IPv6. Если с `20.` или `13.` — трафик ушёл по IPv4, и прокси настроен неправильно.

Настройка пула IPv6-прокси для Azure API

Простейший вариант — SOCKS5 прокси с IPv6-выходом. В Python это выглядит так:

```python

import httpx

import asyncio

PROXIES = [

"socks5://[2a01:4f8:1c1c:1::1]:1080",

"socks5://[2a01:4f8:1c1c:1::2]:1080",

"socks5://[2a01:4f8:1c1c:1::3]:1080",

]

async def fetch_doc(client, url, proxy):

try:

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

return r.status_code, len(r.content), proxy

except httpx.ProxyError as e:

return None, 0, proxy

async def main(urls):

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

tasks = []

for i, url in enumerate(urls):

proxy = PROXIES[i % len(PROXIES)]

tasks.append(fetch_doc(client, url, proxy))

results = await asyncio.gather(*tasks)

for status, size, proxy in results:

print(f"{proxy} -> {status} {size}b")

asyncio.run(main(["https://learn.microsoft.com/en-us/rest/api/azure/"] * 30))

```

Ключевой момент — ротация. Если гнать все запросы через один IPv6, Azure edge словит паттерн за 2-3 минуты. Ротация по round-robin работает, но лучше — по хешу от URL, чтобы не долбить один и тот же ресурс с разных адресов (это выглядит подозрительно).

Парсинг документации learn.microsoft.com

Структура сайта — Next.js с SSR. Каждая страница отдаёт JSON в `__NEXT_DATA__`, что упрощает парсинг: не нужен BeautifulSoup, достаточно вытащить скрипт и распарсить.

```python

import json

import re

import httpx

PATTERN = re.compile(r'', re.DOTALL)

def extract_next_data(html: str) -> dict:

match = PATTERN.search(html)

if not match:

return {}

return json.loads(match.group(1))

def parse_learn_page(url: str, proxy: str) -> dict:

with httpx.Client(proxy=proxy, timeout=20.0) as client:

r = client.get(url)

r.raise_for_status()

data = extract_next_data(r.text)

props = data.get("props", {}).get("pageProps", {})

return {

"title": props.get("title"),

"toc": props.get("toc", []),

"content_html": props.get("content", {}).get("html", ""),

"updated": props.get("ms.date"),

}

```

Проблема в том, что learn.microsoft.com отдаёт разный HTML для IPv4 и IPv6. Не принципиально разный, но edge-узлы разные, и иногда `__NEXT_DATA__` приходит усечённым. Лечится retry с другим адресом из пула.

Rate limits: реальные цифры

| Endpoint | Лимит (IPv4) | Лимит (IPv6) | Окно |

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

| management.azure.com | 1200 req/h | 1200 req/h | 1 час |

| learn.microsoft.com | ~300 req/min | ~300 req/min | 1 минута |

| api.applicationinsights.io | 100 req/min | 100 req/min | 1 минута |

| graph.microsoft.com | 10000 req/10min | 10000 req/10min | 10 минут |

Цифры по learn.microsoft.com — эмпирические, Microsoft их не публикует. Лимит одинаковый для IPv4 и IPv6, но IPv6-адресов у вас может быть миллион, а IPv4 — обычно 1-2. Вот где выигрыш.

Кейс: сбор OpenAPI-спек Azure REST

Задача — собрать все OpenAPI-спеки Azure REST API. Это ~1400 файлов на GitHub, но некоторые доступны только через `learn.microsoft.com/en-us/rest/api/`. Прямой парсинг с одного IP упирался в 429 после 250 страниц.

Причина — Azure Front Door считает запросы по IP + User-Agent. Смена UA помогала на 10-15 минут, потом снова бан.

Решение: пул из 64 IPv6-адресов (один /64 префикс, разные суффиксы), SOCKS5 на каждом, round-robin с задержкой 200 мс между запросами. 1400 страниц собрались за 18 минут без единого 429. Через IPv4 с одним адресом это заняло бы 4+ часа с постоянными retry.

Детали: `Accept-Encoding: gzip`, HTTP/2, keep-alive на каждом прокси. Без keep-alive TLS handshake добавляет 80-120 мс на запрос, а это 28 секунд оверхеда на 1400 страниц.

Кейс: мониторинг Azure Service Health по регионам

Service Health API (`management.azure.com/.../events`) отдаёт события по подписке. Нужно было собирать статус 60 регионов каждые 5 минут. Один IP — не проблема по rate limit, но Azure иногда отдаёт stale data, если запросы идут с одного адреса подряд.

Причина — кэш на edge-узле. Azure Front Door кэширует ответы по IP клиента для некоторых endpoint'ов (не документировано, но воспроизводится).

Решение: 60 IPv6-адресов, по одному на регион. Каждый регион опрашивается со своего адреса. Кэш не пересекается, данные свежие. Плюс — можно параллелить без риска получить 429.

```bash

for i in \$(seq 1 60); do

region=\$(az account list-locations --query "[\$((i-1))].name" -o tsv)

addr="2a01:4f8:1c1c:1::\${i}"

curl -6 --interface "\$addr" -sS \

"https://management.azure.com/subscriptions/\$SUB/providers/Microsoft.ResourceHealth/events?api-version=2022-10-01" \

-H "Authorization: Bearer \$TOKEN" \

-o "health_\${region}.json" &

done

wait

```

`--interface` с IPv6-адресом работает, если адреса реально назначены на интерфейс. На VPS с /64 это делается одной командой `ip -6 addr add`.

Кейс: парсинг Azure Updates через RSS

Azure Updates (`azure.microsoft.com/en-us/updates/`) имеет RSS, но отдаёт только 100 последних записей. Для полного архива нужен парсинг пагинации. Проблема — Cloudflare перед сайтом, который банит IPv6-адреса из известных датацентровых диапазонов (Hetzner, OVH, DigitalOcean).

Причина — Cloudflare ведёт список ASN, и если ваш /64 принадлежит хостингу, запросы идут через JS-challenge. С IPv4 та же история, но IPv4-адреса можно купить резидентные, а IPv6 — почти всегда датацентровые.

Решение: residential IPv6 через туннель (например, через домашний роутер с OpenWrt и wireguard). Скорость падает до 5-10 Мбит/с, но challenge не прилетает. Для парсинга текста этого хватает.

Альтернатива — использовать Azure Functions с исходящим IPv6. У Azure есть свои IPv6-диапазоны, Cloudflare их не банит. Но это дороже: ~\$0.20 за миллион выполнений плюс исходящий трафик.

Обработка 429 и retry-стратегия

Экспоненциальный backoff — не всегда лучший вариант. Azure отдаёт заголовок `Retry-After` в секундах, и его надо уважать.

```python

import time

import httpx

def fetch_with_retry(url: str, proxies: list, max_attempts: int = 5) -> httpx.Response:

for attempt in range(max_attempts):

proxy = proxies[attempt % len(proxies)]

try:

r = httpx.get(url, proxy=proxy, timeout=20.0)

if r.status_code == 429:

wait = int(r.headers.get("Retry-After", 2 ** attempt))

time.sleep(min(wait, 60))

continue

if r.status_code in (500, 502, 503):

time.sleep(2 ** attempt)

continue

return r

except (httpx.ProxyError, httpx.ConnectTimeout):

continue

raise RuntimeError(f"failed after {max_attempts} attempts: {url}")

```

Главное — не долбить один и тот же прокси при retry. Если адрес получил 429, он, скорее всего, в бане на несколько минут. Смена прокси на каждой попытке увеличивает шанс успеха в разы.

TLS fingerprinting и HTTP/2

Azure edge использует TLS fingerprinting. Python `httpx` с `http2=True` даёт JA3, близкий к Chrome, но не идентичный. Если парсинг упирается в 403 без внятной причины — стоит попробовать `curl_cffi` с имперсонацией Chrome.

```python

from curl_cffi import requests

r = requests.get(

"https://learn.microsoft.com/en-us/rest/api/azure/",

impersonate="chrome120",

proxies={"https": "socks5://[2a01:4f8:1c1c:1::1]:1080"},

)

```

`curl_cffi` подделывает не только JA3, но и порядок HTTP/2-фреймов, что для Azure Front Door важно. Без этого можно получить 403 на ровном месте, хотя заголовки корректные.

Когда IPv6-прокси не поможет

Если Azure API требует managed identity или сертификат — прокси бесполезен. Токен привязан к конкретному principal, и смена IP ничего не даёт. Также не поможет, если endpoint доступен только из определённой VNet (private endpoint).

Для парсинга публичной документации и публичных API — IPv6-прокси работает. Для доступа к защищённым ресурсам — нет. Это важно понимать до того, как вы построите инфраструктуру на 500 адресов и обнаружите, что нужен service principal.

Ещё один случай — Azure DevOps API. Там rate limit на уровне PAT (personal access token), не IP. Хоть 1000 IPv6-адресов, лимит останется 300 запросов в 5 минут на токен.

Практические ограничения

Провайдеры IPv6-прокси часто дают /64 префикс, но реально маршрутизируют только /112 или /120. Проверить можно так: назначить адрес с суффиксом за пределами маршрутизируемого диапазона и попробовать `ping6`. Если не отвечает — префикс урезан.

Ещё нюанс — reverse DNS. Azure edge иногда проверяет PTR-запись для IPv6-адресов. Без PTR запросы идут, но с большей вероятностью попадают под challenge. Настроить PTR можно не у всех провайдеров, обычно только у крупных (Hetzner, OVH).

И последнее — MTU. IPv6 не фрагментирует пакеты на роутерах, только на отправителе. Если MTU туннеля меньше 1280 байт (минимум для IPv6), соединение просто не поднимется. Для wireguard-туннелей ставьте MTU 1420 или ниже, иначе большие ответы от Azure API будут теряться.

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