主线程上单独的线程引发事件中的 C# .NET Web 浏览器控件

本文关键字:线程 NET Web 浏览器 控件 单独 事件 | 更新日期: 2023-09-27 18:31:36

我需要在paralel中抓取很多页面,而我的UI线程不能被阻止。我正在为每个页面(url)创建线程,并在该线程中实例化webBrowser控件以执行javascript并在此之后获取html。当 webBrowser 获取 html 时,我在 UI 线程上引发事件以注册浏览器已完成其工作,因为我想知道所有浏览器何时都获取了 html,以便我可以合并所有数据并显示它。

1.)第一个探测是,有些线程永远不会引发事件,所以我一直在等待。

2.)第二个问题是我无法在不导致外部浏览器触发的情况下处理浏览器,一直拉着浏览器下方的地毯,所以他决定通过在用户默认浏览器中打开页面来继续我猜。但如果根本不处理,我的公羊就用完了。

我一直在搜索,找到了很多相关的东西,但我未能为我的用例实现它。这是我的代码:

 [System.Runtime.InteropServices.ComVisibleAttribute(true)]
    public partial class Form1 : Form
    {
        public delegate void ThreadFinishedEventHandler(object source, EventArgs e);
        public event ThreadFinishedEventHandler threadFinishedEvent;
        int threadCount = 0;
        int threadReturnedCount = 0;
        List<string> linksGlobal;
        public Form1()
        {
            InitializeComponent();
            threadFinishedEvent += new ThreadFinishedEventHandler(OnThreadFinished); 
        }
        private void Form1_Load(object sender, EventArgs e)
        {
        }
        private void btnGO_Click(object sender, EventArgs e)
        {
            scrapeLinksWithBrowsersInSeparateThreads();
        }

        private void scrapeLinksWithBrowsersInSeparateThreads()
        {
            linksGlobal = getLinks(); //10 urls all the same -> https://sports.betway.com
            threadCount = linksGlobal.Count;
            Random rand = new Random(123);
            int waitTime = 0;//trying not to be registered as DOS attack or smth
            foreach (string url in linksGlobal)
            {
                runBrowserThread(url, waitTime);
                waitTime += rand.Next(500, 3000) + 500;//each browser will start navigating withing 1 - 4 seconds interval from each other
            }
        }

        public void runBrowserThread(string url, int waitTime)
        {
            var th = new Thread(() =>
            {
                try
                {
                    WebBrowserDocumentCompletedEventHandler completed = null;
                    WebBrowser wb = new WebBrowser();
                    completed = (sndr, e) =>
                    {
                        if (e.Url.AbsolutePath != (sndr as WebBrowser).Url.AbsolutePath)
                        {
                            wb.DocumentCompleted -= completed;
                            string html = (sndr as WebBrowser).Document.Body.InnerHtml;
                            threadFinishedEvent.Raise(this, EventArgs.Empty); // I have EventExtension allowing me this
                            //wb.Dispose(); //whenever and wherever I put this it causes external browser to fire
                            // Application.ExitThread();  //this sometimes seems to cause event never firing, not shure
                        }
                    };
                    wb.DocumentCompleted += completed;
                    wb.ScriptErrorsSuppressed = true;
                    Thread.Sleep(waitTime); //tryin not to get registerd as DOS attck or smth, each browser will start navigating withing 1 - 4 seconds interval from each other
                    wb.Navigate(url);
                    Application.Run();
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            });
            th.SetApartmentState(ApartmentState.STA);
            th.Start();
        }

        private void OnThreadFinished(object source, EventArgs e)
        {
            threadReturnedCount++; // i get this for smth like 3 - 5 out od 11 threads, then this event stops being raised, dunno why
            if (threadReturnedCount == threadCount)
            {
                // Do work
                //this never happens cos a lot of threads never raise event, some do
            }
        }

        private List<string> getLinks()
        {
            List<string> links = new List<string>();
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            links.Add("https://sports.betway.com");
            return links;
        }
      }

附言从线程返回数据是单独的问题,我还没有实现它,但首先我想解决这个问题。我将使用对象工厂,它将像Factory.createObject(html)一样从每个线程调用,我将不得不在该工厂上使用某种锁定,因为它将位于主线程上。

主线程上单独的线程引发事件中的 C# .NET Web 浏览器控件

我没有设法为我的问题找到干净的解决方案。我确实尝试了一些东西,我确实得到了一些结果,但这还不够好。我将回顾我的问题并解释我最终为解决我的问题做了什么。

1.)第一个探测是,有些线程永远不会引发事件,所以我一直在等待。

答案1:仍然不知道这里发生了什么,但是在我(有点)解决了第二个问题之后,情况好多

2.)第二个问题是我无法在不导致外部浏览器触发的情况下处理浏览器

答案 2:现在这可以通过使用 Web 浏览器控件的 ActiveXInstance 来完成,您需要将 SHDocvW dll 包含在您的项目中。在这里查看Frank_FC的答案检测 Web 浏览器完成页面加载

WebBrowser控件也存在内存泄漏问题。使用谷歌,我发现了如何减少这个问题(有很多关于这个问题的信息)。

最后,整个事情不是很稳定,发生了内存泄漏,我会摆脱内存异常,不可预测的行为,性能不佳(页面加载缓慢)等。代码很丑陋,一切似乎都只是......不是正确的做事方式。如果您想在短时间内抓取大量页面,请不要使用 webBrowser 控件。不要在自己的头中实例化数十个不可见的 webBrowser 控件,并期望有效地处理所有事件。

我最后做了什么?和我的朋友一起喝啤酒,他向我展示了他作为大学任务制作的程序。在Eclipse中开发的Java程序使用JSoup包来抓取Web。Java中的2个函数,每个函数10-20行代码,他得到的解决方案比我快100倍,更简单,更好的解决方案。你只是说getHtml(url)和JSoup为你得到它,不管页面运行javascript还是任何东西,疯狂。

所以现在我的 .NET 应用程序正在触发 java 应用程序,该应用程序在磁盘上的文本文件中写入 html,完成后 .NET 应用程序收集数据,一遍又一遍地循环。

花了100 +小时摆弄WebBrowser控件,然后在2小时内制定了不可估量的更好的解决方案。明智地选择您的工具!Java + Eclipse + JSoup 似乎比 .NET 更好的抓取/爬网方式