2

我正在开发一个网络爬虫,它通常工作得很好。它将浏览大多数网站上的数千个页面,并成功完成,没有任何问题。

在一些网站上,我反复看到同样的问题。

Insufficient memory to continue the execution of the program.

编辑: 我使用 perfmon 来确定泄漏发生在非托管内存中。我知道,因为随着程序运行,“私有字节”不断增加,而所有堆中的字节保持稳定。

(实际上,它上升和下降,但逐渐攀升。它通常在我上面列出的代码部分中耗尽内存,但我不认为该部分是原因,而是可能是第一个受害者,因为它使用了大量内存...我认为它会在之后发布它)


编辑2:

我按照这个网站上的指示:http: //www.codeproject.com/Articles/42721/Best-Practices-No-5-Detecting-NET-application-memo

我使用 debugDiag 来检查程序。

分析数据后,调试诊断告诉我泄漏的原因:

jscript.dll is responsible for 1.10 GBytes worth of outstanding allocations. The following are the top 2 memory consuming functions:



jscript!Parser::GenerateCode+167: 498.19 MBytes worth of outstanding allocations.

jscript!NoRelAlloc::PvAlloc+96: 292.99 MBytes worth of outstanding allocations.

我没有在我的应用程序中引用 jscript.dll,它必须被我正在使用的 Web 浏览器控件使用。

System.Windows.Forms.WebBrowser

这是我的猜测,至少。

我还收到一个消息框,弹出标题为“来自网页的消息”,其内容是“X 行内存不足”。

所以,我想我可以处理 webbrowser 对象并取回我的记忆 - 所以我添加了一个带有以下代码的按钮:

Me.wbMain.Dispose() 'dispose all of thwe web-browsers
frmDebugger.wbDebugMain.Dispose()
Me.WBNewWin.Dispose()

GC.Collect() 'just for the heck of it

所以,在运行了一段时间后,我停止了抓取并点击了我的新按钮......它根本没有任何区别。我正在观看 perfmon 中的总“Private Bytes”,它甚至没有动。

任何想法,任何人?


编辑3:

我尝试了一堆推荐的解决方案,但似乎都没有奏效。

有人建议这可能是由于图像未从缓存中清除,但我禁用了图像加载,所以我知道这不是问题。

我也听说IE7有问题,升级到IE8就可以解决。我有IE8,它仍然泄漏内存。

有人建议使用 webbrowser 控件最小化表单会释放一些内存。我试过了,它没有任何区别。

我还被告知我不应该期望内存使用量会下降,因为我将不得不等待垃圾收集器。这不是托管代码中的泄漏,因此 GC.Collect() 不会做任何事情。它在非托管内存中。显然,javascript 功能使用不同的内存,并且没有手动方式来强制收集。但是它已经到了崩溃的地步,所以显然存在问题。

我在这个问题上增加了 50 的赏金,我会将它奖励给任何帮助我解决泄漏的人。我想尝试这个解决方案:http: //www.codeproject.com/Questions/322884/WPF-WebBrowser-control-vs-Internet-Explorer-browse 但我无法弄清楚 vb.net 的等效项是什么。我尝试过在线转换器,但在转换此代码时它们会出错(尽管它们对于我过去转换的其他代码工作正常)

如果我无法解决泄漏,我会将它奖励给将我上面提到的页面从 c# 转换为 vb.net 的任何人。

我的后备计划是创建一个仅包含 webbrowser 的单独应用程序,并与该进程通信,直到它内存不足,此时我将重新启动它(当我完全关闭我的应用程序时,内存被释放)。这对于我的应用程序来说远非理想,因为网络浏览器非常紧密地融入了我的项目。


编辑 4

我尝试实施建议的 javascript 注入 - 这是我的代码:

(我在导航到新页面之前触发它)

Public Shared Sub Clean_JS(ByRef wb As System.Windows.Forms.WebBrowser)

        Dim args As Object() = {"document.body"}

        Dim head As HtmlElement = wb.Document.GetElementsByTagName("head")(0)

        Dim scriptEl0 As HtmlElement = wb.Document.CreateElement("script")
        Dim element0 As mshtml.IHTMLScriptElement = DirectCast(scriptEl0.DomElement, mshtml.IHTMLScriptElement)
        element0.text = "function ReleaseHandler() {" + vbCrLf + "        var EvtMgr = (function() {" + vbCrLf + "            var listenerMap = {};" + vbCrLf + " " + vbCrLf + "            // Public interface" + vbCrLf + "            return {" + vbCrLf + "                addListener: function(evtName, node, handler) {" + vbCrLf + "                    node[""on"" + evtName] = handler;" + vbCrLf + "                    var eventList = listenerMap[evtName];" + vbCrLf + "                    if (!eventList) {" + vbCrLf + "                        eventList = listenerMap[evtName] = [];" + vbCrLf + "                    }" + vbCrLf + "                    eventList.push(node);" + vbCrLf + "                }," + vbCrLf + " " + vbCrLf + "                removeAllListeners: function() {" + vbCrLf + "                    for (var evtName in listenerMap) {" + vbCrLf + "                        var nodeList = listenerMap[evtName];" + vbCrLf + "                        for (var i = 0, node; node = nodeList[i]; i++) {" + vbCrLf + "                            node[""on"" + evtName] = null;" + vbCrLf + "                        }" + vbCrLf + "                    }" + vbCrLf + "                }" + vbCrLf + "            }" + vbCrLf + "        })();" + vbCrLf + "    }"
        head.AppendChild(scriptEl0)

        Dim scriptEl1 As HtmlElement = wb.Document.CreateElement("script")
        Dim element1 As mshtml.IHTMLScriptElement = DirectCast(scriptEl1.DomElement, mshtml.IHTMLScriptElement)
        element1.text = "function ReleaseHandler() {" + vbCrLf + "        var EvtMgr = (function() {" + vbCrLf + "            var listenerMap = {};" + vbCrLf + " " + vbCrLf + "            // Public interface" + vbCrLf + "            return {" + vbCrLf + "                addListener: function(evtName, node, handler) {" + vbCrLf + "                    node[""on"" + evtName] = handler;" + vbCrLf + "                    var eventList = listenerMap[evtName];" + vbCrLf + "                    if (!eventList) {" + vbCrLf + "                        eventList = listenerMap[evtName] = [];" + vbCrLf + "                    }" + vbCrLf + "                    eventList.push(node);" + vbCrLf + "                }," + vbCrLf + " " + vbCrLf + "                removeAllListeners: function() {" + vbCrLf + "                    for (var evtName in listenerMap) {" + vbCrLf + "                        var nodeList = listenerMap[evtName];" + vbCrLf + "                        for (var i = 0, node; node = nodeList[i]; i++) {" + vbCrLf + "                            node[""on"" + evtName] = null;" + vbCrLf + "                        }" + vbCrLf + "                    }" + vbCrLf + "                }" + vbCrLf + "            }" + vbCrLf + "        })();" + vbCrLf + "    }"
        head.AppendChild(scriptEl1)

        wb.Document.InvokeScript("ReleaseHandler")
        wb.Document.InvokeScript("purge", args)


End Sub

不幸的是,我仍然看到 perfmon 中的隐私字节在增加。

谁能看出我的逻辑有任何缺陷?我正在尝试实施此修复: http: //www.codeproject.com/Questions/322884/WPF-WebBrowser-control-vs-Internet-Explorer-browse

顺便说一句 - 我使用这样的简单代码对其进行了测试:

object[] args = {"my important message"};
webBrowser1.Document.InvokeScript("alert",args);

和这个:

Dim head As HtmlElement = wb.Document.GetElementsByTagName("head")(0)
Dim scriptEl As HtmlElement = wb.Document.CreateElement("script")
Dim element As mshtml.IHTMLScriptElement = DirectCast(scriptEl.DomElement, mshtml.IHTMLScriptElement)
element.text = "function sayHello() { alert('hello') }"
head.AppendChild(scriptEl)
wb.Document.InvokeScript("sayHello")

它在两个测试用例中都显示了消息。

奇怪的是,当我尝试通过这样做来测试脚本注入时:

    Dim head As HtmlElement = wbMain.Document.GetElementsByTagName("head")(0)
    Dim scriptEl As HtmlElement = wbMain.Document.CreateElement("script")
    Dim element As mshtml.IHTMLScriptElement = DirectCast(scriptEl.DomElement, mshtml.IHTMLScriptElement)
    element.text = "function sayHello() { alert('hello') }"
    head.AppendChild(scriptEl)
    wbMain.Document.InvokeScript("sayHello")


    RTB_RawHTML.Text = "TEST" + vbCrLf + wbMain.DocumentText

我没有看到文本框中反映的注入代码 - 我看到的唯一变化是出现了“测试”一词(当页面从 documentCompleted 事件完成加载时,我运行代码 RTB_RawHTML.Text = wbMain.DocumentText...)

4

2 回答 2

0

您引用的文章中的代码不是 C#,而是 Javascript。我相信这个想法是将 JS 注入您的 HTML 页面,以便它可以在页面卸载时运行,这将清除现有的 JS 事件。

您可以查看这篇文章,将 JS 添加到 WebBrowser 控件中的页面:http:
//www.codeproject.com/Articles/94777/Adding-a-Javascript-Block-Into-a-Form-Hosted-by-We

Dim scriptText As String =
    <string>
        function ReleaseHandler() {
                var EvtMgr = (function() {
                    var listenerMap = {};

                    // Public interface
                    return {
                        addListener: function(evtName, node, handler) {
                            node["on" + evtName] = handler;
                            var eventList = listenerMap[evtName];
                            if (!eventList) {
                                eventList = listenerMap[evtName] = [];
                            }
                            eventList.push(node);
                        },

                        removeAllListeners: function() {
                            for (var evtName in listenerMap) {
                                var nodeList = listenerMap[evtName];
                                for (var i = 0, node; node = nodeList[i]; i++) {
                                    node["on" + evtName] = null;
                                }
                            }
                        }
                    }
                })();
            }

        function purge(d){
            var a = d.attributes, i, l, n;
            if (a) {
                for (i = a.length - 1; i >= 0 ; i -= 1) {
                    n = a[i].name;
                    if (typeof d[n] === 'function') {
                        d[n] = null;
                    }
                }
            }
            a = d.childNodes;
            if (a) {
                l = a.length;
                for (i = 0; i < l; i += 1) {
                    purge(d.childNodes[i]);
                }
            }
        }

    <string>

Dim head As HtmlElement = webBrowser1.Document.GetElementsByTagName("head")(0)
Dim script As HtmlElement = webBrowser1.Document.CreateElement("script")
Dim domElement As IHTMLScriptElement = CType(script.DomElement, IHTMLScriptElement)
domElement.text = scriptText
head.AppendChild(script)

我没有测试过这段代码(我不确定我会怎么做,因为你自己没有提供示例代码)......这更多的是对你如何进行的建议。我从来没有尝试将 JS 插入 WebBrowser 控件,所以我不太确定你将如何执行它(因为理论上,JS 在加载页面后已经执行,因此你注入的 JS 会“迟到”)。

您还需要找到一种连接文档的方法,以便在卸载时调用这两个函数。这个想法是通过消除 JS 对象和事件来消除 JS 内存泄漏,因此仅声明函数是不够的。我在网上看到很多文章讨论了 WebBrowser 控件中的 OnBeforeUnload 事件是如何被破坏的(它不能正确触发),因此您可能需要做很多工作。

于 2013-03-06T14:11:48.830 回答
0

可能您可以尝试不将 cookie 保存到用户计算机的代码。导致临时项目可能对用户计算机造成几个问题

于 2013-03-07T02:26:23.630 回答