Как получить все страницы сайта для поиска информации
Что проверяют
Умеете ли вы спроектировать краулер, который заканчивает работу: обход в ширину, нормализация адресов, ограничение домена и нагрузки. Следом обычно спрашивают, как распараллелить обход и что делать со страницами, которые рендерятся JavaScript.
Как отвечать
Сначала дешёвый путь: robots.txt часто указывает Sitemap, а sitemap.xml уже содержит список страниц. Если его нет или он неполный, нужен обход:
- очередь и посещённые: BFS через очередь URL и
HashSet<string>(при параллельной работеConcurrentDictionary). Рекурсия по ссылкам даёт неконтролируемую глубину, очередь проще ограничить по глубине и числу страниц; - нормализация: относительные ссылки разрешаются через
new Uri(baseUri, href), отрезаются фрагмент и служебные параметры, хост приводится к нижнему регистру. Иначе одна страница попадает в очередь десятком адресов; - границы: только свой хост, только
text/htmlпоContent-Type, учётrel="canonical"иnoindex; - HTTP: один долгоживущий
HttpClientилиIHttpClientFactory, свойUser-Agent, таймауты, обработка редиректов и429сRetry-After; - параллельность:
Channel<Uri>с несколькими воркерами илиParallel.ForEachAsyncсMaxDegreeOfParallelism, плюс пауза между запросами, чтобы не положить чужой сервер; - разбор HTML: AngleSharp или HtmlAgilityPack. Если контент дорисовывается скриптами, нужен headless-браузер через Playwright для .NET, а это на порядок дороже.
Для самого поиска тексты индексируют, например, в Lucene.NET, а не перебирают строки на каждый запрос.
Где спотыкаются
Создают new HttpClient() на каждый запрос внутри using. На тысячах страниц это исчерпание сокетов из-за соединений в TIME_WAIT.
Берут только абсолютные ссылки через Uri.IsWellFormedUriString. Внутренние ссылки почти всегда относительные, поэтому краулер теряет большую часть сайта, зато уходит по внешним на чужие домены.
Не защищаются от ловушек: календарь с бесконечным «следующим месяцем», фильтры каталога с комбинациями параметров, идентификатор сессии в URL. Без лимита глубины и числа страниц обход не закончится.