我有一个包含大约120k HTML页面的文件夹,我需要打开它(每个文件大约70kb),用xPath解析一些数据并将该数据附加到.csv文件。
以下是我的代码:
它应该从parseFolder中读取一个文件列表,遍历每个文件名,用fs.readFile打开它,然后使用jsdom和xpath解析数据,并使用fs.appendFile将其保存到csv文件。
它似乎对前100个左右的文件做得很好,但之后会逐渐变慢,消耗内存和CPU并最终停止。我有16演出的内存,当我的内存使用量达到约7gig时,似乎达到了一定的限制。
我是JS和Node的新手,任何帮助指出我失踪的内容都会非常感激。
var fs = require('fs');
var jsdom = require('jsdom').jsdom;
var xpath = require('xpath');
var S = require('string');
var os = require('os');
ParserRules = {
saveFile: 'output.csv',
parseFolder: '/a/folder/with/120k/HTML/files',
fields: {
"field1": "//div[@class='field1']/text()",
}
};
start();
function start() {
console.log('Starting...');
fs.readdir(ParserRules.parseFolder, iterateFiles);
}
function iterateFiles(err, filesToParse) {
for (var i = 0; i < filesToParse.length; i++) {
file = ParserRules.parseFolder + '/' + filesToParse[i];
console.log('Beginning read of ' + file);
fs.readFile(file, {encoding: 'utf8'}, parseFile);
}
}
function parseFile(err, data) {
if (err == null) {
var jsdomDocument = jsdom(data);
var document = jsdomDocument.parentWindow.document;
getContent(document);
}
}
function getContent(document) {
fields = ParserRules.fields;
var csvRow = [];
for (var field in fields) {
try {
console.log('Looking for ' + field);
var nodes = xpath.select(fields[field], document);
for (var i = 0; i < nodes.length; i++) {
csvRow.push(getValue(nodes[i]));
}
} catch (err) {
console.log(err);
}
}
saveToCsv(csvRow, ParserRules.saveFile);
}
function getValue(node) {
if(node.nodeValue != null) {
toReturn = node.nodeValue;
} else {
newNode = $(node);
toReturn = newNode.html();
}
return toReturn;
}
function saveToCsv(object, filePath) {
console.log('Saving...');
if(object.length > 0) {
console.log('Row Exists, Saving...');
toString = S(object).toCSV().s + os.EOL;
fs.appendFile(filePath, toString, {encoding: 'utf8', flag: 'a'}, function(err){
if (err) {
console.log('Write Error: ' + err);
} else {
console.log('Saved ' + object);
}
});
}
}
答案 0 :(得分:4)
Node.js异步工作。
所以你的代码的结构方式就是这样:
函数iterateFiles
连续发出120k fs.readFile
次调用,导致Node.js排队120k文件系统读取操作。
当读取操作完成时,Node.js将调用fs.readFile
的120k回调,并且每个回调将发出fs.appendFile
操作,这将导致Node.js排队120k文件系统写操作。
最终,Node.js将调用传递给fs.appendFile
的120k回调。在这些写入操作完成之前 Node.js 必须挂起要写入的数据。
对于这样的任务,我建议使用fs调用的同步版本:fs.readFileSync
和fs.appendFileSync
。
在为Web服务器编写代码或以某种方式事件驱动时,您不希望使用这些调用的同步版本,因为它们会导致应用程序阻塞。但是,如果您正在编写正在进行数据批处理的代码(例如,像shell脚本那样运行的代码),则使用这些调用的同步版本会更简单。
以下代码是代码的简化模型,并说明了问题。它设置为从/tmp
读取,因为它与任何文件一样好。我还设置它以避免在文件为空时执行除parseFile
以外的任何进一步工作。
var fs = require('fs');
var ParserRules = {
saveFile: 'output.csv',
parseFolder: '/tmp'
};
start();
function start() {
console.log('Starting...');
fs.readdir(ParserRules.parseFolder, iterateFiles);
}
function iterateFiles(err, filesToParse) {
for (var i = 0; i < filesToParse.length; i++) {
var file = ParserRules.parseFolder + '/' + filesToParse[i];
console.log('Beginning read of file number ' + i);
fs.readFile(file, {encoding: 'utf8'}, parseFile);
}
}
var parse_count = 0;
function parseFile(err, data) {
if (err)
return;
if (data.length) {
console.log("Parse: " + parse_count++);
getContent(data);
}
}
function getContent(data) {
saveToCsv(data, ParserRules.saveFile);
}
var save_count = 0;
function saveToCsv(data, filePath) {
fs.appendFile(filePath, data, {encoding: 'utf8', flag: 'a'},
function(err){
if (err) {
console.log('Write Error: ' + err);
} else {
console.log('Saved: ' + save_count++);
}
});
}
如果您运行此代码,您会看到所有Parse:
消息都是连续显示的。然后仅在输出所有Parse:
消息后,您才会收到Saved:
条消息。所以你会看到类似的东西:
Beginning read of file number N
Beginning read of file number N+1
Parse: 0
Parse: 1
... more parse messages ...
Parse: 18
Parse: 19
Saved: 0
Saved: 1
... more saved messages...
Saved: 18
Saved: 19
这告诉你的是,在解析所有文件之前,Node不会开始保存。由于Node无法释放与文件关联的数据,因为它知道它不会再次使用 - 在这种情况下,它意味着直到文件被保存 - 然后在某些时候节点将至少需要120,000 * 70kb的内存来保存所有文件中的所有数据。