【问题标题】:c# WebBrowser- How can I wait for javascript to finish running that runs when the document has finished loading?c#WebBrowser-如何等待文档完成加载后运行的javascript完成运行?
【发布时间】:2014-01-08 16:54:24
【问题描述】:

我正在从事一个项目,该项目涉及从供应商的网站上抓取一些产品数据(在他们的祝福下,但没有他们的帮助)。我在一家 C# 商店工作,所以我使用的是 .NET Windows Forms WebBrowser 控件。

我正在响应文档完成事件,但我发现我必须让线程休眠一段时间,否则数据不会显示在 DOM 中我期望的位置。

在查看页面上的 javascript 时,我可以看到它在页面完成加载后动态更改现有的 DOM 内容(设置 someDomElement.innerHTML)。它没有进行任何 ajax 调用,它使用的是原始页面加载中已有的数据。 (我可以尝试解析该数据,但它嵌入在 javascript 中,并且有点混淆。)所以很明显,在 javascript 完成运行之前,我以某种方式获取了文档完成事件。

最终可能会有很多页面要抓取,因此请等待半秒或其他任何不太理想的情况。在我检查页面之前,我只想等到所有在文档准备好/页面加载时启动的 JavaScript 完成运行。有谁知道这样做的方法吗?

我想直到那时文档完成事件才应该触发,对吧?但它肯定似乎是。也许某处页面 javascript 正在使用 setTimeout。有没有办法判断是否有任何超时未决?

感谢您的帮助!

【问题讨论】:

  • DocumentComplete 在页面完全加载并且 dom 由浏览器呈现时触发。正是很多 js 等待初始化页面脚本的信号。从浏览器的角度来看,脚本永远不会停止运行,因此无法获得脚本完成事件。我猜你只需要轮询 dom 直到它处于你期望的状态。
  • 好点很多 js 等待文档准备好,我自己写了一堆。确实,事件处理程序仍在发挥作用,但似乎可能有一个忙标志(当前正忙于实际执行 js),以及发出的 xhtml 请求计数和收到的响应计数。
  • 我正在创建我的第一个 WebBrowser/ConsoleApp,并想知道为什么即使 DocumentComplete 已触发,网页也没有完全加载。这对我有帮助-谢谢。看到这些 cmets 后,我决定等待文档元素可用,如下所示:stackoverflow.com/questions/49350908/…

标签: c# javascript .net webbrowser-control


【解决方案1】:

你可以

  1. 假设数据的解析永远不会改变,请查看 Javascript 如何处理数据并在您的端执行相同操作以在页面加载时立即检索数据
  2. 将 javascript 注入网页并检测 DOM 修改以了解何时从 C# 获取数据
  3. 使用 PhantomJS 编写纯 javascript 解决方案

【讨论】:

  • +1 听起来不错且有用的建议。我维护的这些网络爬虫不断需要注意,每次发生都是pita,因为html dom搜索/遍历/过滤等,特别是需要等待它们的各种js函数完成运行。我不确定您所说的“最后做同样的事情”是什么意思。您是在谈论从 C# 代码中使用他们的服务器端调用吗?另外,我不确定您在 #2 中的建议是什么 - 以某种方式添加 javascript,以某种方式运行它,并让该 javascript 消耗他们的服务器调用?
【解决方案2】:

对于后代/其他任何人来说,我最终做的是创建一个函数,该函数等待某些特定事物(与给定条件匹配的元素)在指定的超时时间内显示在页面上,然后返回它是什么的 HtmlElement。它定期检查浏览器 dom,寻找特定的东西。它旨在由在后台线程中运行的刮板工作人员调用;每次检查时,它都会使用调用来访问浏览器 dom。

    /// <summary>
    /// Waits for a tag that matches a given criteria to show up on the page.
    /// 
    /// Note: This function returns a browser DOM element from the foreground thread, and this scraper is running in a background thread,
    /// so use an invoke [ scraperForm.Browser.Invoke(new Action(()=>{ ... })); ] when doing anything with the returned DOM element.
    /// </summary>
    /// <param name="tagName">The type of tag, or "" if all tags are to be searched.</param>
    /// <param name="id">The id of the tag, or "" if the search is not to be by id.</param>
    /// <paran name="className">The class name of the tag, or "" if the search is not to be by class name.</paran>
    /// <param name="keyContent">A string to search the tag's innerText for.</param>
    /// <returns>The first tag to match the criteria, or null if such a tag was not found after the timeout period.</returns>
    public HtmlElement WaitForTag(string tagName, string id, string className, string keyContent, int timeout) {
        Log(string.Format("WaitForTag('{0}','{1}','{2}','{3}',{4}) --", tagName, id, className, keyContent, timeout));
        HtmlElement result = null;
        int timeleft = timeout;
        while (timeleft > 0) {
            //Log("time left: " + timeleft);
            // Access the DOM in the foreground thread using an Invoke call.
            // (required by the WebBrowser control, otherwise cryptic errors result, like "invalid cast")
            scraperForm.Browser.Invoke(new Action(() => {
                HtmlDocument doc = scraperForm.CurrentDocument;
                if (id == "") {
                    //Log("no id supplied..");
                    // no id was supplied, so get tags by tag name if a tag name was supplied, or get all the tags
                    HtmlElementCollection elements = (tagName == "") ? doc.All : doc.GetElementsByTagName(tagName);
                    //Log("found " + elements.Count + " '" + tagName + "' tags");
                    // find the tag that matches the class name (if given) and contains the given content (if any)
                    foreach (HtmlElement element in elements) {
                        if (element == null) continue;
                        if (className != "" && !TagHasClass(element, className)) {
                            //Log(string.Format("looking for className {0}, found {1}", className, element.GetAttribute("className")));
                            continue;
                        }
                        if (keyContent == "" || 
                            (element.InnerText != null && element.InnerText.Contains(keyContent)) ||
                            (tagName == "input" && element.GetAttribute("value").Contains(keyContent)) ||
                            (tagName == "img" && element.GetAttribute("src").Contains(keyContent)) || 
                            (element.OuterHtml.Contains(keyContent)))
                        {
                            result = element;
                        }
                        else if (keyContent != "") {
                            //Log(string.Format("searching for key content '{0}' - found '{1}'", keyContent, element.InnerText));
                        }
                    }
                }
                else {
                    //Log(string.Format("searching for tag by id '{0}'", id));
                    // an id was supplied, so get the tag by id 
                    // Log("looking for element with id [" + id + "]");
                    HtmlElement element = doc.GetElementById(id);
                    // make sure it matches any given class name and contains any given content
                    if (
                        element != null 
                        && 
                        (className == "" || TagHasClass(element, className))
                        && 
                        (keyContent == "" || 
                            (element.InnerText != null && element.InnerText.Contains(keyContent))
                        )
                    ) {
                        // Log("  found it");
                        result = element;
                    }
                    else {
                        // Log("  didn't find it");
                    }
                }
            }));
            if (result != null) break;   // the searched for tag appeared, break out of the loop 
            Thread.Sleep(200);           // wait for more milliseconds and continue looping 
            // Note: Make sure sleeps like this are outside of invokes to the foreground thread, so they only pause this background thread.
            timeleft -= 200;
        }
        return result;
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-19
    • 1970-01-01
    • 1970-01-01
    • 2018-11-07
    • 1970-01-01
    相关资源
    最近更新 更多