Задать вопрос
@tempick

Почему при выполнении скрипта node.js увеличивается занятая RAM?

Установил на сервер nginx+node.js (express.js) и PM2

Вот полный код скрипта

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);
});



После каждого get-запроса я смотрю pm2 list и вижу что памяти этим процессом занимается всё больше и больше. Из-за чего это?
  • Вопрос задан
  • 252 просмотра
Подписаться 2 Средний 6 комментариев
Решения вопроса 1
bingo347
@bingo347 Куратор тега Node.js
Crazy on performance...
Давайте посмотрим, что Вы тут накодили... Я уберу Ваши комментарии из кода и добавлю свои:
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);
});
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы