首页 > 解决方案 > 我可以使用多线程和并行编程进行网络抓取吗?

问题描述

我很难理解多线程和并行编程。我有一个小应用程序(Scraper)。我将 Selenium 与 C# .NET 一起使用。我有一个包含企业地址的文件。然后我用我的爬虫来查找公司名称和他们的网站。之后,我根据他们的公司网站再次抓取通用电子邮件地址

这是问题所在。如果我手动执行此操作,我需要 3 年时间才能完成 50,000 条记录。我做了数学。哈哈。这就是我创建刮板的原因。一个普通的控制台应用程序需要 5 到 6 天才能完成。然后,我决定也许使用多线程和并行编程可以减少时间。

所以,我做了一个小样本测试。我注意到 1 条记录需要 10 秒。完成。然后有 10 条记录需要 100 秒。我的问题是为什么多线程需要同样的时间?

我不确定我对多线程的期望和理解是否错误。我想通过使用Parallel.ForEach将启动所有十个记录并在 10 秒内完成,为我节省了 90 秒。这是正确的假设吗?有人可以澄清一下多线程和并行编程的实际工作原理吗?

private static List<GoogleList> MultiTreadMain(List<FileStructure> values)
{
        List<GoogleList> ListGInfo = new List<GoogleList>();
        var threads = new List<Thread>();
        Parallel.ForEach (values, value =>
        {
            if (value.ID <= 10)
            {
                List<GoogleList> SingleListGInfo = new List<GoogleList>();
                var threadDesc = new Thread(() =>
                {
                   lock (lockObjDec)
                   {
                      SingleListGInfo = LoadBrowser("https://www.google.com", value.Address, value.City, value.State,
                                 value.FirstName, value.LastName,
                                 "USA", value.ZipCode, value.ID);
                        SingleListGInfo.ForEach(p => ListGInfo.Add(p));
                    }
                });
                threadDesc.Name = value.ID.ToString();
                threadDesc.Start();
                threads.Add(threadDesc);

            }
        });

        while (threads.Count > 0)
        {
            for (var x = (threads.Count - 1); x > -1; x--)
            {
                if (((Thread)threads[x]).ThreadState == System.Threading.ThreadState.Stopped)
                {
                    ((Thread)threads[x]).Abort();
                    threads.RemoveAt(x);
                }
            }
            Thread.Sleep(1);
        }
     

       return ListGInfo;
}

标签: c#multithreadingseleniumweb-scrapingparallel-processing

解决方案


这可能不是您所面临的特定问题的答案,但它可能暗示了一般问题“为什么多线程不是更快”。假设 Selenium 有一个公共类EdgeDriver,它是这样实现的:

public class EdgeDriver
{
    private static object _locker = new();

    public void GoToUrl(string url)
    {
        lock (_locker)
        {
            GoToUrlInternal(url);
        }
    }

    internal void GoToUrlInternal(string url) //...
}

作为该类的消费者,您看不到私有_locker字段或内部方法。这些是对您隐藏的实现细节,而了解这个类在做什么的唯一方法是阅读文档。因此,如果实现看起来像上面人为设计的示例,那么任何通过创建多个实例并在循环中EdgeDriver调用它们的GoToUrl方法来加速程序的尝试都是徒劳的。Parallel.ForEach静态对象上的lock将确保一次只允许一个线程调用GoToUrlInternal,而所有其他线程都必须等待轮到它们。这称为“调用被序列化”。这只是多线程可能不比在单线程上运行的代码快的众多可能原因之一。


推荐阅读