Отправка уведомлений о ценах с помощью AWS Lambda, SNS и Node
В этой статье мы разработаем автоматизированное решение, которое проверяет цены на аренду автомобилей на сайте Costco Travel. Он будет ежедневно присылать нам SMS-сообщения с самой низкой ценой.
Технологии
АВС Лямбда
Lambda — это безсерверная вычислительная платформа Amazon, которая позволяет нам запускать наш код без необходимости полагаться на серверы. Он поддерживает код, написанный на Node.js, Java, C# и Python.
В нашем примере мы будем использовать Node.js, который является серверной средой выполнения для JavaScript.
АМС SNS
SNS — это служба уведомлений Amazon, которая позволяет нам отправлять SMS-сообщения из Lambda.
SMS-сообщения доступны не во всех регионах. Дополнительные сведения см. в разделе Поддерживаемые регионы и страны в Руководстве для разработчиков Amazon SNS.
Настройка социальных сетей
В этом разделе мы рассмотрим шаги по настройке SNS.
Создать тему
Темы — это в основном списки подписок, в которых мы можем публиковать сообщения.
На панели управления SNS нажмите Создать тему.
Давайте создадим тему с именем NotifyMe.
Каждой теме присваивается уникальный идентификатор, называемый ARN темы. Запишите это, так как оно понадобится нам позже в нашем коде.
Тема ARN будет выглядеть примерно так:
arn:aws:sns:us-east-1:000000000000:NotifyMe
Создать подписку
- Нажмите Создать подписку.
- Выберите SMS из раскрывающегося списка Протокол. Если СМС нет в списке, значит регион не поддерживает.
- Введите номер мобильного телефона в поле Конечная точка.
На номер будет отправлено текстовое сообщение с подтверждением.
Туристический сайт Costco
Давайте посмотрим на сайт Costco Travel. Форма на вкладке Прокат автомобилей запрашивает стандартную информацию:
- Выбрать место
- Место высадки (если отличается)
- Дата/время получения
- Дата/время возврата
Детали поездки
Предположим, мы планируем поездку в Калифорнию в 2018 году и летим в Лос-Анджелес.
Давайте определим наши критерии поиска:
Pickup location: LAX Dropoff location: LAX Pickup date: 3/22/2018 Pickup time: 10:00 AM Dropoff date: 3/26/2018 Dropoff time: 10:00 AM
Ниже мы видим, что самая низкая цена составляет 171 доллар.
Как работает форма
Когда страница, содержащая форму поиска, загружается, веб-сервер отправляет файл cookie в своем заголовке ответа с именем Csrf-token. Этот токен будет использоваться браузером во время отправки формы.
(Подробнее о CSRF читайте в статье Понимание и предотвращение CSRF-атак)
Кнопка «Найти мою машину» в форме поиска отправляет запрос POST на следующую страницу:
https://www.costcotravel.com/rentalCarSearch.act
Этот URL-адрес требует, чтобы отправка формы включала токен CSRF.
Однако есть страница, для которой не требуется токен CSRF, расположенная по адресу:
https://www.costcotravel.com/carSearch.act
Мы будем использовать вторую страницу для нашего решения.
Модули NPM
NPM — это менеджер пакетов, входящий в состав среды выполнения Node. Последнюю версию Node можно скачать здесь.
Мы будем использовать пару пакетов, чтобы сделать нашу жизнь проще:
- запрос — создавать http-запросы с данными формы без суеты
- cheerio — анализировать HTML-страницу, возвращенную с сервера, используя стандартный синтаксис селектора.
Их можно установить с помощью следующих команд в папке нашего проекта:
$ npm init
$ npm install request
$ npm install cheerio
Вот код, который делает всю тяжелую работу за нас.
index.js
'use strict';
console.log("Loading function"); const request = require('request'); const cheerio = require('cheerio'); const AWS = require("aws-sdk");
AWS.config.region = 'us-east-1';
const formData = { 'cs': 1, 'pickupCity': process.env.AIRPORT, 'pickupAsAirport': 'True', 'dropoffCity': process.env.AIRPORT, 'dropoffAsAirport': 'True', 'pickupDate': process.env.PICKUP_DATE, 'dropoffDate': process.env.DROPOFF_DATE, 'pickupTime': process.env.TIME, 'dropoffTime': process.env.TIME, 'driverAge': 25 };
const url = 'https://www.costcotravel.com/carSearch.act';
const options = { 'url': url, 'formData': formData, 'User-Agent': 'codebyamir.com' };
exports.handler = function (event, context, callback) { request.post(options, function reqCallback(err, httpResponse, body) { if (err) { const error = new Error("Form submission failed"); callback(error); }
// Load HTML page into cheerio const $ = cheerio.load(body); const prices = [];
// Populate array with prices $('div.carCell > a').each(function (index, element) { let price = parseFloat($(element).text().replace(/\$|,/g, '')); prices.push(price); });
// Find lowest price const lowestPrice = Math.min.apply(Math, prices);
const sns = new AWS.SNS();
const params = { Message: JSON.stringify('Lowest Costco rental car price: ' + lowestPrice), Subject: "SNS Notification from Lambda", TopicArn: process.env.SNS_TOPIC_ARN };
sns.publish(params, context.done);
}); };
Объяснение кода
Во-первых, мы вызываем метод require, чтобы сообщить Node, что мы хотим использовать пакеты для request, cheerio и AWS SDK.
const request = require('request');
const cheerio = require('cheerio');
const AWS = require("aws-sdk");
Далее мы настраиваем клиентский объект AWS для us-east-1.
AWS.config.region = 'us-east-1';
Мы объявляем объект formData для использования в запросе POST. Этот объект ссылается на некоторые переменные среды, которые мы определим в следующем разделе.
const formData = {
'cs': 1,
'pickupCity': process.env.AIRPORT,
'pickupAsAirport': 'True',
'dropoffCity': process.env.AIRPORT,
'dropoffAsAirport': 'True',
'pickupDate': process.env.PICKUP_DATE,
'dropoffDate': process.env.DROPOFF_DATE,
'pickupTime': process.env.TIME,
'dropoffTime': process.env.TIME,
'driverAge': 25
};
Затем мы определяем URL-адрес, на который мы хотим отправить форму, и объект параметров, который будет использоваться в запросе. User-Agent может быть любой произвольной строкой, и это обычно устанавливается нашим браузером. Поскольку здесь нет браузера, мы будем хорошими гражданами сети и установим его на имя нашего сайта, чтобы владельцы веб-сайтов могли видеть это в своих журналах.
const url = 'https://www.costcotravel.com/carSearch.act';
const options = { 'url': url, 'formData': formData, 'User-Agent': 'codebyamir.com' };
В следующем разделе кода объявляется функция-обработчик, необходимая для Lambda.
exports.handler = function (event, context, callback) {
Внутри функции-обработчика мы вызываем метод request.post() и передаем наш объект параметров и функцию обратного вызова.
Функция обратного вызова запроса определяет, что происходит при ошибке и успешном выполнении. В случае ошибки мы просто хотим вывести «Ошибка отправки формы» в обратный вызов Lambda.
request.post(options, function reqCallback(err, httpResponse, body) {
if (err) {
const error = new Error("Form submission failed");
callback(error);
}
В случае успеха мы хотим проанализировать тело ответа, содержащее наши цены. Однако эти цены скрыты внутри тонны HTML-разметки. Вот тут-то и появляется приветствие.
Мы загружаем тело ответа в cheerio. Затем мы используем стандартный синтаксис селектора CSS, чтобы получить цены и заполнить массив.
В ценах есть знаки доллара и запятые, которые мы отфильтровываем с помощью функции parseFloat().
const $ = cheerio.load(body); const prices = [];
$('div.carCell > a').each(function (index, element) { let price = parseFloat($(element).text().replace(/\$|,/g, '')); prices.push(price); });
const lowestPrice = Math.min.apply(Math, prices);
Наконец, мы можем создать SNS-клиент, определить его параметры и опубликовать сообщение в нашей теме. Это приводит к доставке SMS-сообщения всем подписчикам этой темы.
const sns = new AWS.SNS();
const params = { Message: JSON.stringify('Lowest Costco rental car price: ' + lowestPrice), Subject: "SNS Notification from Lambda", TopicArn: process.env.SNS_TOPIC_ARN };
sns.publish(params, context.done);
Селекторы DOM
Чтобы лучше объяснить, почему мы выбрали $('div.carCell > a')
, давайте взглянем на выдержку из тела ответа.
Мы видим, что цены расположены внутри якорных ссылок. Эти ссылки являются потомками элементов div класса carCell.
<tr> <th class="tar">Economy Car</th> <td class=""> <div class="carCell"> <a class="u linkredU">$210</a> </div> </td>
<td class=""> <div class="carCell"> <a class="u">$276</a> </div> </td>
<td class=""> <div class="carCell"> <a class="u ">$243</a> </div> </td>
<td class=""> <div class="carCell"> <a class="u " >$255</a> </div> </td> </tr>
Создайте пакет развертывания
Прежде чем мы сможем запустить наш код на Lambda, нам нужно создать пакет развертывания.
Это можно сделать, просто заархивировав содержимое каталога нашего проекта:
$ cd project
$ zip -r ../deployment.zip index.js package.json node_modules/
Пакет развертывания представляет собой ZIP-файл, который включает в себя наш файл index.js, package.json и каталог node_modules, содержащий наши зависимости.
Мы должны быть осторожны при упаковке содержимого каталога, а не самого каталога.
Таким образом, список содержимого ZIP будет выглядеть так:
index.js package.json node_modules/*
В каталоге node_modules будет куча всего, потому что request и cheerio имеют свои собственные зависимости.
Дополнительные сведения см. в разделе Создание пакета развертывания (Node.js) в Руководстве по Amazon Lambda для разработчиков.
Создайте лямбда-функцию
Теперь мы готовы создать нашу лямбда-функцию.
В консоли управления Lambda нажмите Создать функцию Lambda.
Выберите страницу чертежа
На странице Выберите схему нажмите Создать с нуля.
Настроить триггеры
На странице Настроить триггеры выберите CloudWatch Events в раскрывающемся списке.
Мы создадим новое правило CloudWatch, чтобы функция Lambda могла работать по расписанию.
Настроить функцию
Далее мы попадаем на страницу Настроить функцию. Здесь мы введем имя функции и выберем Node.js 6.10 в качестве среды выполнения.
В поле Код функции Lambda мы выбираем параметр Загрузить ZIP-файл, а затем нажимаем кнопку Загрузить. >кнопка. Затем мы выбираем наш файл пакета развертывания.
Lambda поддерживает переменные среды, поэтому мы будем использовать их для определения наших критериев поиска, а не жестко кодировать их в нашей функции. Мы также определим переменную для нашей темы SNS ARN, чтобы мы могли легко изменить ее позже.
Переменные среды
AIRPORT = LAX PICKUP_DATE = 03/22/2018 DROPOFF_DATE = 03/26/2018 TIME = 10:00 AM SNS_TOPIC_ARN = arn:aws:sns:us-east-1:000000000000:NotifyMe
В поле Обработчик функции Lambda и роль мы оставим имя обработчика как index.handler и выберем роль IAM, которая имеет разрешения на опубликовать в нашей теме SNS.
В поле Дополнительные настройки увеличим время ожидания до 10 секунд.
(По умолчанию 3 секунды, максимальное время ожидания 5 минут.)
На странице Обзор нажмите Создать функцию.
Тестирование
Теперь наша функция Lambda создана с отключенным триггером расписания.
Давайте протестируем функцию, нажав кнопку Тест.
Нажмите Сохранить и протестировать на следующем экране с запросом ввода для запуска функции.
Первоначально опубликовано на www.codebyamir.com 10 августа 2017 г.