Il modo in cui affronta questo problema dipenderà molto dal numero di pagine che desideri scaricare e da quanti siti stai facendo riferimento.
Userò un numero di round buono come 1,000. Se si desidera scaricare più pagine da un singolo sito, ci vorrà molto più tempo rispetto a quando si desidera scaricare 1.000 pagine distribuite su dozzine o centinaia di siti. Il motivo è che se colpisci un singolo sito con un sacco di richieste simultanee, probabilmente finirai per essere bloccato.
Quindi è necessario implementare un tipo di "politica di cortesia" che emette un ritardo tra più richieste su un singolo sito. La durata di questo ritardo dipende da un numero di cose. Se il file robots.txt del sito ha una voce crawl-delay
, dovresti rispettarlo. Se non vogliono che tu acceda a più di una pagina al minuto, allora è veloce come dovresti eseguire la scansione. Se non c'è crawl-delay
, dovresti basare il tuo ritardo su quanto tempo impiega un sito a rispondere. Ad esempio, se puoi scaricare una pagina dal sito in 500 millisecondi, imposta il ritardo su X. Se impiega un secondo intero, imposta il ritardo su 2X. Probabilmente è possibile limitare il ritardo a 60 secondi (a meno che lo crawl-delay
non sia più lungo) e si consiglia di impostare un ritardo minimo compreso tra 5 e 10 secondi.
Non mi consiglia di utilizzare Parallel.ForEach
per questo. I miei test hanno dimostrato che non fa un buon lavoro. A volte esagerare la connessione e spesso non consente un numero sufficiente di connessioni simultanee. Vorrei invece creare una coda di WebClient
istanze e scrivere qualcosa come:
// 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, ...);
}
Quando si inizializza le WebClient
istanze che vanno in coda, impostare le loro OnDownloadStringCompleted
gestori di eventi per puntare a un gestore di eventi completata. Quel gestore deve salvare la stringa in un file (o forse dovresti semplicemente usare DownloadFileAsync
), quindi il client, si aggiunge nuovamente allo ClientQueue
.
Nel mio test, sono stato in grado di supportare da 10 a 15 connessioni simultanee con questo metodo. Più di questo e mi imbatto in problemi con la risoluzione DNS (`DownloadStringAsync 'non esegue la risoluzione DNS in modo asincrono). Puoi ottenere più connessioni, ma farlo è molto lavoro.
Questo è l'approccio che ho adottato in passato e ha funzionato molto bene per scaricare rapidamente migliaia di pagine. Non è sicuramente l'approccio che ho seguito con il mio crawler Web ad alte prestazioni.
Vorrei anche notare che c'è una differenza enorme nell'uso delle risorse tra questi due blocchi di codice:
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);
}
La prima assegna un unico WebClient
istanza che viene utilizzato per tutte le richieste. Il secondo assegna uno WebClient
per ogni richiesta. La differenza è enorme. WebClient
utilizza molte risorse di sistema e l'allocazione di migliaia di esse in un tempo relativamente breve inciderà sulle prestazioni. Credimi ... mi sono imbattuto in questo. È meglio allocare solo 10 o 20 WebClient
s (tanti quanti ne occorrono per l'elaborazione simultanea), piuttosto che assegnarne uno per richiesta.
È necessaria una connessione T1 –
Dal momento che molte risposte sono suggerendo il recupero parallelo, voglio guardia contro l'invio di troppe richieste simultanee; potresti essere bannato se il sito web non è amichevole. Inoltre, ci sarà un limite a quanto ogni thread aggiuntivo aiuti e oltre un punto che causerà il degrado. –
@Hemal Pandya: una preoccupazione valida, che non è * quella * molto di una preoccupazione; la classe 'WebClient' userà infine le classi' HttpWebRequest'/'HttpWebResponse' che usano la classe' ServicePointManager'. Il 'ServicePointManager' per impostazione predefinita limiterà la maggior parte dei download a due alla volta per un dominio particolare (come da raccomandazione nella specifica HTTP 1.1). – casperOne