Web浏览器控制文档完成iframe& Javascript完成

时间:2013-10-15 01:27:26

标签: c# .net winforms web-scraping webbrowser-control

我需要捕获生成的HTML图像。我在这里使用Alex Filipovici的优秀解决方案:Convert HTML string to image。除非我正在尝试加载包含使用某些Javascript加载的iframe的页面,否则它的效果很好。

        static int width = 1024;
        static int height = 768;

        public static void Capture()
        {
            var html = @"
<!DOCTYPE html>
<meta http-equiv='X-UA-Compatible' content='IE=Edge'>
<html>
<iframe id='forecast_embed' type='text/html' frameborder='0' height='245' width='100%' src='http://forecast.io/embed/#lat=42.3583&lon=-71.0603&name=Downtown Boston'> </iframe>
</html>
";
            StartBrowser(html);
        }

        private static void StartBrowser(string source)
        {
            var th = new Thread(() =>
            {
                var webBrowser = new WebBrowser();
                webBrowser.Width = width;
                webBrowser.Height = height;
                webBrowser.ScrollBarsEnabled = false;
                webBrowser.DocumentCompleted += webBrowser_DocumentCompleted;
                webBrowser.DocumentText = source;
                Application.Run();
            });
            th.SetApartmentState(ApartmentState.STA);
            th.Start();
        }

        static void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            var webBrowser = (WebBrowser)sender;
            using (Bitmap bitmap = new Bitmap(width, height))
            {
                webBrowser.DrawToBitmap(bitmap, new System.Drawing.Rectangle(0, 0, width, height));
                bitmap.Save(@"image.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
            }
            Application.Exit();
        }

据我所知,可能没有明确的方法可以知道所有的javascript是否已经结束以及iframe加载的变幻莫测以及DocumentCompleted被调用的事实多次,因为有帧/ iframes + 1.我可以处理iframe加载使用计数器或其他东西,但我想要的是一个合理的延迟,所以加载了javascript,我没有得到像这样的“正在加载”的图像:http://imgur.com/FiFMTmm

2 个答案:

答案 0 :(得分:3)

如果您正在处理使用框架和AJAX的动态网页,那么在特定网页完成加载资源时找不到完美的解决方案。你可以通过以下两件事来接近:

  • 处理页面的window.onload事件;
  • 然后异步轮询WebBrowser Busy属性,并预先设定一些相当短的超时时间。

例如,(查看https://stackoverflow.com/a/19283143/1768303以获取完整示例):

const int AJAX_DELAY = 2000; // non-deterministic wait for AJAX dynamic code
const int AJAX_DELAY_STEP = 500;

// wait until webBrowser.Busy == false or timed out
async Task<bool> AjaxDelay(CancellationToken ct, int timeout)
{
    using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ct))
    {
        cts.CancelAfter(timeout);
        while (true)
        {
            try
            {
                await Task.Delay(AJAX_DELAY_STEP, cts.Token);
                var busy = (bool)this.webBrowser.ActiveXInstance.GetType().InvokeMember("Busy", System.Reflection.BindingFlags.GetProperty, null, this.webBrowser.ActiveXInstance, new object[] { });
                if (!busy)
                    return true;
            }
            catch (OperationCanceledException)
            {
                if (cts.IsCancellationRequested && !ct.IsCancellationRequested)
                    return false;
                throw;
            }
        }
    }
}

如果您不想使用async/await,可以使用计时器实现相同的逻辑。

答案 1 :(得分:0)

以下是我经常使用的各种其他想法,这些想法最终变得复杂并且有竞争条件或需要.Net 4.5(例如这个问题的答案)。

诀窍是在每个DocumentCompleted上重新启动秒表并等到某个阈值内没有完成任何文档。

为了更容易使用,我加入了一个扩展方法:

browser.NavigateAndWaitUntilComplete(uri);

我应该称之为NavigateUntilProbablyComplete()。这种方法的缺点是每个导航都有250毫秒的罚款。我看到的许多解决方案都依赖于最终页面与我的场景中无法保证的URL相同。

using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

namespace MyProject.Extensions
{
    public static class WebBrowserExtensions
    {
        const int CompletionDelay = 250;

        private class WebBrowserCompletionHelper
        {
            public Stopwatch LastCompletion;

            public WebBrowserCompletionHelper()
            {
                // create but don't start.
                LastCompletion = new Stopwatch();
            }

            public void DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
            {
                WebBrowser browser = sender as WebBrowser;
                if (browser != null)
                {
                    LastCompletion.Restart();
                }
            }
        }

        public static void NavigateAndWaitUntilComplete(this WebBrowser browser, Uri uri)
        {
            WebBrowserCompletionHelper helper = new WebBrowserCompletionHelper();
            try
            {
                browser.DocumentCompleted += helper.DocumentCompleted;
                browser.Navigate(uri);

                Thread.Sleep(CompletionDelay);
                Application.DoEvents();

                while (browser.ReadyState != WebBrowserReadyState.Complete && helper.LastCompletion.ElapsedMilliseconds < CompletionDelay)
                {
                    Thread.Sleep(CompletionDelay);
                    Application.DoEvents();
                }
            }
            finally
            {
                browser.DocumentCompleted -= helper.DocumentCompleted;
            }
        }
    }
}