[英]Why does my NodeJS script bog down during fs.readFile and fs.appendFile handling large numbers of files.
我有一個需要打開約120k HTML頁面的文件夾(每個文件約為70kb),使用xPath解析一些數據並將該數據附加到.csv文件中。
下面是我的代碼:
它應該從parseFolder中讀取文件列表,遍歷每個文件名,然后使用fs.readFile打開文件,然后使用jsdom和xpath解析數據,並使用fs.appendFile將其保存到csv文件中。
對於前100個左右的文件來說,這似乎做得很好,但是之后會逐漸變慢,消耗內存和CPU,最終停滯。 我有16 gigs的內存,當我的內存使用量達到約7gigs時,似乎達到了一些極限。
我是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);
}
});
}
}
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在知道不會再次使用文件之前無法釋放與文件關聯的數據-在這種情況下,這意味着直到文件被保存--然后在某個時候Node將至少花費120,000 * 70kb的內存可容納所有文件中的所有數據。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.