当爬虫数百万条记录时堆内存不足
heap out of memory when crawler millions of record
我正在对许多来源(有数百万条记录)的爬虫进行一些 api,但我遇到了内存不足的问题。我用谷歌搜索并找到了一些资源,但它并没有解决我的问题。
没有解决我的问题
这是我的示例代码:
function getContent() {
let d = q.defer();
let urls = [];
array.forEach(function(mang, index) {
for (let i = 1; i <= 600000; i++) {
urls.push(function (callback) {
setTimeout(function () {
let link = 'http://something.com/' + i;
let x = link;
let options = {
url: link,
headers: {
'User-Agent': 'something'
}
};
function callback1(error, response, html) {
if (!error) {
let $ = cheerio.load(html);
let tag_name = $('h1').text();
tag_name = tag_name.trim();
let tag_content = $('#content-holder').find('div').text();
let tagObject = new Object();
tagObject.tag_name = tag_name;
tagObject.tag_content = tag_content;
tagObject.tag_number = i;
tagArray.push(tagObject);
for (let v = 0; v < tagArray.length; v++) {
//console.log("INSERT INTO `tags` (tag_name, content, story_id, tag_number) SELECT * FROM (SELECT " + "'" + tagArray[v].tag_name + "'" + "," + "'" + tagArray[v].tag_content + "','" + array[c].story_id + "','" + tagArray[v].tag_number + "' as ChapName) AS tmp WHERE NOT EXISTS (SELECT `tag_name` FROM `tags` WHERE `tag_name`=" + "'" + tagArray[v].tag_name + "'" + ") LIMIT 1");
db.query("INSERT INTO `tags` (tag_name, content) " +
"SELECT * FROM (SELECT " + "'" + tagArray[v].tag_name + "'" + "," + "'" + tagArray[v].tag_content + "','" + "' as TagName) AS tmp " +
"WHERE NOT EXISTS (SELECT `tag_name` FROM `tags` WHERE `tag_name`=" + "'" + tagArray[v].tag_name + "'" + ") " +
"LIMIT 1", (err) => {
if (err) {
console.log(err);
}
});
}
urls = null;
global.gc();
console.log("Program is using " + heapUsed + " bytes of Heap.")
}
}
request(options, callback1);
callback(null, x);
}, 15000);
});
}
});
d.resolve(urls);
return d.promise;
}
getContent()
.then(function (data) {
let tasks = data;
console.log("start data");
async.parallelLimit(tasks, 10, () => {
console.log("DONE ");
});
})
我尝试使用 global.gc() 函数,但似乎效果不佳
啊,我现在明白你的问题了。您正试图在一个循环中在内存中完成所有操作。当您创建的每个匿名函数都被添加到堆中时,这种方式对于任何不平凡的工作都是疯狂的。另外,它不是很健壮。如果在第 450,000 次爬网时出现网络中断会怎样?你会失去一切并重新开始吗?
寻找 运行 小批量的工作。我之前使用过像 Kue 这样的任务管理器,但坦率地说,您需要做的就是首先用一些合理的数字(例如 10 或 25)填充您的 URL 数组。一种方法是让 table 包含所有 URL在其中以及它们已被成功抓取的标志,或者如果您打算再次抓取它们的最后一次抓取日期我有时间。
然后查询所有未被抓取的 URL(或者上次抓取的时间早于一周前的某个日期)并将结果限制为 10 或 25 或其他。首先抓取并存储它们,我可能会使用 async.js#map 或 Promise.all 之类的东西来执行此操作,而不是您当前正在使用的循环。
如果所有 URL 都访问同一个域,您可能希望在每个请求之间有一个较短的超时时间,以尊重这些资源。
一批完成后,查询您的数据库以获取下一批并重复。
根据您的体系结构,让这个程序更简单可能会更好,除了获取一批并解决一批的爬网之外什么都不做。然后,您可以 运行 它在 cron 作业上或作为 Windows 服务 运行 每 5 分钟或每 15 分钟或其他任何时间。
现在在移动设备上,但稍后我会尝试在笔记本电脑上为您提供代码示例(如果您需要)。
我正在对许多来源(有数百万条记录)的爬虫进行一些 api,但我遇到了内存不足的问题。我用谷歌搜索并找到了一些资源,但它并没有解决我的问题。
这是我的示例代码:
function getContent() {
let d = q.defer();
let urls = [];
array.forEach(function(mang, index) {
for (let i = 1; i <= 600000; i++) {
urls.push(function (callback) {
setTimeout(function () {
let link = 'http://something.com/' + i;
let x = link;
let options = {
url: link,
headers: {
'User-Agent': 'something'
}
};
function callback1(error, response, html) {
if (!error) {
let $ = cheerio.load(html);
let tag_name = $('h1').text();
tag_name = tag_name.trim();
let tag_content = $('#content-holder').find('div').text();
let tagObject = new Object();
tagObject.tag_name = tag_name;
tagObject.tag_content = tag_content;
tagObject.tag_number = i;
tagArray.push(tagObject);
for (let v = 0; v < tagArray.length; v++) {
//console.log("INSERT INTO `tags` (tag_name, content, story_id, tag_number) SELECT * FROM (SELECT " + "'" + tagArray[v].tag_name + "'" + "," + "'" + tagArray[v].tag_content + "','" + array[c].story_id + "','" + tagArray[v].tag_number + "' as ChapName) AS tmp WHERE NOT EXISTS (SELECT `tag_name` FROM `tags` WHERE `tag_name`=" + "'" + tagArray[v].tag_name + "'" + ") LIMIT 1");
db.query("INSERT INTO `tags` (tag_name, content) " +
"SELECT * FROM (SELECT " + "'" + tagArray[v].tag_name + "'" + "," + "'" + tagArray[v].tag_content + "','" + "' as TagName) AS tmp " +
"WHERE NOT EXISTS (SELECT `tag_name` FROM `tags` WHERE `tag_name`=" + "'" + tagArray[v].tag_name + "'" + ") " +
"LIMIT 1", (err) => {
if (err) {
console.log(err);
}
});
}
urls = null;
global.gc();
console.log("Program is using " + heapUsed + " bytes of Heap.")
}
}
request(options, callback1);
callback(null, x);
}, 15000);
});
}
});
d.resolve(urls);
return d.promise;
}
getContent()
.then(function (data) {
let tasks = data;
console.log("start data");
async.parallelLimit(tasks, 10, () => {
console.log("DONE ");
});
})
我尝试使用 global.gc() 函数,但似乎效果不佳
啊,我现在明白你的问题了。您正试图在一个循环中在内存中完成所有操作。当您创建的每个匿名函数都被添加到堆中时,这种方式对于任何不平凡的工作都是疯狂的。另外,它不是很健壮。如果在第 450,000 次爬网时出现网络中断会怎样?你会失去一切并重新开始吗?
寻找 运行 小批量的工作。我之前使用过像 Kue 这样的任务管理器,但坦率地说,您需要做的就是首先用一些合理的数字(例如 10 或 25)填充您的 URL 数组。一种方法是让 table 包含所有 URL在其中以及它们已被成功抓取的标志,或者如果您打算再次抓取它们的最后一次抓取日期我有时间。
然后查询所有未被抓取的 URL(或者上次抓取的时间早于一周前的某个日期)并将结果限制为 10 或 25 或其他。首先抓取并存储它们,我可能会使用 async.js#map 或 Promise.all 之类的东西来执行此操作,而不是您当前正在使用的循环。
如果所有 URL 都访问同一个域,您可能希望在每个请求之间有一个较短的超时时间,以尊重这些资源。
一批完成后,查询您的数据库以获取下一批并重复。
根据您的体系结构,让这个程序更简单可能会更好,除了获取一批并解决一批的爬网之外什么都不做。然后,您可以 运行 它在 cron 作业上或作为 Windows 服务 运行 每 5 分钟或每 15 分钟或其他任何时间。
现在在移动设备上,但稍后我会尝试在笔记本电脑上为您提供代码示例(如果您需要)。