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

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

Что на самом деле означает крупномасштабный веб-скрейпинг

Крупномасштабный веб-скрейпинг, это практика извлечения данных с миллионов страниц: с одного огромного сайта или одновременно тысяч меньших. Переход от обычного скрейпинга, не просто большее число, и одна цифра делает это конкретным. Представьте категорию с 20 000 страниц листингов по 20 позиций каждая, то есть 400 000 страниц для загрузки. При реалистичных 2,5 секундах на страницу строго последовательный запуск занимает приблизительно 1 000 000 секунд, или около 11,5 дней ожидания загрузки страниц до того, как вы распарсите хоть одно поле. Здесь числа иллюстративны, но порядок величины верен. Именно в этой цифре и заключается смысл данной статьи: в масштабе время является ограничением, а параллелизм, способом его вернуть. Обработка 200 страниц параллельно схлопывает те 11,5 дней до приблизительно часа реального времени.

Архитектура в общих чертах

Скрейпер, выдерживающий миллионы страниц, представляет собой небольшую распределённую систему с несколькими именованными компонентами. Каждый из них решает проблему, которая возникает только при объёме.

  • Очередь хранит URL, ожидающие загрузки, и отделяет обнаружение от работы, чтобы производители и потребители работали в собственном темпе.
  • Асинхронные или распределённые воркеры берут задачи из очереди и выполняют загрузку параллельно. Именно здесь возникает экономия реального времени.
  • Слой прокси и антибота ротирует IP и представляет трафик, который цели воспринимают как реальный браузер, чтобы ни один адрес не превысил лимит скорости.
  • Рендеринг, только когда нужен, запускает безголовый браузер для JavaScript-насыщенных страниц и пропускает его для статичных, поскольку рендеринг, самая затратная операция.
  • Повторные попытки с отступлением перехватывают преходящие сбои, неизбежные при таком объёме.
  • Дедупликация предотвращает двойную загрузку или хранение одного URL.
  • Хранилище принимает распарсенные строки и помещает их куда-то, где можно делать запросы.
  • Мониторинг и проверки качества данных говорят вам, что запуск здоров, а вывод заслуживает доверия.

Разделы ниже рассматривают их по порядку. Нить, проходящая через все, это компромисс между контролем и операционной нагрузкой: строить каждый слой самостоятельно или отдать самые сложные (прокси, антибот, рендеринг, повторные попытки) управляемому слою и тратить время на данные.

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

Единственное наиболее важное структурное решение, поставить очередь между «что скрейпить» и «выполнять скрейпинг». Производитель перечисляет URL (из карты сайта, обхода результатов поиска или базы данных идентификаторов) и помещает их в очередь; пул воркеров берёт задачи из неё. Ни одна из сторон не должна знать, как быстро работает другая, и вы можете добавлять воркеры, не трогая производителя.

В Python это обычно Celery или RQ поверх Redis; в Node, очередь Bull или BullMQ поверх Redis; в большем масштабе, реальный брокер вроде RabbitMQ или Kafka. Минимальный набросок воркера делает паттерн конкретным.

python
import asyncio
import aiohttp

CONCURRENCY = 50
queue = asyncio.Queue()

async def worker(name, session):
    while True:
        url = await queue.get()
        try:
            async with session.get(url) as resp:
                html = await resp.text()
                parse_and_store(url, html)
        except Exception as err:
            print(f'failed {url}: {err}')
        finally:
            queue.task_done()

async def run(urls):
    for u in urls:
        queue.put_nowait(u)
    async with aiohttp.ClientSession() as session:
        workers = [asyncio.create_task(worker(i, session)) for i in range(CONCURRENCY)]
        await queue.join()
        for w in workers:
            w.cancel()

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

Асинхронность vs распределённость

Асинхронный параллелизм (одна машина, много запросов в полёте) и распределённые воркеры (много машин) решают разные потолки. Асинхронность дёшево поднимает вас с уровня одного запроса за раз. Распределённые воркеры переводят вас за пределы ограничений одной машины: CPU для рендеринга, память для парсинга и исходящая пропускная способность. Большинство крупных задач используют оба подхода: асинхронность внутри каждого воркера, много воркеров на разных машинах.

Ротация прокси и антибот: то, что ломается первым

При низком объёме антибот-защита почти не замечается; в масштабе она первой ломает запуск. Отправьте несколько сотен тысяч запросов с одного IP, и вас ограничат по скорости, потом проверят, потом заблокируют. Решение, ротация: распределите запросы по многим адресам, чтобы ни один не выглядел злоупотреблением.

Тип прокси имеет значение. Датацентровые IP дешевы и быстры, но легко поддаются снятию отпечатков и блокировке оптом. Резидентные прокси маршрутизируют через реальные потребительские соединения и воспринимаются как обычные пользователи, чего требуют жёсткие коммерческие цели. Для большинства крупных задач правильным выбором по умолчанию является пул ротируемых резидентных прокси, где каждый запрос или короткая сессия исходит от нового реального пользовательского IP. Если собирать это самостоятельно, правильная реализация логики ротации (закреплённые сессии там, где нужны сайту, новые IP там, где нет) составляет большую часть работы; см. как использовать ротируемые прокси.

Ротация необходима, но недостаточна. Современные защиты также считывают TLS-отпечатки, порядок заголовков и поведение браузера. Управляемый слой, такой как Crawlbase Smart AI Proxy, объединяет ротацию и обработку отпечатков в единую конечную точку: вы направляете существующий HTTP-клиент на один URL прокси, а он управляет пулом, заголовками и повторными попытками при блокировках за ним. Полное защитное руководство см. в статье как скрейпить сайты без блокировок.

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

Рендеринг страницы в безголовом браузере, самая затратная операция в конвейере: он стоит CPU, памяти и секунд на страницу, а при миллионе страниц эти секунды доминируют над всем. Поэтому рендерьте только тогда, когда данные действительно этого требуют.

Многие сайты по-прежнему включают данные в первоначальный HTML или предоставляют их через JSON-конечную точку, которую вызывает страница. Для таких сайтов простой HTTP-запрос плюс парсер на порядок дешевле браузера. Оставьте рендеринг для страниц, которые строят контент на стороне клиента, где голый запрос возвращает пустую оболочку. Дисциплина проста: сначала попробуйте дешёвый путь, убедитесь, что поля присутствуют, и переходите к рендерингу только для страниц, которым он нужен. Совмещение обоих подходов в одном запуске (статическая загрузка для страниц каталога, рендеринг для небольшого числа детальных страниц с JavaScript) является нормой и именно там находится экономия.

Управляемый слой масштабирования: Crawling API и асинхронный Crawler

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

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({ 'token': 'YOUR_CRAWLBASE_TOKEN' })

options = {
    'ajax_wait': 'true',
    'page_wait': 3000,
    'country': 'US',
}

resp = api.get('https://www.example.com/products?page=42', options)
if resp['status_code'] == 200:
    parse_and_store(resp['body'])

Синхронные вызовы отлично подходят внутри пула воркеров выше: каждый воркер вызывает api.get, а API берёт на себя заботы о прокси, антиботе и рендеринге. Но для действительно крупных задач существует лучший паттерн. Асинхронный Crawler инвертирует поток: вместо того чтобы держать соединение открытым, пока каждая страница загружается, вы отправляете в него URL, а он краулит их по собственному расписанию, затем POST-запросом возвращает каждую готовую страницу на конечную точку вебхука, которую вы контролируете. Вы добавляете два параметра к вызову Crawling API, &callback=true&crawler=YourCrawlerName, и Crawler берёт на себя очередь, расписание и повторные попытки.

python
from crawlbase import CrawlingAPI

api = CrawlingAPI({ 'token': 'YOUR_CRAWLBASE_TOKEN' })

# Push as many URLs as you like; the Crawler queues and crawls them async,
# then POSTs each finished page to the webhook on your registered crawler.
for url in urls_to_crawl:
    api.get(url, {
        'callback': 'true',
        'crawler': 'my-products-crawler',
    })

Асинхронная модель, правильный выбор для миллионов страниц, поскольку устраняет часть системы, которую иначе пришлось бы обслуживать вручную. Вы не держите соединения открытыми, не запускаете флот рендеринга, не управляете очередью повторных попыток; вы отправляете и получаете. Crawler даже мониторит ваш вебхук: если ваша конечная точка упадёт, он сделает паузу, уведомит вас, повторит попытку доставки и автоматически возобновит работу, когда ваш сервер будет в строю. Это очередь, расписание, повторные попытки и надёжность доставки, управляемые как слой-сервис, большая часть того, что остаток этой статьи советует вам строить.

Crawlbase Crawling API + async Crawler

Масштаб, это в основном части, которые неинтересно строить: ротируемые резидентные IP, антибот, безголовый рендеринг, очереди и повторные попытки. Crawling API объединяет первые три в один вызов, а асинхронный Crawler принимает отправленные вами URL, краулит их по собственному расписанию и POST-запросом доставляет готовые страницы на ваш вебхук с автоматическими повторными попытками. Сначала направьте его на публичную цель с бесплатного уровня.

Повторные попытки и отступление: сбой, это постоянное состояние

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

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

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

Обнаружение в масштабе постоянно порождает дубликаты: один и тот же товар доступен из трёх путей категорий, параметры отслеживания делают одну страницу похожей на десять, пагинация зацикливается. Без дедупликации вы тратите бюджет на повторную загрузку страниц и засоряете набор данных повторяющимися строками.

Два слоя справляются с этим. Во-первых, нормализуйте URL перед помещением в очередь: удаляйте параметры отслеживания, переводите хост в нижний регистр, сортируйте ключи запроса, приводите относительные ссылки к канонической форме. Во-вторых, ведите множество посещённых URL (набор Redis или фильтр Блума для очень крупных запусков) и пропускайте любой URL, который в нём уже есть. Фильтр Блума обменивает маленький процент ложноположительных срабатываний на существенную экономию памяти, правильный компромисс, когда множества посещённых URL достигают сотен миллионов. Дедуплицируйте и вывод: ключируйте строки по стабильному идентификатору, чтобы страница, загруженная дважды, не стала двумя записями.

Хранение: выбирайте хранилище под паттерн доступа

Место хранения данных зависит от того, что вы с ними делаете дальше. Плоские файлы (CSV, JSONL) или объектное хранилище подходят для архивирования с активной записью и дешёвой пакетной обработки. Реляционная база данных подходит, когда нужно делать запросы, объединять и обновлять строки. Хранилище документов подходит для полуструктурированных записей, форма которых варьируется по источнику. Ошибка, загонять всё в одно из них, потому что оно первым оказалось под рукой.

Две привычки, специфичные для масштаба, имеют значение. Пишите пакетами, а не по одной строке на запрос, чтобы хранилище не стало узким местом; воркер должен буферизовать и сбрасывать. И разделяйте сырые данные и распарсенные: храните оригинальный HTML (или ссылку на него) так, чтобы можно было перепарсить без повторной загрузки при изменении селекторов или обнаружении нового поля. Crawlbase может доставлять страницы прямо в Cloud Storage или на ваш вебхук, полностью убирая с вашей стороны трубопровод приёма.

Мониторинг и качество данных

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

Качество данных, та половина мониторинга, которую команды пропускают, и та, которая определяет, можно ли использовать данные. Запуск может сообщать о 100% успехе HTTP и при этом производить мусор, если разметка изменилась и ваши селекторы теперь ничему не соответствуют. Добавьте дешёвые, непрерывные проверки: убедитесь, что обязательные поля непустые, что цены парсятся как числа в разумном диапазоне, что количество строк на страницу примерно соответствует ожидаемому. Когда проверка fails на многих страницах сразу, разметка изменилась и парсер требует внимания. Лучше поймать это на 5 000-й странице, чем после хранения пяти миллионов пустых строк.

Где провести границу «строить vs покупать»

Всё вышеописанное можно построить самому, поэтому честный вопрос, какие части стоят вашего инженерного времени. Модель данных, логика парсинга, проверки качества и схема хранилища специфичны для вашего проекта, и только вы можете их хорошо построить. Пул прокси, обработка антибота, флот безголового рендеринга и асинхронная очередь повторных попыток и доставки, это обобщённая инфраструктура, дорогая в создании и утомительная в поддержке по мере эволюции целей. Вот где находится управляемый слой масштабирования: используйте Crawling API или асинхронный Crawler для загрузки, рендеринга и антибота; Smart AI Proxy, чтобы сохранить собственный клиент и подключить управляемую ротирующую конечную точку; или Crawling API, для распарсенного JSON с поддерживаемых сайтов, чтобы полностью пропустить этап селекторов. Тратьте время на данные; арендуйте части, одинаковые для всех.

Итоги

Ключевые выводы

  • Масштаб, это параллелизм, а не большой цикл. Последовательный запуск на миллион страниц занимает дни; очередь, питающая асинхронных или распределённых воркеров, сжимает это до часов.
  • Прокси и антибот ломаются первыми. Ротируйте через резидентные IP и представляйте трафик реального браузера, или пусть управляемый слой занимается ротацией и отпечатками за вас.
  • Рендерьте только когда необходимо. Безголовый рендеринг, самый дорогой шаг; сначала попробуйте статическую загрузку и переходите к нему только для клиентских страниц.
  • Сбой, это постоянное состояние. Повторяйте преходящие ошибки с отступлением и очередью мёртвых писем, дедуплицируйте URL и строки и воспринимайте коды статусов прокси как сигнал.
  • Асинхронность обходит синхронность на верхнем уровне. Отправляйте URL в Crawler и получайте результаты на вебхуке, чтобы очередь, расписание, повторные попытки и доставка обрабатывались за вас.
  • Мониторьте успех и качество данных. 100% успех HTTP при пустых полях, всё равно неудачный запуск; делайте утверждения о данных, а не только о коде статуса.

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

Что считается крупномасштабным веб-скрейпингом?

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

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

Распределяйте запросы по многим IP, чтобы ни один адрес не выглядел злоупотреблением, предпочитайте ротируемые резидентные прокси для жёстких целей, представляйте трафик реального браузера, регулируйте запросы и отступайте при появлении проверок. Строить всё это самостоятельно, значительная работа, поэтому большинство команд маршрутизируют через управляемый слой, такой как Crawling API или Smart AI Proxy, который обрабатывает ротацию, отпечатки и решение проверок за одной конечной точкой.

Использовать ли синхронный или асинхронный скрейпинг в масштабе?

Асинхронный, для всего по-настоящему крупного. Синхронная загрузка держит соединение открытым на страницу и занимает воркер до завершения каждого запроса. Асинхронная модель Crawler позволяет отправлять URL и получать готовые страницы в вебхук-колбэке, так что очередь, расписание и повторные попытки происходят на сервере, а ваше приложение не блокируется в ожидании. Отправляете и получаете, значительно проще масштабировать и эксплуатировать.

Всегда ли нужен безголовый браузер для крупномасштабного скрейпинга?

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

Как обрабатывать сбои и дубликаты при миллионах запросов?

Воспринимайте оба как рутину. Повторяйте преходящие сбои (таймауты, 503) с экспоненциальным отступлением и ограничением, затем отправляйте упрямые в очередь мёртвых писем вместо блокировки запуска; не повторяйте жёсткие 404. Для дубликатов нормализуйте URL перед постановкой в очередь, ведите множество посещённых URL или фильтр Блума, чтобы пропускать уже загруженные, и ключируйте хранимые строки по стабильному идентификатору, чтобы страница, загруженная дважды, не стала двумя записями.

Где хранить данные из крупного скрейпинга?

Выбирайте хранилище под способ использования данных: объектное хранилище или JSONL для дешёвого архивирования, реляционная база данных для запросов и объединений, хранилище документов для записей с переменной формой. Пишите пакетами, а не по одной строке на запрос, чтобы хранилище не стало узким местом, и храните сырой HTML рядом с распарсенным выводом, чтобы можно было перепарсить без повторной загрузки при изменении селекторов или добавлении поля.

Начать создавать

Обходите любой сайт в масштабе, без борьбы с инфраструктурой.

Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.

Самообслуживание · Звонок отдела продаж не требуется · Доступны корпоративные объёмы краулинга