2012-01-15 20 views
6

ho bisogno di scaricare circa 2 milioni di file dal sito web SEC. ogni file ha un URL univoco ed è in media 10kB. questo è il mio attuale implementazione:un modo più veloce per scaricare più file

List<string> urls = new List<string>(); 
    // ... initialize urls ... 
    WebBrowser browser = new WebBrowser(); 
    foreach (string url in urls) 
    { 
     browser.Navigate(url); 
     while (browser.ReadyState != WebBrowserReadyState.Complete) Application.DoEvents(); 
     StreamReader sr = new StreamReader(browser.DocumentStream); 
     StreamWriter sw = new StreamWriter(), url.Substring(url.LastIndexOf('/'))); 
     sw.Write(sr.ReadToEnd()); 
     sr.Close(); 
     sw.Close(); 
    } 

il tempo previsto è circa 12 giorni ... c'è un modo più veloce?

Edit: btw, la gestione file locale richiede solo il 7% del tempo

Edit: questa è la mia realizzazione finale:

void Main(void) 
    { 
     ServicePointManager.DefaultConnectionLimit = 10000; 
     List<string> urls = new List<string>(); 
     // ... initialize urls ... 
     int retries = urls.AsParallel().WithDegreeOfParallelism(8).Sum(arg => downloadFile(arg)); 
    } 

    public int downloadFile(string url) 
    { 
     int retries = 0; 

     retry: 
     try 
     { 
      HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(url); 
      webrequest.Timeout = 10000; 
      webrequest.ReadWriteTimeout = 10000; 
      webrequest.Proxy = null; 
      webrequest.KeepAlive = false; 
      webresponse = (HttpWebResponse)webrequest.GetResponse(); 

      using (Stream sr = webrequest.GetResponse().GetResponseStream()) 
      using (FileStream sw = File.Create(url.Substring(url.LastIndexOf('/')))) 
      { 
       sr.CopyTo(sw); 
      } 
     } 

     catch (Exception ee) 
     { 
      if (ee.Message != "The remote server returned an error: (404) Not Found." && ee.Message != "The remote server returned an error: (403) Forbidden.") 
      { 
       if (ee.Message.StartsWith("The operation has timed out") || ee.Message == "Unable to connect to the remote server" || ee.Message.StartsWith("The request was aborted: ") || ee.Message.StartsWith("Unable to read data from the trans­port con­nec­tion: ") || ee.Message == "The remote server returned an error: (408) Request Timeout.") retries++; 
       else MessageBox.Show(ee.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 
       goto retry; 
      } 
     } 

     return retries; 
    } 
+0

Questi file non possono essere combinati in un archivio e scaricati in un'unità? – Oded

+0

sfortunatamente no. – eyaler

+0

Qualsiasi motivo si sta utilizzando un controllo del browser invece di un 'WebRequest'? – CodesInChaos

risposta

11

eseguire il download contemporaneamente invece che in sequenza, e impostare un MaxDegreeOfParallelism ragionevole altrimenti si cercherà di fare troppi contestuale richiesta, che sarà simile a un attacco DOS:

public static void Main(string[] args) 
    { 
     var urls = new List<string>(); 
     Parallel.ForEach(
      urls, 
      new ParallelOptions{MaxDegreeOfParallelism = 10}, 
      DownloadFile); 
    } 

    public static void DownloadFile(string url) 
    { 
     using(var sr = new StreamReader(HttpWebRequest.Create(url).GetResponse().GetResponseStream())) 
     using(var sw = new StreamWriter(url.Substring(url.LastIndexOf('/')))) 
     { 
      sw.Write(sr.ReadToEnd()); 
     } 
    } 
+1

sembra molto dubbioso per me. Stai utilizzando un'istanza condivisa di browser da più thread. E chiamare 'Application.DoEvents' da un altro thread probabilmente è anche sbagliato. – CodesInChaos

+0

@CodeInChaos, d'accordo, mi sono concentrato sul parallelismo senza considerare l'implementazione del download. fix .. –

+1

.. ora corretto, sostituito il controllo del browser con HttpWebRequest –

6

Scaricare file in diversi thread. Il numero di thread dipende dalla velocità effettiva. Inoltre, guarda le classi WebClient e HttpWebRequest. campione Semplice:

var list = new[] 
{ 
    "http://google.com", 
    "http://yahoo.com", 
    "http://stackoverflow.com" 
}; 

var tasks = Parallel.ForEach(list, 
     s => 
     { 
      using (var client = new WebClient()) 
      { 
       Console.WriteLine("starting to download {0}", s); 
       string result = client.DownloadString((string)s); 
       Console.WriteLine("finished downloading {0}", s); 
      } 
     }); 
+1

L'unica cosa che manca qui è impostare MaxDegreeOfParallelism. L'OP contiene 2 milioni di file, quindi senza di esso il suddetto accoderà 2 milioni di elementi di lavoro e renderà più richieste simultanee al server che consentirà e/o gestirà. È meglio limitarlo alle connessioni massime per client del server di destinazione. –

5

I' d utilizzare più thread in parallelo, con uno WebClient. Raccomando di impostare il grado massimo di parallelismo sul numero di thread che si desidera, poiché il grado non specificato di parallelismo non funziona bene per le attività di lunga durata. Ho usato 50 download paralleli in uno dei miei progetti senza problemi, ma a seconda della velocità di un singolo download potrebbe essere sufficiente un livello molto più basso.

Se si scaricano più file in parallelo dallo stesso server, l'impostazione predefinita è limitata a un numero limitato (2 o 4) di download paralleli. Mentre lo standard http specifica un limite così basso, molti server non lo applicano. Utilizzare ServicePointManager.DefaultConnectionLimit = 10000; per aumentare il limite.

+0

infatti ServicePointManager.DefaultConnectionLimit = 10000; si è rivelata fondamentale per ottenere aumenti di velocità superiori a 2 – eyaler

Problemi correlati