Отправка уведомлений о ценах с помощью 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 г.