2012-01-15 21 views
6

necesito descargar unos 2 millones de archivos del sitio web de la SEC. cada archivo tiene una url única y tiene un promedio de 10kB. esta es mi implementación actual:una forma más rápida de descargar varios archivos

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(); 
    } 

el tiempo proyectado es de aproximadamente 12 días ... ¿hay una manera más rápida?

Editar: por cierto, el manejo de archivos local toma sólo el 7% de las veces

Editar: esta es mi implementación final:

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

¿No se pueden combinar estos archivos en un archivo y descargarlos en una unidad? – Oded

+0

desafortunadamente no. – eyaler

+0

¿Alguna razón por la que está utilizando un control de navegador en lugar de un 'WebRequest'? – CodesInChaos

Respuesta

11

ejecutar las descargas al mismo tiempo en lugar de secuencialmente, y establecer un MaxDegreeOfParallelism sensata de lo contrario se tratará de hacer demasiados solicitud simultánea, que se verá como un ataque 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

me parece muy dudoso. Está utilizando una instancia compartida de navegador de múltiples hilos. Y llamar 'Application.DoEvents' desde otro hilo probablemente también sea incorrecto. – CodesInChaos

+0

@CodeInChaos, de acuerdo, me centré en el paralelismo sin tener en cuenta la implementación de la descarga. va a arreglar .. –

+1

... ahora reparado, reemplazado control del navegador con HttpWebRequest –

6

Descarga de archivos en varios hilos. El número de subprocesos depende de su rendimiento. Además, mira las clases WebClient y HttpWebRequest. muestra simple:

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

Lo único que falta aquí es establecer el MaxDegreeOfParallelism. El OP declara 2 millones de archivos, de modo que sin él, lo anterior pondrá en cola 2 millones de elementos de trabajo y hará más solicitudes simultáneas al servidor que permitirá y/o manejará. Lo mejor es acelerarlo a las conexiones máximas por cliente del servidor de destino. –

5

I' d use varios hilos en paralelo, con un WebClient. Recomiendo establecer el máximo grado de paralelismo con el número de subprocesos que desee, ya que el grado de paralelismo no especificado no funciona bien para las tareas de larga ejecución. He usado 50 descargas paralelas en uno de mis proyectos sin ningún problema, pero dependiendo de la velocidad de una descarga individual, una cantidad mucho más baja podría ser suficiente.

Si descarga varios archivos en paralelo desde el mismo servidor, está limitado de forma predeterminada a un pequeño número (2 o 4) de descargas paralelas. Si bien el estándar http especifica un límite tan bajo, muchos servidores no lo hacen cumplir. Use ServicePointManager.DefaultConnectionLimit = 10000; para aumentar el límite.

+0

de hecho ServicePointManager.DefaultConnectionLimit = 10000; resultó ser crítico para obtener aceleraciones superiores a 2 – eyaler

Cuestiones relacionadas