Способ решения этой проблемы во многом будет зависеть от того, сколько страниц вы хотите загрузить и на скольких сайтах вы ссылаетесь.
Я буду использовать хорошее круглое число, например 1000.Если вы хотите загрузить столько страниц с одного сайта, это займет намного больше времени, чем если бы вы захотели загрузить 1000 страниц, которые распределены по десяткам или сотням сайтов.Причина в том, что если вы попали на один сайт с целой кучей одновременных запросов, вы, вероятно, в конечном итоге заблокируетесь.между несколькими запросами на одном сайте.Продолжительность этой задержки зависит от ряда вещей.Если в файле robots.txt на сайте есть запись crawl-delay
, вы должны это учитывать.Если они не хотят, чтобы вы обращались более чем к одной странице в минуту, то это происходит так же быстро, как и при сканировании.Если нет crawl-delay
, вы должны основывать свою задержку на том, сколько времени требуется сайту для ответа.Например, если вы можете загрузить страницу с сайта за 500 миллисекунд, вы установите задержку на X. Если это займет целую секунду, установите задержку на 2X.Вероятно, вы можете ограничить задержку до 60 секунд (если crawl-delay
больше), и я бы порекомендовал установить минимальную задержку от 5 до 10 секунд.
Я бы не рекомендовал использовать Parallel.ForEach
дляэтот.Мое тестирование показало, что оно не работает хорошо.Иногда это переоценивает соединение и часто не позволяет достаточно одновременных соединений.Вместо этого я бы создал очередь из WebClient
экземпляров, а затем написал бы что-то вроде:
// Create queue of WebClient instances
BlockingCollection<WebClient> ClientQueue = new BlockingCollection<WebClient>();
// Initialize queue with some number of WebClient instances
// now process urls
foreach (var url in urls_to_download)
{
var worker = ClientQueue.Take();
worker.DownloadStringAsync(url, ...);
}
Когда вы инициализируете WebClient
экземпляров, которые попадают в очередь, установите их обработчики событий OnDownloadStringCompleted
, чтобы они указывали назавершенный обработчик событий.Этот обработчик должен сохранить строку в файл (или, возможно, вам следует просто использовать DownloadFileAsync
), а затем клиент, , добавляет себя обратно к ClientQueue
.
В моем тестированииЯ смог поддерживать от 10 до 15 одновременных подключений с помощью этого метода.Более того, у меня возникают проблемы с разрешением DNS (`DownloadStringAsync 'не выполняет разрешение DNS асинхронно).Вы можете получить больше подключений, но сделать это - большая работа.
Это подход, который я использовал в прошлом, и он очень хорошо работал для быстрой загрузки тысяч страниц.Однако это определенно не тот подход, который я использовал в своем высокопроизводительном веб-сканере.
Следует также отметить, что между этими двумя блоками кода существует огромная разница в использовании ресурсов:
WebClient MyWebClient = new WebClient();
foreach (var url in urls_to_download)
{
MyWebClient.DownloadString(url);
}
---------------
foreach (var url in urls_to_download)
{
WebClient MyWebClient = new WebClient();
MyWebClient.DownloadString(url);
}
Первый выделяет один экземпляр WebClient
, который используется для всех запросов.Второй выделяет один WebClient
для каждого запроса.Разница огромная.WebClient
использует много системных ресурсов, и выделение тысяч из них за относительно короткое время повлияет на производительность.Поверь мне ... Я столкнулся с этим.Лучше выделять всего 10 или 20 WebClient
с (столько, сколько нужно для параллельной обработки), чем выделять по одному на запрос.