Javascript Node.js中存在内存泄漏
这是一个简单的scraper,用JavaScript和Node.js编写,用于从维基百科中获取元素周期表数据。依赖项用于DOM操作和排队 在大多数情况下,它工作得很好(不能优雅地处理错误),代码也不算太差,我敢说这是一次尝试,但它有一个严重的错误——它严重地泄漏内存,每个元素的内存占计算机内存的0.3%到0.6%,因此,当它领先时,它将使用接近20%的内存,这显然是不可接受的 我曾尝试使用探查器,但我要么没有发现它们有帮助,要么难以解释数据。我怀疑这与Javascript Node.js中存在内存泄漏,javascript,memory-leaks,node.js,screen-scraping,Javascript,Memory Leaks,Node.js,Screen Scraping,这是一个简单的scraper,用JavaScript和Node.js编写,用于从维基百科中获取元素周期表数据。依赖项用于DOM操作和排队 在大多数情况下,它工作得很好(不能优雅地处理错误),代码也不算太差,我敢说这是一次尝试,但它有一个严重的错误——它严重地泄漏内存,每个元素的内存占计算机内存的0.3%到0.6%,因此,当它领先时,它将使用接近20%的内存,这显然是不可接受的 我曾尝试使用探查器,但我要么没有发现它们有帮助,要么难以解释数据。我怀疑这与processElement的传递方式有关,
processElement
的传递方式有关,但我很难将队列代码重写为更优雅的代码
var fs = require('fs'),
path = require('path'),
jsdom = require("jsdom"),
parseUrl = require('url').parse,
chainGang = require('chain-gang');
var chain = chainGang.create({
workers: 1
});
var Settings = {
periodicUrl: 'http://en.wikipedia.org/wiki/Template:Periodic_table',
periodicSelector: '#bodyContent > table:first',
pathPrefix: 'data/',
ignoredProperties: ['Pronunciation']
};
function writeToFile(output) {
var keys = 0;
// Huge nests for finding the name of the element... yeah
for(var i in output) {
if(typeof output[i] === 'object' && output[i] !== null){
for(var l in output[i]) {
if(l.toLowerCase() === 'name') {
var name = output[i][l];
}
}
keys += Object.keys(output[i]).length;
}
}
console.log('Scraped ' + keys + ' properties for ' + name);
console.log('Writing to ' + Settings.pathPrefix + name + '.json');
fs.writeFile(Settings.pathPrefix + name + '.json', JSON.stringify(output));
}
// Generic create task function to create a task function that
// would be passed to the chain gang
function createTask (url, callback) {
console.log('Task added - ' + url);
return function(worker){
console.log('Requesting: ' +url);
jsdom.env(url, [
'jquery.min.js' // Local copy of jQuery
], function(errors, window) {
if(errors){
console.log('Error! ' + errors)
createTask(url, callback);
} else {
// Give me thy $
var $ = window.$;
// Cleanup - remove unneeded elements
$.fn.cleanup = function() {
return this.each(function(){
$(this).find('sup.reference, .IPA').remove().end()
.find('a, b, i, small, span').replaceWith(function(){
return this.innerHTML;
}).end()
.find('br').replaceWith(' ');
});
}
callback($);
}
worker.finish();
});
}
}
function processElement ($){
var infoBox = $('.infobox'),
image = infoBox.find('tr:contains("Appearance") + tr img:first'),
description = $('#toc').prevAll('p').cleanup(),
headers = infoBox.find('tr:contains("properties")'),
output = {
Appearance: image.attr('src'),
Description: $('.infobox + p').cleanup().html()
};
headers.each(function(){
var that = this,
title = this.textContent.trim(),
rowspan = 0,
rowspanHeading = '';
output[title] = {};
$(this).nextUntil('tr:has(th:only-child)').each(function(){
var t = $(this).cleanup(),
headingEle = t.children('th'),
data = t.children('td').html().trim();
if(headingEle.length) {
var heading = headingEle.html().trim();
}
// Skip to next heading if current property is ignored
if(~Settings.ignoredProperties.indexOf(heading)) {
return true;
}
if (rowspan) {
output[title][rowspanHeading][data.split(':')[0].trim()] = data.split(':')[1].trim();
rowspan--;
} else if (headingEle.attr('rowspan')){
rowspan = headingEle.attr('rowspan') - 1;
rowspanHeading = heading;
output[title][heading] = {};
output[title][heading][data.split(':')[0]] = data.split(':')[1];
} else if (~heading.indexOf(',')){
data = data.split(',');
heading.split(',').forEach(function(v, i){
output[title][v.trim()] = data[i].trim();
});
} else {
output[title][heading] = data;
}
});
});
writeToFile(output);
}
function fetchElements(elements) {
elements.forEach(function(value){
// Element URL used here as task id (second argument)
chain.add(createTask(value, processElement), value);
});
}
function processTable($){
var elementArray = $(Settings.periodicSelector).find('td').map(function(){
var t = $(this),
atomicN = parseInt(t.text(), 10);
if(atomicN && t.children('a').length) {
var elementUrl = 'http://' + parseUrl(Settings.periodicUrl).host + t.children('a:first').attr('href');
console.log(atomicN, t.children('a:first').attr('href').split('/').pop(), elementUrl);
return elementUrl;
}
}).get();
fetchElements(elementArray);
fs.writeFile(Settings.pathPrefix + 'elements.json', JSON.stringify(elementArray));
}
// Get table - init
function getPeriodicList(){
var elementsList = Settings.pathPrefix + 'elements.json';
if(path.existsSync(elementsList)){
var fileData = JSON.parse(fs.readFileSync(elementsList, 'utf8'));
fetchElements(fileData);
} else {
chain.add(createTask(Settings.periodicUrl, processTable));
}
}
getPeriodicList();
我知道这不是一个很好的答案,但我有一个类似的问题。我有多个刮板同时运行,内存正在泄漏 我最终使用了节点jquery而不是JSDOM
jsdom确实存在内存泄漏,这是由于节点的
vm.runInContext()后面的复制入和复制出逻辑造成的。已经有人试图用C++来解决这个问题,我们希望在尝试将它推到节点之前证明解决方案。
目前的一种解决方法是为每个dom生成一个子进程,并在完成后将其关闭
编辑:
从jsdom 0.2.3开始,只要在使用完窗口(window.close()
)后关闭它,这个问题就会得到解决。我想我有更好的解决方法,通过设置window.document.innerHTML属性重用jsdom实例。解决了我的内存泄漏问题
// jsdom has a memory leak when using multiple instance
// cache a single instance and swap out innerHTML
var dom = require('jsdom');
var win;
var useJQuery = function(html, fnCallback) {
if (!win) {
var defEnv = {
html:html,
scripts:['jquery-1.5.min.js'],
};
dom.env(defEnv, function (err, window) {
if (err) throw new Error('failed to init dom');
win = window;
fnCallback(window.jQuery);
});
}
else {
win.document.innerHTML = html;
fnCallback(win.jQuery);
}
};
....
// Use it!
useJQuery(html, function($) { $('woohoo').val('test'); });
对于类似jQuery的html处理,我现在使用节点而不是jsdom。到目前为止,我在几个小时内没有看到任何内存泄漏,而销毁和解析了超过10K的页面。没有,我刚刚用jsdom替换了node jquery
——这两种泄漏内存的方式惊人地相似。你认为这不是你的代码泄漏内存吗?更改库可能不会有帮助。这是一个很好的提示。我有一些代码试图解析一些大约25兆字节的html,jsdom在长时间延迟后由于内存不足错误崩溃。重写代码以使用ChereIO代码在7秒内完成,没有任何错误。在当前阶段,ChereIO与jQuery非常不同,并且在选择器和DOM包装器中缺少很多功能。它肯定更快,但不兼容,如果您需要熟悉jQuery.window.close()并具有良好的表达能力,这是一个问题。如果正确,请确保完成后关闭窗口,并且GC将按预期工作。:)如果可以的话:你应该在github的frontpage自述中包含这一点。我认为让用户知道窗口对象需要清理是非常重要的!也许这是“明显的”,但对我来说不是。这对我来说真的不起作用,下面是我的代码片段:var jsdom=require('jsdom');var win=jsdom.jsdom().createWindow();var useJQuery=function(html,fnCallback){jsdom.jQueryify(win,”,function(){win.document.innerHtml=html;fnCallback(win);});