T
T
tempick2020-09-21 17:14:25
Node.js
tempick, 2020-09-21 17:14:25

Why does RAM usage increase when running a node.js script?

Installed nginx+node.js (express.js) and PM2 on the server

Here is the complete script code

const setup = {port:8000}
// Подключаем express
const express = require ('express');
const puppeteer = require('puppeteer');

// создаем приложение
const app = express ();

app.get('/', (req, res) => {
    const url = req.query.url;
    let scrape = async () => {
        const browser = await puppeteer.launch({args: ['--no-sandbox']});
        //const browser = await puppeteer.launch({headless: false});
        const page = await browser.newPage();
        await page.setDefaultNavigationTimeout(0);

        //имитируем мобилку
        await page.setUserAgent('Mozilla/5.0 (Linux; Android 7.0; NEM-L51) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.81 Mobile Safari/537.36');
        await page.setViewport({width: 375, height: 812});

        //переходим по ссылке
        await page.goto(url);


        //если есть модалка
        const bottomSheet = await page.evaluate(() => {
            return document.querySelector('div[data-marker="bottom-sheet"]');
        });

        //то закрываем ее
        if (bottomSheet !== null) {
            await page.click('div[data-marker="bottom-sheet"] button');
        }

        //Проверяем наличие кнопки "позвонить"
        const phoneButton = await page.evaluate(() => {
            return document.querySelector('a[data-marker="item-contact-bar/call"]');
        });

        if (phoneButton === null) {
            await browser.close();
            return false;
        }


        await page.waitForSelector('a[data-marker="item-contact-bar/call"]');

        //кликаем по кнопке для получения номера
        await page.click('a[data-marker="item-contact-bar/call"]');

        //ждем, когда загрузится блок с номером
        try {
            await page.waitForSelector('span[data-marker="phone-popup/phone-number"]');
        } catch (e) {
            await browser.close();
            return false;
        }


        //извлекаем номер и возвращаем
        const result = await page.evaluate(() => {
            console.log('phone', document.querySelector('span[data-marker="phone-popup/phone-number"]'));
            return document.querySelector('span[data-marker="phone-popup/phone-number"]').innerHTML;

        });

        await browser.close();
        return result;

    };

    scrape().then((value) => {
        console.log(value);
        if (value === false)
            res.send(500);
        res.send(value);
        scrape = null;
    });
    //res.send(req.query.url);
});


app.get('/test', (req, res) => {
    res.send('Тест');
});

// Слушаем порт и при запуске сервера сообщаем
app.listen(setup.port, () => {
    console.log('Сервер: порт %s - старт!', setup.port);
});



After each get request, I look at pm2 list and see that more and more memory is being occupied by this process. What is it for?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
D
Dmitry Belyaev, 2020-09-21
@tempick

Let's see what you coded here... I'll remove your comments from the code and add my own:

const setup = {port:8000}
const express = require ('express');
const puppeteer = require('puppeteer');

const app = express ();

app.get('/', (req, res) => {
    const url = req.query.url;

    // вот тут Вы на каждый запрос создаете весьма тяжелую функцию
    // в ней 203 AST ноды
    // и она жрет в среднем 220КБ оперативы
    // (node: 14.4.0; v8: 8.1.307.31-node.33, мерил через process.memoryUsage().heapUsed)
    let scrape = async () => {

        // а еще на каждый запрос запускам новый браузер
        // у ноды это особо памяти не отнимет, а вот у системы - прилично
        const browser = await puppeteer.launch({args: ['--no-sandbox']});
        const page = await browser.newPage();

        // еще и разрешаем запросу из браузера жить вечно
        // если конечно сервак не оборвет коннект
        await page.setDefaultNavigationTimeout(0);

        await page.setUserAgent('Mozilla/5.0 (Linux; Android 7.0; NEM-L51) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.81 Mobile Safari/537.36');
        await page.setViewport({width: 375, height: 812});

        // куда мы отправляем браузер?
        // переменная url у нас из req.query.url - а следовательно начинается с /
        // то есть без хоста и протокола...
        await page.goto(url);

        // что-то мне подсказывает, что это работает не совсем так
        // как Вы ожидаете
        // https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#pageevaluatepagefunction-args
        // читаем: If the function passed to the page.evaluate returns a non-Serializable value, then page.evaluate resolves to undefined
        const bottomSheet = await page.evaluate(() => {
            return document.querySelector('div[data-marker="bottom-sheet"]');
        });

        // так как undefined !== null данное условие всегда истинно
        if (bottomSheet !== null) {
            // здесь по идее придет Promise.reject который мы не ловим (об этом ниже)
            await page.click('div[data-marker="bottom-sheet"] button');
        }

        // и еще раз... ловите доку на нужный метод:
        // https://github.com/puppeteer/puppeteer/blob/main/docs/api.md#pageselector
        const phoneButton = await page.evaluate(() => {
            return document.querySelector('a[data-marker="item-contact-bar/call"]');
        });

        // всегда ложное условие...
        if (phoneButton === null) {
            await browser.close();
            return false; // ...с return внутри...
        }

        // еще 1 способ зависнуть (дефолтный таймаут 30 сек)
        await page.waitForSelector('a[data-marker="item-contact-bar/call"]');

        await page.click('a[data-marker="item-contact-bar/call"]');

        try {
            await page.waitForSelector('span[data-marker="phone-popup/phone-number"]');
        } catch (e) {
            await browser.close();
            return false;
        }


        const result = await page.evaluate(() => {
            console.log('phone', document.querySelector('span[data-marker="phone-popup/phone-number"]'));
            return document.querySelector('span[data-marker="phone-popup/phone-number"]').innerHTML;

        });

        await browser.close();
        return result;

    };

    // не ловим reject промиса
    // и в случае reject не завершаем запрос
    // и он тоже висит в памяти
    scrape().then((value) => {
        console.log(value);
        if (value === false)
            res.send(500);
        // при value === false будет запись в закрытый поток... (или у express есть защита от дурака?)
        res.send(value);
        // абсолютно бесполезное действие...
        scrape = null;
    });
});


app.get('/test', (req, res) => {
    res.send('Тест');
});

app.listen(setup.port, () => {
    console.log('Сервер: порт %s - старт!', setup.port);
});

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question