你处理这个问题的方式将在很大程度上取决于你想要下载多少页以及你引用了多少网站。
我将使用一个很好的整数,比如1000。如果你想从一个单一的网站下载那么多页面,时间会比从分布在几十或几百个网站上的1000个页面中下载要长得多。原因是如果你向一个网站发送大量的并发请求,你可能最终会被阻止。
因此,你必须实施一种“礼貌策略”,在单个网站的多个请求之间发布延迟。这个延迟的长度取决于许多因素。如果该站点的robots.txt文件有一个“crawl-delay”条目,你应该尊重它。如果他们不希望你每分钟访问超过一页,那么你爬行的速度就应该是这个快。如果没有“crawl-delay”,你应该根据一个站点响应的时间来确定延迟。例如,如果你可以在500毫秒内从该站点下载一页,那么你将设置你的延迟为X。如果需要一个完整的秒钟,设置你的延迟为2X。你可以将你的延迟限制在60秒(除非“crawl-delay”更长),我建议你设置一个最小延迟为5到10秒。
我不建议在这里使用Parallel.ForEach。我的测试表明它做得不好。有时它会超负荷连接,并且通常它不允许足够的并发连接。我建议创建一个WebClient实例队列,然后编写以下内容:
BlockingCollection<WebClient> ClientQueue = new BlockingCollection<WebClient>();
foreach (var url in urls_to_download)
当您初始化进入队列的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
使用了大量系统资源,在相对较短的时间内分配数千个
WebClient
将影响性能。相信我...我曾经遇到过这种情况。最好只分配10或20个
WebClient
(尽可能多地满足并发处理的需求),而不是为每个请求分配一个
WebClient
。
WebClient
类最终将使用HttpWebRequest
/HttpWebResponse
类,这些类使用ServicePointManager
类。默认情况下,ServicePointManager
将限制特定域的大多数下载为每次两个(根据HTTP 1.1规范的建议)。 - casperOneServicePointManager
,我只是将其与在命令行上发出一堆wget ... &
进行了比较。我不知道 HTTP 1.1 推荐,但在这个时代似乎太少了。在我看来,OP 可能会想要覆盖它。 - Miserable Variable