【问题标题】:a faster way to download multiple files下载多个文件的更快方法
【发布时间】:2012-02-10 19:00:49
【问题描述】:

我需要从 SEC 网站下载大约 200 万个文件。每个文件都有一个唯一的 url,平均为 10kB。这是我目前的实现:

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

预计时间约为 12 天...有更快的方法吗?

编辑:顺便说一句,本地文件处理只需要 7% 的时间

编辑:这是我的最终实现:

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

【问题讨论】:

  • 这些文件是不是不能合并成一个存档,在一个单元中下载?
  • 您使用浏览器控件而不是 WebRequest 的任何原因?
  • @CodeInChaos 原因是我对差异一无所知...
  • @CodeInChaos 我用顺序 WebRequest 测试过,它实际上慢了 30%

标签: c# .net browser


【解决方案1】:

在多个线程中下载文件。线程数取决于您的吞吐量。另外,请查看 WebClientHttpWebRequest 类。简单示例:

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 {s}");
                string result = client.DownloadString((string)s);
                Console.WriteLine($"finished downloading {s}");
            }
        });

【讨论】:

  • 这里唯一缺少的是设置 MaxDegreeOfParallelism。 OP 声明了 200 万个文件,因此如果没有它,上面的文件将排队 200 万个工作项,并向服务器发出更多并发请求,它将允许和/或处理。最好将其限制为目标服务器每个客户端的最大连接数。
【解决方案2】:

同时执行下载而不是顺序执行,并设置一个合理的 MaxDegreeOfParallelism 否则您将尝试同时发出太多请求,这看起来像是 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());
        }
    }

【讨论】:

  • 对我来说看起来很可疑。您正在使用来自多个线程的共享浏览器实例。从另一个线程调用Application.DoEvents 也可能是错误的。
  • @CodeInChaos,同意,我专注于并行性而不考虑下载实现。将修复..
  • ..现在已修复,将浏览器控件替换为 HttpWebRequest
  • 谢谢,我可以通过这种方法(也使用 ServicePointManager.DefaultConnectionLimit = 10000;)获得 4 倍的加速,我猜这是由于服务器限制。还有什么建议吗?
  • 瓶颈是我怀疑服务器上每个客户端(IP 地址)的并发连接数。如果您知道将 MaxDegreeOfParallelism 设置为匹配的内容,则这不会增加,但会阻止请求等待连接。如果您拥有可以横向扩展的资源,则要获得更高的吞吐量,即在 n 个客户端之间拆分 URL,每个客户端具有不同的 IP 地址以同时运行。
【解决方案3】:

我会并行使用多个线程,WebClient。我建议将最大并行度设置为所需的线程数,因为未指定的并行度不适用于长时间运行的任务。我在我的一个项目中使用了 50 次并行下载没有问题,但根据个人下载的速度,低得多的下载可能就足够了。

如果您从同一台服务器并行下载多个文件,默认情况下您只能进行少量(2 或 4 个)并行下载。虽然 http 标准规定了如此低的限制,但许多服务器并未强制执行。使用ServicePointManager.DefaultConnectionLimit = 10000; 增加限制。

【讨论】:

  • 确实是 ServicePointManager.DefaultConnectionLimit = 10000;事实证明,这对于获得高于 2 的加速比至关重要
猜你喜欢
  • 2022-06-10
  • 1970-01-01
  • 1970-01-01
  • 2018-08-18
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多