1. Вступайте в наш Telegram чат: https://t.me/a_parser Нас уже 2600+ и мы растем!
    Скрыть объявление

Периодический мониторинг цен с уведомлением в Telegram

Мониторинг цен на товары на mvideo и rozetka с уведомлениями в Телеграм

  1. Support
    В этой статье будет показано, как с помощью A-Parser периодически проверять цены товаров сразу на несколько сайтах и получать уведомление в телеграм в случае их изменения.

    [​IMG]

    Для примера возьмем несколько ссылок на страницы товаров:
    Решение будет состоять из нескольких этапов:
    1. Парсинг названия и цены для каждого товара
    2. Сравнение полученной цены с ранее сохраненной:
      • если данный товар парсится впервые, то просто сохраняем его цену
      • иначе считаем разницу и сохраняем новую цену для последующих запусков
    3. Формирование результата, а также, если цена повысилась или снизилась, то отправляем уведомление в Telegram
    Хранить цены будем в базе данных SQLite, т.к. для работы с такими БД в А-Парсере уже есть встроенные инструменты.

    Парсинг цен
    На данном этапе парсер должен перейти по ссылке и получить код страницы товара.
    Код:
    resp = yield this.request('GET', url, {}, {
        attempt: attempt,
        decode: 'auto-html',
        browser: 1,
        headers: {
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36'
        }
    });
    Т.к. для примера были взяты ссылки из 2-х различных сайтов, то нужно предусмотреть корректный парсинг страниц для каждого сайта. Для этого нужно извлечь домен из ссылки и в зависимости от него парсить данные:
    Код:
    switch(this.utils.url.extractTopDomainByZone(url)) {
       
    case 'avito.ru':
        if (!/itemProp="price".*?>(.*?)</.test(resp.data)) throw {message: 'Content mismatch'}
        result.price = parseFloat(this.parseSingle(resp.data, /itemProp="price".*?>(.*?)</)?.replace(/\s/g, ''));
        result.title = this.parseSingle(resp.data, /<title.*?>(.*?)<\/title>/);
        break;
    case 'hotline.ua':
        if (!/class="price__value".*?>(.*?)<\/span/.test(resp.data)) throw {message: 'Content mismatch'}
        result.price = parseFloat(this.parseSingle(resp.data, /class="price__value".*?>(.*?)<\/span/)?.replace(/\s/g, ''));
        result.title = this.parseSingle(resp.data, /<title.*?>(.*?)<\/title>/);
        break;
        default:
            this.logger.put('Unknown query');
            resp.success = 0;
    }
    Как видно в коде выше, чтобы обеспечить получение именно той страницы, которая нужна, а не различных заглушек, каптч и т.п., мы будем использовать собственный обработчик попыток и проверять получаемый контент.
    Для удобства парсинг цен вынесем в отдельную функцию, которая должна вернуть объект с флагом успешности запроса, ценой и названием товара.

    Сравнение цен
    Этот этап мы также реализуем в виде отдельной функции. Логика ее работы такова:
    • если новая цена не была спаршена (например, товар закончился и его цена больше не отображается на странице), то возвращаем статус not found
    • иначе ищем в БД товар, в качестве идентификатора используем ссылку
      • если товар не найден, то записываем в БД данные о товаре и возвращаем статус new
      • если товар найден, то сравниваем цены и возвращаем статус up, down или equal, обозначающие направление изменения цены
    Код:
    comparePrice(url, price, title) {
        if(!price) return {status: 'not found', oldPrice: ''};
        let row = tools.sqlite.get(db, "SELECT * FROM prices WHERE url = ?", url);
        if(!row) {
            tools.sqlite.run(db, "INSERT INTO prices(url, title, price) VALUES(?, ?, ?)", url, title, price);
            return {status: 'new', oldPrice: ''};
        } else {
            tools.sqlite.run(db, "UPDATE prices SET title = ?, price = ? WHERE url = ?", title, price, url);
            let diff = price - (!row.price ? 0 : row.price);
            return {
                status: diff > 0 ? 'up' : diff < 0 ? 'down' : 'equal',
                oldPrice: row.price
            };
        }
    }
    Обработка результата
    Записываем полученные данные в results, а также, если статус цены up или down, отправляем сообщение в Telegram.
    Код:
    results.title = data.title;
    results.price = data.price;
    results.oldPrice = status.oldPrice;
    results.status = status.status;
    switch(status.status) {
        case 'up':
            results.success = yield* this.send(`Цена на <a href="${set.query}">${data.title}</a> <b>повысилась</b>`);
            break;
        case 'down':
            results.success = yield* this.send(`Цена на <a href="${set.query}">${data.title}</a> <b>снизилась</b>`);
            break;
    }
    Для отправки сообщений нужно написать небольшую функцию, которая отправит сообщение и вернет успешность отправки (true/false):
    Код:
    *send(text) {
        if(!this.conf.token || !this.conf.id) {
            this.logger.put('Need set Telegram parameters!');
            return false;
        }
     
        let resp = yield this.request('POST', 'https://api.telegram.org/bot' + this.conf.token + '/sendMessage', {
            chat_id: this.conf.id,
            text: text,
            parse_mode: 'HTML',
            disable_web_page_preview: true
        }, {
            use_proxy: 0,
            parsecodes: {
                200: 1,
                400: 1,
                401: 1
            }
        });
        if(!resp.success) {
            this.logger.put('Sending failed');
            return false;
        }
     
        try {
            let json = JSON.parse(resp.data);
            if(json.ok) {
                return true;
            } else {
                if(json.description)
                    this.logger.put(json.description);
                return false;
            }
        } catch(e) {
            return false;
        }
    }
    В нашем парсере нужно указать токен бота и id чата с ним. Подробно процедура создания бота и добавления его в группу описана здесь в разделе Подготовка.

    Заключение
    В результате получается парсер, который решает поставленную вначале задачу. Благодаря универсальности кода, можно добавлять любые другие сайты, написав буквально пару строк кода в функции getData по аналогии с уже существующими.

    Сохраняем задание на основе этого парсера и с заданными ссылками на товары, создаем в Планировщике задание с периодичностью, например, 1 день. В итоге парсер каждый день будет проверять цены по списку товаров и в случае их изменений, присылать уведомление в Телеграм.

    Готовый пресет доступен в Каталоге: https://a-parser.com/resources/366/