YouTube через IPv6 прокси: парсинг комментариев и аналитика каналов
Содержание
- Почему YouTube и IPv6 — это отдельная история
- Что вообще отдаёт YouTube без API-ключа
- Как Google детектит парсер по IPv6
- Схема пула: сколько адресов реально нужно
- Практика: запрос комментариев через прокси
- Ротация: sticky vs per-request
- Кейс: парсинг 40 000 комментариев с канала-миллионника
- Аналитика каналов: что тянем и откуда
- Ошибки, которые ломают весь пайплайн
- Когда прокси недостаточно
- Итог по цифрам
Почему YouTube и IPv6 — это отдельная история
YouTube отдаёт контент по IPv6 с 2012 года. Причём делает это агрессивно: если у клиента есть AAAA-запись и рабочий маршрут, трафик почти наверняка пойдёт по v6. Для парсинга это одновременно подарок и головная боль. Подарок — потому что IPv6-адресов у провайдера десятки миллионов, блокировки по IP размазываются тонким слоем. Головная боль — потому что Google видит v6-подсети крупными блоками и банит не отдельный адрес, а /64 или даже /48.
Разбираем, как выстроить сбор комментариев и статистики каналов через IPv6-прокси так, чтобы не ловить 429 на каждом втором запросе. С примерами кода, цифрами и граблями, на которые наступают все.
Что вообще отдаёт YouTube без API-ключа
Официальный Data API v3 даёт 10 000 units в день бесплатно. Один вызов `commentThreads.list` стоит 1 unit, `search.list` — 100 units. Хочешь собрать 5000 комментариев под роликом — это 50 запросов по 100 комментариев, то есть 50 units. Вроде немного. Но как только начинаешь мониторить сотни каналов, квота кончается за пару часов.
Отсюда два пути. Первый — платить Google за дополнительные квоты (дорого, \$0.005 за unit сверху, и не всегда дают). Второй — парсить внутренние эндпоинты `youtubei/v1/next`, которые отдают те же данные без ключа, но требуют валидный `context` и `visitorData`. Второй путь и есть вотчина прокси-пулов.
Внутренний API отвечает JSON с структурой `continuationCommand.token`. Токен живёт примерно 30-60 минут, потом протухает. Каждый следующий запрос за комментариями использует токен из предыдущего ответа. Это значит, что сессию нельзя размазывать по разным IP — токен привязан к visitor-сессии, и с чужого адреса Google отдаст 403.
Как Google детектит парсер по IPv6
Три главных маркера. Первый — плотность запросов с одной /64-подсети. Если с блока `2a01:4f8:1c1c:xxxx::/64` прилетает 200 запросов в минуту на разные видео, это аномалия. Домашний пользователь так не делает.
Второй — TLS-фингерпринт. Стандартный `requests` в Python шлёт JA3-хеш, который Google давно в чёрном списке. Нужен либо `curl_cffi` с имитацией Chrome, либо `httpx` с настройкой TLS.
Третий — отсутствие сопутствующих запросов. Реальный браузер перед загрузкой комментариев дёргает `/youtubei/v1/player`, `/youtubei/v1/browse`, тянет превью, статику с `ytimg.com`. Парсер, который ходит только в `next`, палится на пустом профиле.
Пример: сервер на Hetzner с /64-блоком, 50 параллельных потоков через `aiohttp`, дефолтный User-Agent. Через 8 минут — 429 на всех потоках. Смена UA на свежий Chrome не помогла: JA3 остался прежним. После перехода на `curl_cffi` с `impersonate="chrome120"` и раскидывания по 5 адресам из /64 — стабильно 40 минут без бана.
Схема пула: сколько адресов реально нужно
Один IPv6-адрес держит примерно 60-80 запросов к `youtubei` в час без риска. Дальше растёт вероятность 429. Если задача — собирать 10 000 комментариев в сутки с 200 видео, это около 100 запросов `next` плюс 200 `player`. Итого 300 запросов. Хватит 5-6 адресов, если размазать по времени.
Другое дело — аналитика каналов: парсинг `/videos`, `/about`, `/community` для сотни каналов раз в час. Это 300 запросов в час минимум. Нужен пул из 20-30 адресов с ротацией.
Таблица реальных лимитов, снятых с тестов на одном /64:
| Тип запроса | Запросов/час с 1 IP | Первый 429 |
|---|---|---|
| `player` | 120 | ~140 |
| `next` (комменты) | 80 | ~95 |
| `browse` (канал) | 100 | ~115 |
| `search` | 40 | ~50 |
Цифры плавают от региона и времени суток. Ночью по UTC лимиты мягче примерно на 15%.
Практика: запрос комментариев через прокси
Рабочий пример на Python с `curl_cffi` и IPv6-прокси. Прокси формата `http://[2a01:4f8:xxxx::1]:3128`.
```python
from curl_cffi import requests as cffi_requests
import json, time
PROXY = "http://[2a01:4f8:1c1c:abcd::1]:3128"
VIDEO_ID = "dQw4w9WgXcQ"
def fetch_comments(video_id, continuation=None):
url = "https://www.youtube.com/youtubei/v1/next?prettyPrint=false"
payload = {
"context": {
"client": {
"clientName": "WEB",
"clientVersion": "2.20240101.00.00",
"hl": "en",
"gl": "US"
}
},
"videoId": video_id
}
if continuation:
payload["continuation"] = continuation
else:
payload["continuation"] = None
r = cffi_requests.post(
url,
json=payload,
proxies={"https": PROXY, "http": PROXY},
impersonate="chrome120",
timeout=20
)
if r.status_code == 429:
raise RuntimeError("rate limited")
return r.json()
def extract_continuation(data):
try:
actions = data["onResponseReceivedEndpoints"]
for a in actions:
if "appendContinuationItemsAction" in a:
items = a["appendContinuationItemsAction"]["continuationItems"]
for item in items:
if "continuationItemRenderer" in item:
return (item["continuationItemRenderer"]
["continuationEndpoint"]["continuationCommand"]["token"])
except (KeyError, TypeError):
pass
return None
```
Логика простая: первый вызов без токена возвращает первую порцию и токен для следующей. Дальше крутишь цикл, пока токен не кончится. Каждые 10 запросов — смена прокси из пула. Важно: токен привязан к IP, с которого пришёл. Сменил прокси — начни сессию заново с `videoId`, не с токена.
Ротация: sticky vs per-request
Два режима. Sticky — один IP держится на всю сессию парсинга одного видео. Нужен, когда листаешь комментарии по токену. Per-request — новый IP на каждый запрос. Годится для независимых вызовов `player` или `browse`.
На практике удобно держать два пула. Sticky-пул на 10-15 адресов для глубокого листания комментов. Round-robin пул на 50+ адресов для параллельного сбора метаданных по списку видео.
Если сервис прокси даёт /64-блок целиком, ротация внутри блока бесплатна. Меняешь последние 64 бита — получаешь новый адрес. Но Google видит /64 как одну подсеть и суммирует репутацию. Так что реальная ротация — это разные /64, а не разные адреса внутри одного /64.
Кейс: парсинг 40 000 комментариев с канала-миллионника
Задача — собрать все комментарии под 30 последними видео крупного канала. Суммарно около 40 000 штук. Первая попытка: один IPv6-адрес, `aiohttp`, 20 потоков. Через 12 минут — сплошные 429, собрано 3 200 комментариев.
Причина: 20 потоков с одного адреса = 20 запросов `next` в секунду в пике. Google обрабатывает это как атаку. Плюс `aiohttp` шлёт JA3, несовпадающий с заявленным Chrome UA — расхождение ловится на TLS-уровне.
Решение: пул из 12 разных /64-подсетей, sticky-сессии по 8 запросов на адрес, `curl_cffi` с `impersonate="chrome120"`, задержка 1.5-2 секунды между запросами внутри сессии. Собрали 38 700 комментариев за 4 часа 20 минут. Потерь около 3% — на видео с отключёнными комментариями и удалёнными ветками. 429 встретился 14 раз, каждый раз лечился сменой подсети и рестартом сессии с начала видео.
Аналитика каналов: что тянем и откуда
Метаданные канала лежат в `browse` с `browseId=UCxxxx`. Оттуда достаём: количество подписчиков, описание, ссылки, список вкладок. Список видео — через `browse` с параметром `params`, закодированным base64. Вкладка Videos кодируется как `EgZ2aWRlb3PyBgQKAjoA`.
```bash
curl -x "http://[2a01:4f8:1c1c:abcd::5]:3128" \
-X POST "https://www.youtube.com/youtubei/v1/browse?prettyPrint=false" \
-H "Content-Type: application/json" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" \
-d '{
"context": {"client": {"clientName": "WEB", "clientVersion": "2.20240101.00.00"}},
"browseId": "UC_x5XG1OV2P6uZZ5FSM9Ttw",
"params": "EgZ2aWRlb3PyBgQKAjoA"
}' -o channel_videos.json
```
Ответ — JSON с `richItemRenderer` для каждого видео: `videoId`, `title`, `viewCountText`, `publishedTimeText`. Пагинация — через `continuationCommand.token`, тот же механизм, что с комментариями.
Тонкость: `viewCountText` приходит строкой вида `"1.2M views"`. Парсить надо с учётом локали — при `hl=ru` будет `"1,2 млн просмотров"`. Фиксируй `hl=en` в context, иначе словишь зоопарк форматов.
Ошибки, которые ломают весь пайплайн
Самая частая — игнорирование `visitorData`. Google выдаёт его в первом ответе на `player` или `browse` в поле `responseContext.visitorData`. Если не передавать его в следующих запросах, сессия считается невалидной и токены протухают быстрее. Всегда сохраняй visitorData и суй в `context.client.visitorData`.
Вторая — параллельные запросы с одного IP на разные видео. Даже с валидным visitorData это выглядит как бот. Один адрес — одно видео за раз.
Третья — использование HTTP/1.1. YouTube по v6 почти всегда отдаёт HTTP/2. Клиент на HTTP/1.1 с тем же JA3 палится моментально. `curl_cffi` и `httpx` умеют HTTP/2, `requests` — нет.
Когда прокси недостаточно
Если задача — собирать данные с тысяч каналов ежедневно, чистого IPv6-пула мало. Нужны: ротация User-Agent по реальным версиям Chrome (раз в 2-3 недели Google выкатывает новую), эмуляция полного профиля (cookies `CONSENT`, `VISITOR_INFO1_LIVE`), рандомизация таймингов. Прокси-слой решает только проблему IP-репутации, остальное — на клиенте.
Для средних задач (до 100 000 запросов в сутки) хватает пула на 30-40 /64-подсетей и дисциплины в клиенте. Провайдер вроде lexic.ml отдаёт /64-блоки с возможностью ротации внутри, что удобно для sticky-сессий — не нужно дёргать API за новым адресом на каждый запрос.
Итог по цифрам
Средний пайплайн на 50 000 комментариев в сутки: 15-20 /64-подсетей, sticky по 6-10 запросов, `curl_cffi`, задержка 1.5-2 с. Укладывается в 3-4 часа при 10 параллельных воркерах. Без прокси с одного адреса — 3000 комментариев и бан через 10 минут. Разница в 15 раз по объёму и в 40 раз по времени до первого 429.
Главное — не гнаться за скоростью. YouTube банит не за объём, а за ритм. Ровный поток с паузами живёт часами. Рваный пик в 50 rps убивает сессию за минуту.