# Архитектура крупномасштабного веб-скрейпинга: Полевое руководство

Несколько сотен страниц — это скрипт. Миллионы — это распределенная система. Вот архитектура, которая поможет вам достичь этого — очереди, асинхронные рабочие, уровни прокси, повторные попытки, дедупликация и проверки качества данных, с работающим кодом.

Скрейпинг нескольких сотен страниц — это скрипт. Скрейпинг миллионов — это распределенная система. Как только количество целей переходит от "запускается за ночь на моем ноутбуке" к "нужно завершить на этой неделе, не перегреваясь", сложность перестает быть в парсинге и становится во всем вокруг — как вы ставите задачи в очередь, распределяете их, избегаете блокировок, повторяете неудачные попытки и сохраняете и проверяете результат. Это **крупномасштабный веб-скрейпинг как архитектура**, а не фрагмент кода, с работающим кодом на каждом этапе.

## Масштаб — это параллелизм, а не больший цикл

Одна цифра делает это конкретным. Возьмите категорию с 20,000 страницами объявлений, по 20 элементов каждая — 400,000 страниц для загрузки. При реалистичных 2.5 секундах на страницу строго последовательный запуск займет около 1,000,000 секунд, примерно **11.5 дней** ожидания загрузки страниц до парсинга первого поля. Запустите 200 страниц параллельно, и эти 11.5 дней сокращаются до **часа** реального времени. Время — это ограничение на масштабе, и параллелизм — это способ его вернуть. Все остальное в архитектуре существует, чтобы сделать этот параллелизм жизнеспособным.

![Два столбца для загрузки 400 000 страниц по 2,5 секунды каждая: около 11,5 дней последовательно против примерно часа при 200 страницах параллельно; карточка добавляет, что при миллионе запросов 1% сбоев означает 10 000 мертвых страниц, поэтому повторяйте таймауты и 503 с нарастающей задержкой и никогда не повторяйте окончательный 404.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335206/blog/generated/qp-b9-077-diag1.png)
*Параллелизм превращает 11.5-дневный запуск в час — но при миллионе запросов даже 1% уровень сбоев — это 10,000 неудачных страниц.*

## Архитектура с первого взгляда

Скрейпер, который выдерживает миллионы страниц, — это небольшая распределенная система с несколькими именованными частями, каждая из которых решает проблему, которая возникает только при большом объеме:

- **Очередь** хранит URL-адреса, которые еще предстоит загрузить, и отделяет обнаружение от работы.
- **Асинхронные или распределенные рабочие** извлекают из очереди и загружают параллельно — здесь и находятся экономия времени.
- **Уровень прокси и антиботов** меняет IP и представляет трафик реального браузера, чтобы ни один адрес не превышал лимит.
- **Рендеринг, только когда необходимо**, потому что безголовый браузер — самая дорогая вещь в конвейере.
- **Повторные попытки с увеличением интервала**, **дедупликация**, **хранение** и **мониторинг плюс проверки качества данных** завершают его.

## Сначала очередь: отделите обнаружение от загрузки

Самое важное структурное решение — это поставить очередь между "что скрейпить" и "выполнением скрейпинга". Производитель перечисляет URL-адреса; пул рабочих их обрабатывает. Ни одна из сторон не знает, насколько быстро работает другая, и вы добавляете рабочих, не касаясь производителя. В Python это Celery или RQ на базе Redis; в Node — BullMQ; на большом масштабе — RabbitMQ или Kafka. Шаблон в одном файле:

```python
import asyncio, aiohttp

CONCURRENCY = 50
queue = asyncio.Queue()

async def worker(session):
    while True:
        url = await queue.get()
        try:
            async with session.get(url, timeout=20) as resp:
                await handle(url, await resp.text(), resp.status)
        except Exception as err:
            await on_failure(url, err)
        finally:
            queue.task_done()

async def run(urls):
    for u in urls:
        queue.put_nowait(u)
    async with aiohttp.ClientSession() as session:
        tasks = [asyncio.create_task(worker(session)) for _ in range(CONCURRENCY)]
        await queue.join()
        for t in tasks:
            t.cancel()
```

Ручка, которая имеет значение, — это `CONCURRENCY`. Слишком низкое значение тратит впустую параллелизм, который делает масштаб возможным; слишком высокое перегружает как цель, так и ваш собственный исходящий трафик. Вы находите правильное значение, наблюдая за ростом уровня ошибок — именно поэтому мониторинг является важной частью системы, а не последующей мыслью.

![Конвейер крупномасштабного скрейпинга от очереди до хранилища: очередь URL, асинхронные воркеры, уровень прокси и антибот-защиты, рендеринг только при необходимости, затем повторы, дедупликация, хранение и проверки качества; карточка делит его: модель данных, парсеры и проверки создавать самим, а уровни прокси, антибот-защиты и рендеринга покупать.](https://res.cloudinary.com/dmqliab0t/image/upload/f_auto,q_auto,w_1200/v1785335206/blog/generated/qp-b9-077-diag2.png)
*Восемь именованных частей. Уровень прокси, антиботы и рендеринг — самые сложные для поддержания в здоровом состоянии — и естественное место для покупки.*

## Уровень прокси ломается первым

При низком объеме вы едва замечаете защиту от ботов; на масштабе они ломают запуск первыми. Отправьте несколько сотен тысяч запросов с одного IP, и вы получите ограничение скорости, затем вызов, затем блокировку. Исправление — это ротация по многим адресам. Уровень: дешевые IP-адреса дата-центра для мягких целей и API, [ротация резидентных IP](https://quantumproxies.io/residential-proxies) для сложных коммерческих целей, которые ожидают трафик реальных пользователей. Но одной ротации недостаточно — современные защиты также считывают отпечатки TLS и порядок заголовков, поэтому трафик должен выглядеть как браузер, а не просто исходить с нового IP.

## Повторные попытки: сбой — это нормальное состояние

При миллионе запросов 1% временный уровень сбоев — это 10,000 неудачных страниц. Сбой не является краевым случаем при таком объеме — это рутина, и конвейер должен рассматривать неудачную загрузку как норму, а не как фатальную ошибку. Повторите с экспоненциальным увеличением интервала и ограничением, затем переместите URL в очередь неудачных сообщений вместо блокировки запуска. Прочтите *почему* это не удалось: тайм-аут или 503 стоит повторить, жесткий 404 — нет.

```python
import asyncio, random

async def fetch_with_retry(session, url, tries=4):
    for attempt in range(tries):
        try:
            async with session.get(url, timeout=20) as r:
                if r.status == 404:
                    return None                # don't retry a hard 404
                if r.status < 400:
                    return await r.text()
        except Exception:
            pass
        await asyncio.sleep(2 ** attempt + random.random())  # backoff + jitter
    await dead_letter(url)                     # give up after the cap
    return None
```

## Дедупликация: не скрейпите одну и ту же страницу дважды

Обнаружение на масштабе постоянно производит дубликаты — один и тот же продукт доступен с трех путей, параметры отслеживания, которые делают одну страницу похожей на десять. Нормализуйте URL-адреса перед их добавлением в очередь, затем ведите учет уже просмотренных (множество Redis или фильтр Блума, когда множество достигает сотен миллионов):

```python
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode

seen = set()

def normalize(url):
    s = urlsplit(url.lower())
    q = [(k, v) for k, v in parse_qsl(s.query) if not k.startswith("utm_")]
    return urlunsplit((s.scheme, s.netloc, s.path.rstrip("/"), urlencode(sorted(q)), ""))

def enqueue(url):
    key = normalize(url)
    if key not in seen:
        seen.add(key)
        queue.put_nowait(key)
```

## Хранение и качество данных

Две привычки, специфичные для масштаба: записывайте пакетами, чтобы хранение не стало вашим узким местом, и отделяйте сырые данные от обработанных, чтобы вы могли перепарсить без повторного скрейпинга, когда селекторы изменяются. Затем добавьте ту часть мониторинга, которую большинство команд пропускает — проверки качества данных. Запуск может сообщить о 100% успехе HTTP и все равно выдать мусор, если макет изменился и ваши селекторы теперь не соответствуют ничему. Утверждайте, что обязательные поля не пусты, а значения разумны:

```python
def validate(row):
    assert row.get("title"), "empty title — selector may have drifted"
    price = row.get("price")
    assert isinstance(price, (int, float)) and 0 < price < 1_000_000, "bad price"
    return row

# fail loud on page 5,000, not silently after 5,000,000 empty rows
```

[Перенесите прокси, антиботы и рендеринг в один API](https://quantumproxies.io/scraper-api)

## Рендерьте только когда необходимо

Безголовый браузер — самая дорогая операция в конвейере — ЦП, память и секунды на страницу, которые доминируют над всем при миллионе страниц. Многие сайты все еще отправляют данные в начальном HTML или на JSON-эндпоинте; простая загрузка плюс парсер на порядок дешевле. Попробуйте сначала дешевый путь, подтвердите, что поля присутствуют, и переходите к рендерингу только для страниц, которые в этом нуждаются. Наш анализ [стоимости рендеринга против HTTP](https://quantumproxies.io/blog/headless-browser-vs-http-requests-cost) дает реальные цифры на этот разрыв.

## Строить или покупать сложные уровни

Все вышеперечисленное можно построить, поэтому честный вопрос — какие части заслуживают вашего инженерного времени. Модель данных, логика парсинга, проверки качества и схема хранения специфичны для вашего проекта — только вы можете построить их хорошо. Пул прокси, обработка антиботов, флот безголового рендеринга и очередь повторных попыток и доставки — это общая инфраструктура, которую дорого строить и сложно поддерживать в здоровом состоянии по мере изменения целей. Это линия, на которой стоит управляемый [Scraper API](https://quantumproxies.io/scraper-api): арендуйте части, которые одинаковы для всех. Наш [анализ "строить или покупать"](https://quantumproxies.io/blog/build-vs-buy-scraping-infrastructure) подробно рассматривает налог на обслуживание, а [запуск скрейперов как производственного программного обеспечения](https://quantumproxies.io/blog/schedule-monitor-web-scrapers) охватывает поддержание всей системы в наблюдаемом состоянии.

## Часто задаваемые вопросы

### Как скрейпить миллионы страниц?

С помощью параллелизма, а не большего цикла. Поставьте очередь между обнаружением URL и загрузкой, обрабатывайте её пулом асинхронных или распределенных рабочих, меняйте IP, чтобы избежать блокировок, повторяйте временные сбои с увеличением интервала, дедуплицируйте URL и записывайте данные в хранилище пакетами. Последовательный запуск миллиона страниц занимает дни; та же работа в пуле параллельных рабочих завершается за часы.

### Какая архитектура лучше всего подходит для крупномасштабного веб-скрейпинга?

Конвейер "очередь и рабочие": производитель перечисляет URL в очередь (Redis, RabbitMQ или Kafka), рабочие загружают параллельно через ротационный уровень прокси, рендерят только страницы, которым нужен JavaScript, повторяют сбои в очередь неудачных сообщений, дедуплицируют с помощью множества просмотренных и хранят сырые и обработанные данные отдельно. Оберните это в мониторинг с проверками качества данных, чтобы отклонения проявлялись рано.

### Сколько запросов можно запускать параллельно?

Это зависит от цели и вашего исходящего трафика, а не от фиксированного числа. Скрейпинг ограничен вводом-выводом, поэтому скромная машина может обрабатывать многие сотни запросов в процессе. Начните с параллелизма около 50, наблюдайте за уровнем ошибок и увеличивайте его, пока не начнут расти сбои — это ваш потолок. За пределами ограничений одной машины добавляйте распределенных рабочих, а не нагружайте один узел сильнее.

### Как обрабатывать сбои на масштабе?

Предполагайте сбои — при миллионе запросов даже 1% уровень ошибок — это 10,000 неудачных страниц. Повторяйте временные ошибки (тайм-ауты, 503) с экспоненциальным увеличением интервала и джиттером, ограничивайте попытки и перемещайте постоянные сбои в очередь неудачных сообщений вместо блокировки запуска. Не повторяйте жесткие 404. Рандомизация порядка сбора также распределяет сбои, чтобы вы не сталкивались с одинаковыми страницами при каждом запуске.

Масштаб — это в основном те части, которые неинтересно строить: ротация IP, антиботы, безголовый рендеринг, очереди и повторные попытки. Владейте частями, специфичными для ваших данных — моделью, парсерами, проверками качества — и арендуйте общую инфраструктуру, которая одинаково сложна для всех. Сначала правильно настройте очередь и параллелизм; все остальное — это обеспечение выживания этого параллелизма при контакте с миллионом реальных страниц.

[Запитайте уровень загрузки с помощью ротации резидентных IP](https://quantumproxies.io/residential-proxies)
