当爬虫数百万条记录时堆内存不足

时间:2017-06-15 16:04:25

标签: node.js express cheerio

我正在从许多来源(有数百万条记录)对爬虫进行一些api但是我的问题涉及内存不足。我用Google搜索并找到了一些资源,但它并没有解决我的问题。

similar question没有解决我的问题

这是我的示例代码:

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()函数,但似乎没有效果

1 个答案:

答案 0 :(得分:1)

啊,我现在看到你的问题了。你试图在一个循环中在内存中完成所有操作。这种方式对于任何非常重要的工作都是疯狂的,因为你正在创建的每个匿名函数都被添加到堆中。此外,它不是很强大。如果您在第450,000次抓取时遇到网络中断,会发生什么?你会失去一切并重新开始吗?

期待有一份小批量的工作。我之前曾经使用像Kue这样的任务管理器,但坦率地说,你需要做的就是首先用10或25之类的合理数字填充你的URL数组。一种方法是在其中包含一个包含所有URL的表并且要么是他们已成功抓取的旗帜,要么是你计划再次执行它们的最后抓取日期我是时候了。

然后查询尚未抓取的所有网址(或者比一周前的某个日期更早抓取的网址),并将结果限制为10或25或其他任何内容。首先抓取并存储这些,我可能会使用像async.js #map或Promise.all这样的东西,而不是你当前正在使用的循环。

如果所有URL都在同一个域中,您可能希望每个请求之间都有一个短暂的超时,以尊重这些资源。

批处理完成后,查询您的数据库以获取下一批并重复。

根据您的体系结构,最好让这个程序更简单,除了获取一个批处理并解决一个批处理的爬网之外什么也不做。然后,您可以在cron作业或Windows服务上运行它,每5分钟或每15分钟或其他任何时间运行一次。

现在在手机上,但是如果你需要的话,我会在以后尝试使用笔记本电脑给你一个代码示例。