Je travaille sur un scraper web, et il fonctionne bien en général. Il peut parcourir des milliers de pages sur la plupart des sites et terminer sans problème.
Sur quelques sites, je constate le même problème à plusieurs reprises.
Insufficient memory to continue the execution of the program.
Edita: J'ai utilisé perfmon pour déterminer que la fuite se produit dans la mémoire non gérée. Je le sais parce que les "octets privés" continuent d'augmenter au fur et à mesure que le programme s'exécute, alors que les octets dans tous les tas restent stables.
(en fait, il monte et descend, mais progressivement. il manque généralement de mémoire dans la section de code que j'ai listée ci-dessus, mais je ne pense pas que cette section soit la cause, mais plutôt une première victime probable parce qu'elle utilise beaucoup de mémoire... je pense qu'elle la libère ensuite cependant)
Edit 2 :
J'ai suivi les instructions de ce site : http://www.codeproject.com/Articles/42721/Best-Practices-No-5-Detecting-NET-application-memo
et j'ai utilisé debugDiag pour inspecter le programme.
Après avoir analysé les données, le programme de débogage m'a dit ce qui était responsable de la fuite :
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.
Je ne fais pas référence à jscript.dll dans mon application, elle doit être utilisée par les contrôles du navigateur Web que j'utilise.
System.Windows.Forms.WebBrowser
C'est ce que je pense, en tout cas.
Je reçois également un message qui s'affiche avec le titre "Message From webpage" et qui dit quelque chose comme "out of memory at line X".
J'ai donc pensé que je pouvais simplement me débarrasser des objets du navigateur web et récupérer ma mémoire - j'ai donc ajouté un bouton avec le code suivant :
Me.wbMain.Dispose() 'dispose all of thwe web-browsers
frmDebugger.wbDebugMain.Dispose()
Me.WBNewWin.Dispose()
GC.Collect() 'just for the heck of it
Donc, après l'avoir utilisé pendant un certain temps, j'ai arrêté de gratter et j'ai cliqué sur mon nouveau bouton... cela n'a fait aucune différence. Je regardais le total des "Private Bytes" dans perfmon, et ça n'a même pas bougé.
Des idées, quelqu'un ?
Edit 3 :
J'ai essayé plusieurs des solutions recommandées, mais aucune ne semble fonctionner.
Quelqu'un a suggéré que cela pouvait être dû au fait que les images ne sont pas effacées du cache, mais j'ai désactivé le chargement des images, donc je sais que ce n'est pas le problème.
J'ai également entendu dire qu'IE7 avait un problème et que la mise à niveau vers IE8 le résoudrait. J'ai IE8 et il perd toujours de la mémoire.
Quelqu'un a suggéré que minimiser le formulaire avec le contrôle du navigateur web libérerait de la mémoire. J'ai essayé, et cela ne fait pas de différence.
On m'a également dit que je ne devais pas m'attendre à ce que l'utilisation de la mémoire diminue simplement, car je dois attendre le ramasseur d'ordures. Ce n'est pas une fuite dans le code géré, donc GC.Collect() ne fera rien. Elle se trouve dans la mémoire non gérée. Apparemment, la fonctionnalité javascript utilise une mémoire différente, et il n'y a pas de moyen manuel de forcer une collecte. Mais il en arrive au point où il se plante, donc il y a manifestement un problème.
J'ajoute une prime de 50 à cette question, et je l'attribuerai à toute personne qui m'aidera à résoudre la fuite. Je voulais essayer cette solution : http://www.codeproject.com/Questions/322884/WPF-WebBrowser-control-vs-Internet-Explorer-browse mais je ne parviens pas à trouver l'équivalent en vb.net. J'ai essayé des convertisseurs en ligne, et ils se trompent lors de la conversion de ce code (bien qu'ils fonctionnent bien pour d'autres codes que j'ai convertis dans le passé).
Si je ne parviens pas à résoudre la fuite, je l'attribuerai à quiconque convertira la page que j'ai mentionnée ci-dessus de c# en vb.net.
Mon plan de secours est de créer une application séparée qui ne contient que le navigateur web, et de communiquer avec ce processus, jusqu'à ce qu'il soit à court de mémoire, auquel cas je le redémarre (la mémoire est libérée lorsque je ferme complètement mon application). Cette méthode est loin d'être idéale pour mon application, car le navigateur web est étroitement lié à mon projet.
Edit 4
J'ai essayé d'implémenter l'injection javascript suggérée - voici mon code :
(Je le déclenche juste avant de naviguer vers une nouvelle page)
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
malheureusement, je vois toujours les octets privés augmenter dans perfmon.
Quelqu'un peut-il voir des failles dans ma logique ? J'essaie de mettre en œuvre cette solution : http://www.codeproject.com/Questions/322884/WPF-WebBrowser-control-vs-Internet-Explorer-browse
btw - je l'ai testé en utilisant un code simple comme celui-ci :
object[] args = {"my important message"};
webBrowser1.Document.InvokeScript("alert",args);
et ceci :
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")
et il a montré le message dans les deux cas de test.
Curieusement, lorsque j'ai essayé de tester l'injection de script en faisant ceci :
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
Je n'ai pas vu le code injecté se refléter dans la zone de texte - le seul changement que j'ai vu est l'apparition du mot "test" (j'exécute le code RTB_RawHTML.Text = wbMain.DocumentText lorsque les pages finissent de se charger à partir de l'événement documentCompleted...).