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

Wikipedia через IPv6 прокси: массовый парсинг и обход ограничений

Wikipedia через IPv6 прокси: массовый парсинг и обход ограничений

Почему Wikipedia вообще упирается в лимиты

Wikipedia отдаёт контент через MediaWiki API и обычные страницы. Для анонимных запросов действует rate limit: примерно 200 запросов в минуту с одного IP на API, и мягче — на HTML-страницы. Превысил — получаешь HTTP 429 с заголовком `Retry-After`. Дальше хуже: если долбить настойчиво, IP попадает в бан на несколько часов, а иногда и на сутки.

Проблема в том, что за одним IPv4-адресом часто сидят сотни людей через NAT. Один парсер-энтузиаст роняет скорость всем. Wikipedia это знает и режет по IP жёстко. IPv6 тут интереснее: адресное пространство огромное, и каждый запрос можно гнать со своего адреса. Но и здесь есть нюансы — Wikimedia банит целые /64-подсети, если видит паттерн.

Разберём, как строить парсер, который не падает на 429 и не превращается в DDOS-инструмент.

Анатомия лимитов Wikimedia

У Wikimedia Foundation есть публичная документация по API etiquette. Ключевые цифры: 200 запросов в минуту для анонимов, 5000 в час для зарегистрированных ботов с нормальным User-Agent. Заголовок `User-Agent` обязателен — без него MediaWiki отдаёт 403 с текстом про policy. Это не шутка, реально блокируют.

Вот что возвращает сервер при превышении:

```

HTTP/2 429

retry-after: 60

x-ratelimit-limit: 200

x-ratelimit-remaining: 0

x-ratelimit-reset: 1718030400

```

Заголовки `x-ratelimit-*` появились относительно недавно, но они есть. Игнорировать `retry-after` — прямой путь к бану. Сервер запоминает, что ты долбил после 429.

Есть ещё отдельный лимит на тяжёлые запросы: `action=query&list=search` с большими `srlimit`, рендеринг PDF, доступ к истории правок. Они считаются дороже. Если парсишь категории с тысячами страниц — готовься к тому, что каждый десятый запрос будет 429 даже при соблюдении общего лимита.

Как IPv6 меняет расклад

IPv6-прокси даёт тебе пул адресов. Не один, а тысячи. Каждый запрос — с нового адреса. С точки зрения MediaWiki это выглядит как трафик от разных пользователей. Rate limit считается по IP, значит, распределяя запросы по /64-подсетям, ты обходишь порог в 200/мин.

Но Wikimedia не наивна. У них есть механизм проверки: если с одной /64-подсети приходит аномально много запросов, они могут забанить всю подсеть. Это называется "range block". Для /64 это 18 квинтиллионов адресов — бан одного /64 не так страшен, если у тебя пул из сотен подсетей.

Практика показывает: ротация по /64 с интервалом 1-2 запроса на адрес работает стабильно. Ротация по каждому запросу внутри одной /64 — быстрее ловишь range block. Провайдеры вроде lexic.ml выдают пул /64-подсетей именно с расчётом на такое использование — это техническое требование, а не маркетинг.

Реализация ротации через прокси

Простейший вариант — curl с прокси и рандомизацией адреса. Пул прокси-серверов с IPv6, каждый запрос идёт через случайный:

```bash

!/bin/bash

PROXIES=(

"http://user:pass@[2a01:4f8:1c1c:1::1]:3128"

"http://user:pass@[2a01:4f8:1c1c:2::1]:3128"

"http://user:pass@[2a01:4f8:1c1c:3::1]:3128"

)

URL="https://en.wikipedia.org/w/api.php?action=query&list=random&rnlimit=10&format=json"

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

PROXY=\${PROXIES[\$((RANDOM % \${#PROXIES[@]}))]}

curl -s --proxy "\$PROXY" \

-H "User-Agent: WikiParser/1.0 (contact@example.com)" \

"\$URL" -o "out_\$i.json"

sleep 0.3

done

```

Работает, но грубо. Нет контроля над лимитами, нет обработки 429, нет ротации по подсетям. Для серьёзного парсинга нужен Python с пулом сессий и счётчиками.

Python-клиент с пулом прокси и backoff

Ниже — рабочий каркас. Он держит пул прокси, считает запросы на каждый адрес, уважает `retry-after` и ротирует при приближении к лимиту.

```python

import time

import random

import requests

from urllib.parse import urlparse

from collections import defaultdict

PROXIES = [

"http://user:pass@[2a01:4f8:1c1c:1::1]:3128",

"http://user:pass@[2a01:4f8:1c1c:2::1]:3128",

"http://user:pass@[2a01:4f8:1c1c:3::1]:3128",

"http://user:pass@[2a01:4f8:1c1c:4::1]:3128",

]

API = "https://en.wikipedia.org/w/api.php"

HEADERS = {"User-Agent": "WikiParser/1.0 (contact@example.com)"}

LIMIT_PER_PROXY = 150

WINDOW = 60

usage = defaultdict(list)

blocked_until = {}

def pick_proxy():

now = time.time()

candidates = []

for p in PROXIES:

if blocked_until.get(p, 0) > now:

continue

recent = [t for t in usage[p] if now - t < WINDOW]

usage[p] = recent

if len(recent) < LIMIT_PER_PROXY:

candidates.append(p)

if not candidates:

wait = min(blocked_until.values()) - now if blocked_until else 1

time.sleep(max(wait, 1))

return pick_proxy()

return random.choice(candidates)

def fetch(params, retries=3):

for attempt in range(retries):

proxy = pick_proxy()

try:

r = requests.get(

API, params=params, headers=HEADERS,

proxies={"http": proxy, "https": proxy},

timeout=15,

)

usage[proxy].append(time.time())

if r.status_code == 429:

retry_after = int(r.headers.get("retry-after", "60"))

blocked_until[proxy] = time.time() + retry_after

continue

r.raise_for_status()

return r.json()

except requests.RequestException:

blocked_until[proxy] = time.time() + 30

raise RuntimeError("all retries exhausted")

def iter_category(category, limit=500):

cont = {}

fetched = 0

while fetched < limit:

params = {

"action": "query", "list": "categorymembers",

"cmtitle": f"Category:{category}", "cmlimit": "50",

"format": "json", **cont,

}

data = fetch(params)

for m in data["query"]["categorymembers"]:

yield m["title"]

fetched += 1

if fetched >= limit:

return

if "continue" not in data:

return

cont = data["continue"]

```

Что здесь важно. Счётчик `usage` считает запросы за окно в 60 секунд по каждому прокси отдельно. `blocked_until` — это честный backoff по `retry-after`. Если все прокси забанены — ждём. Никакого героизма, никакого «продолжим долбить».

Порог `LIMIT_PER_PROXY = 150` — не 200. Специально оставляем запас: MediaWiki считает запросы не идеально ровно, и 150 даёт буфер на всплески. По опыту, на 200 стабильно ловишь 429 раз в 5-10 минут.

Парсинг HTML-страниц и заголовок Accept-Encoding

API — это полдела. Часто нужны сами страницы: `/wiki/Python_(programming_language)`. Здесь лимит мягче, но HTML-ответы весят больше. Если тянешь 10 МБ страниц в минуту — упрёшься в полосу, а не в rate limit.

Полезно просить сжатие. `Accept-Encoding: gzip, br` и парсинг через `brotli` дают экономию в 4-5 раз на типичной статье. Wikipedia отдаёт brotli, если попросить.

```python

import brotli

r = requests.get(

"https://en.wikipedia.org/wiki/Python_(programming_language)",

headers={**HEADERS, "Accept-Encoding": "gzip, br"},

proxies={"https": pick_proxy()}, timeout=20,

)

html = brotli.decompress(r.content).decode("utf-8")

```

Дальше — `BeautifulSoup` или `lxml`. Совет: не парси визуальную часть, если нужны данные. У Wikipedia есть `action=parse&prop=wikitext` — отдаёт сырой викитекст, разбирать его проще и надёжнее, чем DOM с классами вида `mw-parser-output`.

Кейс: обход бана /64-подсети

Пример: парсер на 8 прокси из одной /64-подсети. Через 40 минут работы — все 8 получают 429 одновременно, `retry-after: 3600`. Причина: Wikimedia зафиксировала 200+ запросов/мин с диапазона и наложила range block на /64.

Решение: пул пересобран из 8 разных /64 (по одному адресу на подсеть). Каждый прокси теперь — отдельная подсеть. Ротация между ними. Лимит снижен до 120 запросов/мин на прокси. После этого — 6 часов непрерывного парсинга без единого 429. Скорость: около 950 страниц в минуту суммарно.

Технические детали: запросы шли с `User-Agent: WikiParser/1.0 (contact@example.com)`, что соответствует policy. Без нормального UA бан пришёл бы раньше — на этапе 403.

Кейс: 429 на search API при большом srlimit

Пример: запрос `action=query&list=search&srsearch=...&srlimit=500`. Сервер отвечает 429 даже при 50 запросах/мин с прокси. Причина: search — дорогая операция, у неё отдельный лимит, примерно 50 запросов/мин на IP независимо от общего.

Решение: `srlimit` снижен до 50, пагинация через `sroffset`. Каждый запрос — с нового прокси, но с интервалом не меньше 1.2 секунды на адрес. Плюс кэш результатов в SQLite по хэшу `(query, offset)` — повторные запросы не идут в сеть. Итог: 50 страниц результатов за 6 запросов вместо одного, зато без бана.

Кейс: медленные ответы через IPv6-прокси

Пример: среднее время ответа через IPv6-прокси — 800 мс, через прямое IPv4-подключение — 180 мс. Причина: прокси-сервер в Европе, а клиент в Азии, плюс MTU 1280 на туннеле вызывает фрагментацию.

Диагностика: `ping6 -M do -s 1400` показал, что пакеты больше 1280 байт теряются. Классическая проблема PMTUD на IPv6-туннелях. Решение: на прокси-сервере выставлен `MTU 1420` на интерфейсе, `tcp_mss_clamp` в nftables:

```

nft add rule inet filter forward tcp flags syn tcp option maxseg size set rt mtu

```

После этого время ответа упало до 240 мс. Разница с прямым подключением осталась, но стала приемлемой. Для парсинга 1000 страниц это экономит около 9 минут.

Что НЕ надо делать

Не запускай парсер без `User-Agent`. MediaWiki отдаст 403 с текстом про policy, и это самый мягкий исход. С нормальным UA хотя бы понятно, кто ты.

Не игнорируй `retry-after`. Если сервер сказал 60 секунд — жди 60. Обход через смену прокси в момент 429 работает, но если делать это агрессивно, Wikimedia начнёт банить подсети превентивно.

Не парси `Special:Export` массово. Это тяжёлый endpoint, и его отдельно мониторят. Для больших выгрузок есть официальные dumps — `dumps.wikimedia.org`, они обновляются раз в месяц и качаются по HTTP без лимитов. Если тебе нужен весь корпус — качай дамп, а не долби API.

Не забывай про robots.txt. Wikipedia разрешает парсинг, но с уважением к лимитам. Если твой парсер роняет доступ для других — прилетит жалоба, и Wikimedia может принять меры вплоть до блокировки по ASN.

Итог: сколько реально выжать

С пулом из 10 /64-подсетей, лимитом 150 запросов/мин на прокси и соблюдением `retry-after` стабильно получается 1200-1400 запросов в минуту. Это примерно 20 запросов в секунду. Для 100 000 статей — около 80 минут работы. Без бана, с нормальным UA, с кэшем.

Если нужно быстрее — только дампы. API не резиновый, и уважение к лимитам здесь не вежливость, а техническая необходимость. Один раз перегнул — и следующие сутки парсишь с дампа, потому что все подсети в бане.

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