Привет! Продолжаю разбирать классические задачи с System Design интервью на стримах (за анонсами можете следить тут https://t.me/siliconchannel), а это текстовая версия стрима. В прошлый раз была бесконечная лента, сегодня очередная классика жанра - веб-краулер. Условие звучит примерно так:
Спроектировать веб-краулер, который обходит интернет, вытаскивает со страниц текст и складывает его в хранилище. На этом тексте потом будут обучать LLM (условный ChatGPT). На весь обход даётся 5 дней - данные нужны быстро. Масштаб: ~10 млрд страниц, средняя страница весит ~2 МБ. В ресурсах мы не ограничены (в разумных пределах).
Сразу договоримся о границах. Обучение модели, токенизация и прочий ML - не наша забота. Наша работа заканчивается ровно в тот момент, когда текст лёг в хранилище, дальше его забирает ML-команда, и пусть у неё голова болит.
Этапы
Повторю мысль из прошлой статьи: в System Design нет единственно правильного ответа, и оценивают на интервью не финальную картинку, а то, как вы рассуждаете. Но чтобы не зависнуть перед пустым вайтбордом, полезно держать в голове скелет из этапов:
Сбор требований. Выясняем, что за система, что она должна уметь и под какую нагрузку жить.
Сущности. Перечисляем, вокруг чего всё крутится. Полную схему БД рисовать рано - мы её на этом этапе обычно и не знаем, так что честно говорим интервьюеру, что вернёмся и допишем по ходу.
Интерфейс. В обычном продукте здесь были бы REST-ручки. Но краулер - не пользовательское приложение, API у него нет, поэтому просто фиксируем, что подаём на вход и что получаем на выходе.
Поток данных. Для пользовательских систем этот шаг я пропускаю, а вот для инфраструктурных штук вроде краулера он сильно выручает: расписываем по шагам, как данные идут через систему. Именно этот список потом ляжет в основу архитектуры.
Архитектура. Идём к вайтборду и собираем простейшую систему, которая закрывает функциональные требования. Не больше - на этом этапе схема должна быть максимально тупой и прямолинейной.
Доработка под нефункциональные требования. Самый интересный и долгий этап. Берём нашу простую схему и по одному прогоняем через неё нефункциональные требования - отказоустойчивость, вежливость, масштаб, эффективность, - пока система не начнёт удовлетворять им всем.
Сбор требований
Что вообще такое веб-краулер
На всякий случай: краулер - это программа, которая автоматически обходит веб. Скачала страницу, вытащила из неё ссылки, пошла по этим ссылкам, и так рекурсивно. Поисковики так строят индексы, исследователи собирают данные, а в нашем случае - собирается датасет под обучение модели.
Кстати, если на интервью вам говорят "спроектируй краулер", первый вопрос, который стоит задать, - а зачем он. Краулер для поискового индекса и краулер для разового сбора текста под LLM - это две очень разные системы, и дизайн будет разным.
Функциональные требования
Тут коротко:
обойти весь веб, стартуя с набора seed URL (стартовых ссылок);
извлечь со страниц текст;
сложить его в хранилище.
Нефункциональные требования
А вот здесь начинается мясо. Прежде чем их выписывать, я бы уточнил у интервьюера масштаб: сколько всего страниц и сколько весит средняя. Допустим, нам ответили (или мы прикинули сами): 10 млрд страниц по ~2 МБ. Плюс жёсткий дедлайн - 5 дней на весь обход. Отсюда:
Отказоустойчивость. Интернет - грязное место: сайты лежат, отдают 500-е, таймаутят. Сбои надо переживать, не теряя прогресс. Упасть на середине обхода и начать заново было бы обидно.
Вежливость (politeness). Уважаем
robots.txtи не дудосим чужие сайты. Это базовая гигиена, иначе нас просто забанят.Масштабируемость. 10 млрд страниц сами себя не обойдут.
Эффективность. Уложиться в 5 дней. Вот тут масштаб превращается в конкретное число, и мы его обязательно посчитаем.
И ещё одно, пока не ушли дальше. Во многих гайдах на этом месте советуют сразу садиться за back-of-the-envelope расчёты. Я обычно здесь их не делаю, и вот почему. Кандидат добросовестно перемножает RPS, объёмы и DAU, в конце говорит "ого, дофига" - и продолжает как ни в чём не бывало. Я как интервьюер из этого узнаю только то, что человек умеет умножать. А кандидат не узнаёт про свою систему вообще ничего нового: что она распределённая, было понятно и так. Считать имеет смысл тогда, когда расчёт реально влияет на решение. Дальше будет ровно такой момент - там и посчитаем.
Сущности
Какие сущности крутятся в системе:
текстовые данные - наш выход;
URL и его метаданные - сам адрес плюс информация о том, скачали мы его или нет, где лежит сырой HTML и так далее;
домен и его метаданные - потому что правила из
robots.txtживут на уровне домена, а не отдельного URL.
Полную схему расписывать рано, но модель примерно такая (пишу на Go, по привычке):
type URLMeta struct { URL string `json:"url" db:"url"` // первичный ключ HTMLPath string `json:"html_path" db:"html_path"` // ссылка на сырой HTML в S3 ContentHash string `json:"content_hash" db:"content_hash"` // хэш содержимого Depth int `json:"depth" db:"depth"` // глубина обхода LastCrawled time.Time `json:"last_crawled" db:"last_crawled"` } type DomainMeta struct { Domain string `json:"domain" db:"domain"` LastCrawled time.Time `json:"last_crawled" db:"last_crawled"` Disallow []string `json:"disallow" db:"disallow"` // запрещённые пути из robots.txt CrawlDelay int `json:"crawl_delay" db:"crawl_delay"` // задержка между запросами, сек }
Часть полей сейчас выглядит лишней, и это нормально - они выстрелят позже, когда доберёмся до дедупликации и ловушек.
Интерфейс
Раз пользовательского API нет, просто фиксируем границы системы. На входе - набор seed URL с метаданными. На выходе - текст со всех обойдённых страниц, лежащий в хранилище. Этого достаточно, чтобы зафиксировать, что система ест и что отдаёт.
Поток данных
А вот это самая полезная часть подготовки. Распишем по шагам, что краулер вообще делает, начиная с одного seed URL:
Берём URL из frontier и резолвим его IP через DNS. (Frontier - это множество URL, которые ещё предстоит обойти. В самом начале это просто наши seed-урлы.)
Скачиваем HTML страницы.
Извлекаем из HTML текст.
Сохраняем текст в хранилище.
Вытаскиваем из страницы ссылки и добавляем их во frontier.
Повторяем шаги 1-5, пока frontier не опустеет.
Список нарочно высокоуровневый - это скелет, на который мы сейчас навесим архитектуру. Деталей станет в разы больше, когда полезем в нефункциональные требования, но именно этот простой список разблокирует нас и ведёт к вайтборду.
Архитектура
Берём поток данных и в лоб собираем под него систему.
Первым делом нужна очередь, назовём её frontier-очередью. Kafka это будет или SQS - решим чуть позже, пока непринципиально. В начале в ней лежат seed-урлы.
Дальше ставим воркер-краулер, который читает из этой очереди. Он забирает URL, идёт в DNS за IP, скачивает страницу, извлекает текст и ссылки. Текст кладёт в хранилище - и тут выбор очевидный: это статичные бинарные данные огромного объёма, берём объектное хранилище S3 (объёмы посчитаем ниже, но и без расчётов понятно, что 10 млрд страниц в реляционку мы пихать не будем). А извлечённые ссылки воркер просто возвращает обратно во frontier-очередь. И так по кругу. DNS и сами сайты - внешние для нас системы, всё остальное наше.

Базовая архитектура: frontier-очередь, воркер-краулер, поход в DNS и за HTML, запись в S3 и возврат ссылок в очередь.
Выглядит подозрительно просто, да? Так и должно быть. Эта схема закрывает функциональные требования, и на этом её роль заканчивается. Дальше начинается самое интересное - будем эволюционировать её под нефункциональные.
Расчеты
Прежде чем лезть в deep dives, прикинем порядки. Главный расчёт - сколько нужно машин, чтобы уложиться в 5 дней, - я отложу до этапа масштабирования. Не из лени: систему мы ещё не достроили, и узкое место может оказаться вовсе не там, где кажется. Сначала собираем всё целиком, масштабируем в конце.
А вот объём хранилища посчитать можно прямо сейчас: сырой HTML - это 10 млрд × 2 МБ = 20 ПБ. Двадцать петабайт - это много, но для S3 это вообще не проблема: оно ровно для такого и сделано, масштабируется бесконечно, а за надёжность хранения уже подумал Безос. Текста после очистки от разметки останется в разы меньше, но и он будет измеряться петабайтами. Вывод один - только объектное хранилище, никаких баз.
Заметьте: этот расчёт сразу закрыл вопрос с типом хранилища. Ровно так математика и должна работать на интервью - не ради красивых чисел, а чтобы принять конкретное решение.
Доработка под нефункциональные требования
Дальше идём по требованиям по очереди. Небольшая ремарка про уровни: на мидле от вас ждут в основном ширину - нормально, если узкие места подсказывает интервьюер, а вы грамотно их закрываете. На стаффе наоборот: вы сами уверенно идёте по требованиям и в трёх-четырёх местах ныряете глубоко. Я по ходу буду отмечать, где именно эти места для глубины.
Отказоустойчивость
Посмотрим на наш воркер. Он делает слишком много всего сразу: читает из очереди, резолвит DNS, качает страницу, парсит текст, достаёт ссылки. Падает любой из шагов - теряем прогресс по всем остальным. Масштабировать эти задачи независимо тоже не получается, а наблюдаемость никакая: непонятно даже, на каком этапе мы находимся.
Когда видите задачу про пайплайн обработки данных, первая мысль должна быть такой: как разбить его на более мелкие и осмысленные этапы? У нас просится разбиение на две фазы:
Фаза 1 - Crawler. Только скачать HTML. Самый ненадёжный шаг во всём процессе - это поход на чужой сайт: он лежит, тормозит, рейт-лимитит. Вот пусть воркер и занимается только этим: скачал HTML, положил в S3, обновил метаданные - всё, тяжёлая часть позади.
Фаза 2 - Parser. Из уже сохранённого HTML спокойно достаём текст и ссылки. Эту фазу можно ретраить сколько угодно, не дёргая чужие серверы.
Между фазами ставим вторую очередь - parsing-очередь.

Двухфазный пайплайн: Crawler складывает сырой HTML в S3 и кладёт указатель в parsing-очередь, Parser достаёт HTML, парсит текст и возвращает ссылки во frontier.
Тут возникает резонный вопрос: а почему бы не положить сам HTML прямо в очередь, чтобы парсер забирал его оттуда? Потому что очереди не для этого. Kafka по умолчанию держит сообщения до 1 МБ (настраивается, конечно, но наши страницы по 2 МБ), да и вообще payload в очереди принято минимизировать. Большие данные - работа для блоб-стораджа. Поэтому паттерн стандартный: в очередь кладём указатель на S3 (простенький JSON с URL и ссылкой на файл), а парсер по нему одним запросом достаёт HTML.
У разбиения на фазы есть и приятный бонус - устойчивость к смене требований. Представьте, приходит ML-команда: "текст отличный, но нам нужен ещё alt-текст у картинок и распознанный текст с изображений". Был бы у нас монолитный воркер - пришлось бы заново обходить весь интернет. А так просто перезаливаем все URL в parsing-очередь, обновляем логику парсера и прогоняем заново. Никакого повторного дорогого краулинга.
Теперь ретраи. Что делать, если страница не скачалась? Очевидный плохой вариант - поставить in-memory таймер в воркере и через 5-10 секунд попробовать снова. Плохой по двум причинам: упадёт воркер - таймер потеряем, да и лежащий сайт за 5 секунд вряд ли оживёт.
Нужен нормальный exponential backoff, и здесь интересная развилка для глубины. В Kafka ретраев из коробки нет: пришлось бы городить отдельный топик для зафейленных URL и самим класть в сообщение время следующей попытки. Рабочее решение, но грязноватое. А в SQS ретраи с настраиваемым backoff есть из коробки - через visibility timeout, период, на который сообщение становится невидимым для других консьюмеров после того, как его кто-то забрал. По умолчанию это 30 секунд, и после каждой неудачной попытки он растёт экспоненциально: 30 секунд → 2 минуты → 5 минут → и так далее. Руками ничего реализовывать не надо, всё настраивается конфигом. За такую прагматичность я и люблю SQS, его в этом дизайне и берём.
Бесконечно ретраить тоже глупо: возможно, сайт умер насовсем. Ставим лимит попыток (в SQS это approximate receive count, например 5), после чего сообщение автоматически уезжает в Dead Letter Queue - специальную очередь для того, что обработать не удалось. Это почти наверняка мёртвые URL, и что с ними делать дальше - вопрос уже к продуктовой команде.
Знать разницу очередей на таком уровне для интервью не обязательно, но это ровно одно из мест, где можно блеснуть. Не знаете - достаточно сказать, что in-memory таймер не подойдёт и нужен exponential backoff, и предложить тот самый вариант с отдельным топиком. На любом уровне это нормальный ответ.
И ещё про падения. Что, если упадёт сам воркер? Поднимаем новый, ничего страшного: URL остаётся в очереди, пока мы не подтвердим, что HTML реально лёг в S3. Механика разная по очередям, и это тоже место для глубины. В Kafka каждое сообщение помечено offset'ом, а воркеры состоят в одной consumer group - это гарантирует, что сообщение прочитает только один из них. Обработал успешно - закоммитил offset, остальные за это сообщение уже не возьмутся (физически сообщения удаляются по retention policy, по умолчанию через 7 дней). В SQS сообщение лежит, пока его явно не удалят: воркер забрал - сработал visibility timeout, остальные его не видят; успел сохранить HTML - послал команду на удаление; упал - таймаут истёк, сообщение снова видимо, его подберёт другой воркер.
Вежливость
Начнём с примера. robots.txt - это файл на сервере сайта, который задаёт правила обхода по домену:
User-agent: * Disallow: /private Crawl-delay: 10
User-agent: * - правила для всех краулеров. Disallow - пути, которые трогать нельзя. Crawl-delay: 10 - между двумя запросами к этому домену надо ждать 10 секунд (это много, но бывает).
Значит, при первом обращении к домену тянем его robots.txt и складываем правила в DomainMeta, а дальше просто смотрим в эту таблицу. Алгоритм на каждый URL такой: сначала проверяем, разрешён ли путь вообще - если нет, подтверждаем сообщение и убираем его из очереди (в Kafka двигаем offset, в SQS явно удаляем). Потом смотрим на LastCrawled и CrawlDelay: если с прошлого захода прошло меньше, чем положено, нам ещё рано - возвращаем URL в очередь, выставив visibility timeout равным crawl-delay, и ждём положенное.
robots.txt со временем меняется, так что неплохо повесить на него TTL и периодически перечитывать, а сами правила закэшировать в Redis, чтобы не дёргать таблицу на каждый чих. Хотя, честно говоря, мы тут всё равно упёрты в скорость скачивания страниц, так что это скорее приятная мелочь.
Дальше - общий rate limiting. Даже если crawl-delay не задан (а его не задаёт почти никто), индустриальный стандарт - не больше одного запроса в секунду на домен. Ставим рейт-лимитер: тот же Redis со sliding window на каждый домен, считаем запросы за последнюю секунду и не превышаем. Не прошли по лимиту - возвращаем URL в очередь с visibility timeout.
Тут есть важная деталь - jitter, случайная добавка ко времени. Без неё легко словить такую картину: десять воркеров упёрлись в рейт-лимит одного домена, все вернули URL в очередь, все одновременно забрали их снова, опять упёрлись - и так по кругу. Добавляем рандом, и эта синхронность рассасывается.
И ещё одна проблема, потоньше. Когда парсим страницу, велик шанс, что куча ссылок на ней - с того же домена. Кидаем их в очередь, получаем бэклог из условных 200 URL одного домена, воркеры их разбирают, все упираются в crawl-delay, все возвращают обратно - такты жгутся впустую. Решение, которое любят приносить сеньоры и стаффы, - smart scheduler: вместо того чтобы кидать ссылки прямо в очередь, складываем их в метаданные, а отдельный планировщик периодически подбирает оттуда URL по какому-нибудь приоритетному алгоритму и раскладывает по очереди равномерно. В основную схему я его тащить не буду, штука довольно абстрактная, но как тема для глубокого разговора - отличная.
Масштаб и эффективность
Эти двое ходят парой: чтобы быть эффективным, надо уметь масштабироваться. И вот теперь тот самый отложенный расчёт.
Вопрос конкретный: сколько краулеров нужно, чтобы обойти 10 млрд страниц за 5 дней? Сразу скажу честно - оценка будет грубой. В реальной жизни тут запустили бы тест, замерили фактический throughput и умножили. На интервью теста нет, считаем на коленке:
топовые network-optimized инстансы AWS тянут порядка
400 Гбит/с;400 Гбит/с ÷ 8 = 50 ГБ/с = 50 000 МБ/с;при странице в 2 МБ это
50 000 ÷ 2 = 25 000 страниц/с- теоретический потолок одного инстанса.
Понятно, что 25 000 страниц в секунду - фантастика: сто процентов полосы не выжать никогда, мешают DNS, рейт-лимиты, crawl-delay, медленные сайты, ретраи. Реалистично заложим процентов 40:
25 000 × 0.4 ≈ 10 000 страниц/с;10 млрд ÷ 10 000 = 10^6 секундна одной машине;10^6 ÷ 86 400 ≈ 11.5 дней.
Почти 12 дней на одной машине. Чтобы уложиться в 5, нужно минимум 3 (11.5 / 3 ≈ 3.8 дня), а с запасом на сбои и ошибки парсинга возьмём 4 таких инстанса под краулеры.
С парсером проще: он стоит после нашего узкого места (скачивания), так что ему достаточно успевать за входящим потоком. Пусть масштабируется динамически по длине parsing-очереди: бэклог растёт - поднимаем больше воркеров, EC2, Lambda, что угодно.
Про DNS забывают чаще всего, а это частое узкое место. У стороннего DNS-провайдера почти наверняка есть рейт-лимиты. Их можно поднять за деньги (а мы условились, что богатые), но есть ходы поизящнее. Первый - кэширование: у нас уже есть Redis под рейт-лимитер и кэш robots, пусть он же кэширует и резолвы. Один запрос на домен, дальше всё из кэша - нагрузка на провайдера падает в разы. Второй - несколько DNS-провайдеров с round-robin: размазываем нагрузку и заодно страхуемся от падения одного из них. Простое, но очень практичное решение - мне его как-то предложил кандидат на собеседовании, и идея понравилась: видно, что человек думает не книжными ответами, а как инженер, который реально упирался в DNS на проде.
Теперь к самой эффективности. Чтобы не тратить время впустую, не надо обходить то, что уже обошли. Случая два.
Первый, простой - не краулить один и тот же URL дважды. Дубликатов будет полно: парсер достаёт ссылки, и многие из них мы уже видели. Решение - сделать URL первичным ключом в URLMeta и при добавлении проверять, есть ли он уже. Чтобы не плодить дубли ещё на входе, кладём URL в метаданные сразу при добавлении во frontier - с пустым LastCrawled, который заполнится после обхода. Записей будет 10 млрд, так что таблицу шардируем по первичному ключу; поиск остаётся быстрым, и это точно не наше узкое место.
Второй, потоньше - не парсить дубликат контента. Два разных URL (порой даже на разных доменах) могут вести на абсолютно одинаковый контент, и в интернете это случается чаще, чем кажется. Ловим так: краулер хэширует HTML и кладёт хэш в ContentHash, а перед отправкой страницы в parsing-очередь проверяем, не видели ли мы уже такой хэш. Весь вопрос - как сделать эту проверку быстрой:
Global Secondary Index по хэшу в DynamoDB. На мой взгляд, лучший вариант: проверка за
log n, всё лежит рядом с остальными данными, а узкое место у нас всё равно в скачивании - этого хватает с запасом.Redis-сет хэшей. Проверка за
O(1). Прикинем объём:10^10 страниц × 20 байт на хэш = 200 ГБ- один инстанс на 256 ГБ вытянет. Минусы: лишнее железо плюс отдельная головная боль с отказоустойчивостью и персистентностью, если Redis ляжет. Зато всё в памяти, десятки миллисекунд.
И отдельно про bloom-фильтр, потому что на этом месте его называет каждый второй кандидат - и обычно зря. Bloom-фильтр - это компактная вероятностная структура для проверки на вхождение в множество. Ключевое слово - вероятностная: вы меняете точность на экономию памяти. Возможны ложноположительные срабатывания (а ложноотрицательные невозможны). Для нас это значит вот что: фильтр иногда скажет "этот контент уже парсили", хотя на самом деле нет, - и мы потеряем страницу. На 10 млрд URL таких потерь немного, и, может, на них плевать. А может, нам критично собрать вообще весь текст - тогда размен плохой.
Так вот, при наших вводных (память не ограничена) я бы bloom-фильтр даже не рассматривал. И приношу его сюда только потому, что его постоянно называют - и часто это скорее плохой сигнал. Кандидат не ставит задачу ("у меня ограничена память в Redis, поэтому..."), не сравнивает с GSI или обычным сетом, а сразу выпаливает "возьму bloom-фильтр", потому что он на слуху из книжек. Структура-то классная, просто применяйте её там, где она правда нужна.
Ловушки для краулеров
Последний штрих к эффективности - crawler traps. Это страницы, специально сделанные так, чтобы держать краулер на сайте вечно: страница ссылается сама на себя или на сотни тысяч пустых страниц того же домена. Без подстраховки можно бесконечно крутиться на одном домене без всякой пользы.
Лечится просто: вводим максимальную глубину обхода (Depth, например 20). Парсер достал новую ссылку - поставил ей глубину родителя плюс один. Превысили порог - не кладём URL обратно в очередь. Вот и всё.
Что ещё можно обсудить
Дизайн уже закрывает и функциональные, и нефункциональные требования - на сеньорском интервью этого с лихвой. Но если осталось время или хочется добить глубину, вот темы:
Динамический контент. Куча сайтов сегодня на React и Angular, и контент там не в HTML, а догружается JavaScript'ом. Чтобы такое забирать, нужен headless-браузер (Puppeteer и компания), который отрендерит JS перед парсингом. Это заметно медленнее, дороже и капризнее - вопрос, который лучше прояснить с интервьюером сразу.
Мониторинг. Datadog, New Relic и прочее: смотреть, сколько URL на каждой фазе пайплайна и какие основные ошибки. Разбиение на фазы тут очень кстати.
Большие страницы. Некоторые страницы огромны и портят жизнь. По заголовку
Content-Lengthслишком большие файлы можно просто скипать.Постоянное обновление. Если краулер нужен не разово, а как у поисковика (или модель переобучают раз в пару месяцев), он должен крутиться непрерывно. Тут и пригодится тот самый URL scheduler: периодически смотрит в метаданные, по
LastCrawledрешает, что пора перекраулить, и кладёт URL обратно в очередь.

Финальная архитектура: frontier-очередь → краулеры → S3 с сырым HTML, метаданные в DynamoDB, parsing-очередь → парсеры → текст в S3, Redis под рейт-лимит и DNS-кэш, DLQ для мёртвых URL.
На этом всё. Из тупой схемы с одной очередью и одним воркером мы дошли до краулера, который переживает сбои, не дудосит чужие сайты и обходит 10 млрд страниц за 5 дней. Попадётся задача на интервью - будет с чем выходить к вайтборду. А как решали бы вы - пишите в комментариях, всегда интересно почитать.
Комментарии (17)

Nexoic
20.06.2026 13:56Что то я не увидел пункта про пул прокси, большой % сайтов за cloudflare , ваши ip забанят быстрее чем вы 100к страниц загрузите.
Я думаю прокси будут сильно дороже всего остального проекта для такого масштаба.

xSVPx
20.06.2026 13:56Да и 10к никто не отдаст. И следование лимитам не спасет, только слегка отсрочит.
Как извлечь - это буквально основная тут задача. Надо строить огромную толпу краулеров и гигантскую толпу прокси ими использовать. И совершенно нет смысла лимитам следовать, ддосить не надо конечно, надо оценивать время ответа и подбирать рейт соответственно.
В целом с нуля сроки совершенно нереальные.

bincoin Автор
20.06.2026 13:56Вообще это классическая задача и классическое решение.
Проблема выше тянет на отдельный сисдиз)

dembahol
20.06.2026 13:56Классическая, не классическая, а было бы интересно увидеть разбор в этой статье)

xSVPx
20.06.2026 13:56Это не решение задачи. Задача решения не имеет. Вряд ли можно успеть за пять суток. Решение тут "мы не успеем, шансов ноль".
Более того, сама задача сформулирована странно. Надо хреналион страниц ? Любых ? Так я вам нагенерю :).
А вот если надо хреналион страниц с определенных ресурсов, ибо там есть нужная информация, то ограничивать их просто так глубиной обхода в 20 - это вредительство. Много вы так с какого-нибудь фейсбука напарсите ?
И вообще, сколько процентов страниц от общего надо собрать ? 10%? Или 99%? Разница в подходе огромная. В первом случае мы вообще можем длинное скипать. Во втором придется очень постараться.
Это уж не говоря о том, что делать с страницами, которые по сути 404/503, а возвращаются с кодом 200. Можете легко собрать кучу заглушек "Ваш лимит превышен" вместо реальных данных. И это совершенно обычная ситуация.
Как будете валидировать то, что получили ? Что делать с динамикой ? С страницами в тексте которых есть сообщения об ошибках.
Зачем вы вообще собираетесь помещать в очередь ссылки на сайты, которые не резолвятся в днс?
Итд итп.
Задача гораздо-гораздо обширнее того, в какую базу это сложить и какие очереди использовать.

bincoin Автор
20.06.2026 13:56Но все таки на собеседованиях в условную Meta вы получите ее в такой формулировке и ждать от вас будут такого решения)

tkutru
20.06.2026 13:56Именно. Условный cloudfare и подобное положит ~80% запросов к значимым (не мусорным) сайтам. Это раз.
Также не совсем понятна идеализация облаков, особенно заграничных. Мол, раз "Безос подумал" (а он вряд ли думал), то и придумывать ничего не надо, и надо надеяться на AWS и S3, как будто они беспроблемные, оптимальные, и не существует например всяких распределенных бд и прочих infrastructure as code. Это два.

apcs660
20.06.2026 13:56А как же точки сохранения для кролера на случай падения и рестартов, он же распределенный.
Какая-никакая админка нужна.
Можно сделать задачу попроще и ближе к реальности - репозиторий документов на 1 млд документов, есть текстовые, PDF, tiff. Нужно кролить разный контент (просто текст, текст из пдф, и OCR из тифф).
Вот тебе процессинг контента. Причем его можно делать синхронно, асинхронно, с честной очередью, общей и внутри процессоров, тот же оср можно на ллм сделать специализированной или классически и тд и тп. Можно сделать полиглотную схему на сервисах а можно в монолите (несколько инстансов каждый на свой сегмент документов).
Задно спросить - как обрабатывать документы разного размера - есть к примеру tiff, в основной массе 100 KB но пробегают до 1ГБ - их можно разбирать только однопоточно и с большим объемом памяти. Вот в реале так бывает - отдельно парсишь мелочевку в Н потоков а на крупные документы отдельный процесс в одну нитку.
Сырой контент можно класть в s3.
Распарсенный контент и метаданные в какой то базе кешировать и заодно лог обновлений создать. Для быстрого чтения потребителями без рекрола.
Хеш или чексум, версионирование.
Не люблю интервью если честно - последний раз проходил сисдизайн в Яндексе - какой то нервный пацан не в настроении попался, сбил мне настрой - не прошел хотя тема простая была.Вот по этой теме я бы тому интервьюверу с Яндекса мозги бы накрутил. Остальные кстати нормально себя вели.

bincoin Автор
20.06.2026 13:56Это проблема вообще формата единого найма в корпорациях, что за частую результат чисто от настроения интервьюера зависит и в целом от его душности.
В этом плане когда тебя ребята из команды куда смотрят собесят намного лучше подход
gerbert_MX
кратко и по делу, хорошая статья)
единое что - S3 юзать как кеш такое себе, равзе что на этапе отладки. А так можно сразу отдавать в очередь парсинга и в S3 хранить уже нужные данные (или хотя бы минифицированый документ без css и js) потому как некоторые сайты сырым текстом до 12МБ могут быть где полезной нагрузки хорошо если 100кб
bincoin Автор
Спасибо, в целом полностью согласен, хорошее замечание)