我正在尝试创建一个多线程Web scraper。为此我使用Parallel.For.Here是我的代码:
string source = "http://www.lolsummoners.com/ladders/eune/";
string regex_Search = "leagues/.*>(.*)</a></td>";
List<string> user_List = new List<string>();
int page_Begin = 1;
int page_End = 10;
ParallelOptions p_Option = new ParallelOptions();
p_Option.MaxDegreeOfParallelism = 3;
Parallel.For(page_Begin, page_End, p_Option, index =>
{
try
{
WebClient web = new WebClient();
web.Encoding = Encoding.UTF8;
String html = web.DownloadString(source + page_Begin);
MatchCollection matches = Regex.Matches(html, @regex_Search);
foreach(Match match_Find in matches)
{
string user = match_Find.Groups[1].Value.Replace(" ", string.Empty);
user_List.Add(user);
Console.WriteLine(user);
}
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
page_Begin++;
});
Console.ReadKey();
我的问题是,如果我使用多个线程,我会得到重复项。有没有办法解决这个问题?我不需要循环来从同一个网页获取相同的名称,那就是&#39;为什么我在最后增加page_Begin变量。这就是我所说的:
答案 0 :(得分:1)
由于您未使用索引进行当前并行传递,因此您看到了重复项。相反,您使用之前定义的page_Begin
。所以在两次传递期间至少有两个线程,两者都将开始在1
下载!随着增量,后续传递将下载重复。
更改此行:
String html = web.DownloadString(source + page_Begin);
...至
String html = web.DownloadString(source + index );
List<>
不是线程安全的,因此您可能希望使用来自TPL的良好的线程安全集合,例如ConcurrentBag<>
。
MSDN:
袋子对于存储物品非常有用,当订购无关紧要时,与套装不同,袋子支持重复。 ConcurrentBag是一个线程安全的包实现,针对同一个线程生成和使用存储在包中的数据的场景进行了优化。
System.Collections.Concurrent
中定义的集合都非常高效,并且比通常使用lock(object)
编写的常规集合快得多。
改变这个:
List<string> user_List = new List<string>();
...为:
ConcurrentBag<string> user_List = new ConcurrentBag<string>();
现在您可以从任何线程添加到包中。
稍后当您完成线程处理时,可以将其转换为.ToArray()
的数组。