如何知道我在并行调用时访问了哪些链接?

时间:2018-01-10 19:22:05

标签: c# web-scraping console-application

我正在建立一个网络抓取项目。

我有两个清单:

    private ConcurrentQueue<string> links = new ConcurrentQueue<string>();
    private ConcurrentQueue<string> Visitedlinks = new ConcurrentQueue<string>();

对于我在页面上找到的所有链接以及将保留我已废弃的所有链接的链接。

处理业务的方法:

    public async Task GetUrlContent(string url)
    {
        var page = string.Empty;

        try
        {
            page = await service.Get(url);

            if (page != string.Empty)
            {
                Regex regex = new Regex(@"<a[^>]*?href\s*=\s*[""']?([^'"" >]+?)[ '""][^>]*?>",
                    RegexOptions.Singleline | RegexOptions.CultureInvariant);

                if (regex.IsMatch(page))
                {
                    Console.WriteLine("Downloading url: " + url);
                    for (int i = 0; i < regex.Matches(page).Count; i++)
                    {

                        if (regex.Matches(page)[i].Groups[1].Value.StartsWith("/"))
                        {
                            if (!links.Contains(BaseUrl + regex.Matches(page)[i].Groups[1].Value.ToLower().Replace(".html", "")) &&
                                !Visitedlinks.Contains(BaseUrl + regex.Matches(page)[i].Groups[1].Value.ToLower()))
                            {
                                Uri ValidUri = GetUrl(regex.Matches(page)[i].Groups[1].Value);
                                if (ValidUri != null && HostUrls.Contains(ValidUri.Host))
                                    links.Enqueue(regex.Matches(page)[i].Groups[1].Value.ToLower().Replace(".html", ""));
                                else
                                 links.Enqueue(BaseUrl + regex.Matches(page)[i].Groups[1].Value.ToLower().Replace(".html", ""));
                            }
                        }
                    }

                }

                var results = links.Where(m => !Visitedlinks.Contains(m)); // problkem here, get multiple values

                if (!results.Any())
                {
                    // do nothing
                }

                else
                {
                    Parallel.ForEach(results, new ParallelOptions { MaxDegreeOfParallelism = 4 },
                            webpage =>
                            {
                                if (ValidUrl(webpage))
                                {
                                    if (!Visitedlinks.Contains(webpage))
                                    {
                                        Visitedlinks.Enqueue(webpage);
                                        GetUrlContent(webpage).Wait();
                                    }
                                }

                            });
                }

            }
        }
        catch (Exception e)
        {
            throw;
        }
    }

问题出在这里:

var results = links.Where(m => !Visitedlinks.Contains(m)); 

我可能得到的第一次迭代:

  

Link1,link2,link3,link4,

第二次迭代:

  

Link2 link3 link4,link5,link6,link 7

第三

  

链接3,链接4,链接5,链接6等

这意味着我将多次获得相同的链接,因为这是一个并行的foreach,它可以同时执行多个操作。我无法弄清楚如何确保我没有获得多个值。

任何可以伸出援助之手的人?

2 个答案:

答案 0 :(得分:2)

如果我理解正确,第一个队列包含你想要抓取的链接,第二个队列包含你已经抓取的链接。

问题在于您是否试图迭代ConcurrentQueue的内容:

var results = links.Where(m => !Visitedlinks.Contains(m));

如果您从多个线程访问这些队列,这将无法预测。

您应该做的是从项目中取出项目并处理它们。最突出的是TryDequeue没有出现在您的代码中的任何位置。物品进入队列但从未出现。队列的整个目的是我们把东西放进去拿出来。 ConcurrentQueue使多个线程可以放置项目并将其取出而不会相互踩踏。

如果您要将要处理的链接出列:

string linkToProcess = null;
if(links.TryDequeue(out linkToProcess)) // if this returns false, the queue was empty
{
     // process it
}

然后,只要您从队列中取出一个项目来处理它,它就不再在队列中了。其他线程不必检查项目是否已被处理。他们只是将下一个项目从队列中取出(如果有的话)。两个线程不会从队列中取出相同的项目。只有一个线程可以将一个给定的项目从队列中取出,因为只要它一个,该项目就不再在队列中。

答案 1 :(得分:0)

感谢@Scott Hannen

最终解决方案如下:

        Parallel.ForEach(links, new ParallelOptions { MaxDegreeOfParallelism = 25 },
            webpage =>
            {
                try
                {
                    if (WebPageValidator.ValidUrl(webpage))
                    {
                        string linkToProcess = webpage;
                        if (links.TryDequeue(out linkToProcess) && !Visitedlinks.Contains(linkToProcess))
                        {
                            Task obj = Scrape(linkToProcess);
                            Visitedlinks.Enqueue(linkToProcess);
                        }
                    }
                }
                catch (Exception e)
                {
                    log.Error("Error occured: " + e.Message);
                    Console.WriteLine("Error occured, check log for further details.");
                }