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

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

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

wud8v_191230180827.png


Для примера возьмем несколько ссылок на страницы товаров:

https://hotline.ua/ua/computer-noutbuki-netbuki/hp-probook-450-g9-6a151ea/
https://www.avito.ru/moskva/telefony/telefon_iphone_7_128gb_3286317509
https://hotline.ua/ua/mobile-mobilnye-telefony-i-smartfony/oppo-a78-8128gb-aqua-green/
https://hotline.ua/ua/av-televizory/vinga-s50uhd25bweb/
https://www.avito.ru/moskva/tovary_dlya_detey_i_igrushki/elektrosamokat_kugoo_s3_bu_3357795026
https://www.avito.ru/moskva/mototsikly_i_mototehnika/enduro_racer_300_3240357828

Решение будет состоять из нескольких этапов:
  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/
Автор
Support
Просмотры
88
Первый выпуск
Обновление

Рейтинги

0,00 звёзд Оценок: 0

Ещё ресурсы от Support

Назад
Верх