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

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 分钟或其他任何时间。

现在在移动设备上,但稍后我会尝试在笔记本电脑上为您提供代码示例(如果您需要)。