Охотник за скидками: Лови выгодные предложения на Amazon с помощью Twilio

Автор: | 31.07.2026

Я обожаю выгодные покупки! А кто нет? Каждый раз, когда я что-то покупаю онлайн, я ищу купоны, промокоды или даже более низкие цены на других сайтах.

В девяти случаях из десяти я в итоге покупаю то, что хочу, на Amazon — это удобно, и обычно там дешевле.

Но покупать на Amazon, не пытаясь найти лучшие предложения, кажется мне немного неразумным. Amazon обновляет цены много раз в день, и, будучи экономным человеком, я хочу убедиться, что покупаю то, что хочу, по самой низкой цене, даже если придется немного подождать.

Чтобы решить эту проблему, я решил написать приложение, которое принимает URL товара на Amazon и затем периодически проверяет цены. Если оно обнаруживает, что цена стала ниже, чем в прошлый раз, приложение отправляет мне SMS с новой ценой и информацией о том, насколько она снизилась. Также оно предоставляет сокращенную ссылку на товар, чтобы я мог сразу его купить.

Для реализации этого «хака охотника за скидками» я использовал Visual Studio 2013 и несколько других инструментов:

— Номер Twilio (зарегистрируйте бесплатный аккаунт Twilio здесь)

— .NET 4.5 и ASP.NET MVC 5

— Entity Framework 6

— Аккаунт Amazon

— Аккаунт Bitly

Первые три пункта, скорее всего, уже есть в любой среде разработки .NET, но если нет, их можно скачать здесь. Кроме того, по ходу работы мы установим несколько пакетов *NuGet*.

Если не хотите набирать весь код вручную, можете скачать его здесь, и вам нужно будет только добавить несколько переменных окружения в свою систему.

Я уверен, что у вас уже есть аккаунт Amazon, но для этого хака нам понадобится доступ к их Product Advertising API для получения цен на товары. Зайдя на сайт, убедитесь, что нажали на ссылку *Зарегистрироваться сейчас* и завершили процесс регистрации, так как это понадобится для отправки запросов к API.

Для запросов к API требуется комбинация из трех учетных данных. Первые две можно получить, перейдя в консоль управления Amazon AWS и сгенерировав пару ключей.

После загрузки страницы нажмите кнопку *Создать новый ключ* и затем ссылку *Показать ключ доступа*.

Это даст вам *Идентификатор ключа доступа* и *Секретный ключ доступа*. Запишите оба, так как они нам скоро понадобятся.

Далее нужно получить третий учетный параметр — ваш партнерский *идентификатор отслеживания*. Его можно получить на странице Amazon Associates, войдя в систему с данными своего аккаунта Amazon. После входа скопируйте *Идентификатор отслеживания*, который отображается в верхнем левом углу.

Теперь у вас есть все учетные данные Amazon, необходимые для завершения этого хака.

Bitly — это сервис сокращения URL, который мы будем использовать, когда наше приложение обнаружит снижение цены.

Перейдите на сайт Bitly и войдите в систему. Вы можете использовать свой аккаунт Twitter или Facebook для входа или создать новый, если его еще нет.

После входа перейдите на страницу настроек и нажмите *Дополнительно*. Прокрутите страницу вниз и запишите свой *Логин* и *API-ключ*.

Начнем с создания нового решения в Visual Studio: нажмите *Новый проект*, в разделе *Visual C# > Web* выберите *Веб-приложение ASP.NET*. В полях *Имя* и *Имя решения* введите *BargainHunter*. Вы можете сохранить проект там, где обычно храните свой код, я сохранил его на *Рабочем столе*.

На следующем экране Visual Studio предложит выбрать тип проекта. Выберите шаблон *Пустой* и установите флажок *MVC*, чтобы создать пустой проект MVC.

После создания проекта щелкните правой кнопкой мыши на его названии в обозревателе решений и выберите *Управление пакетами NuGet*. Здесь мы установим все зависимости.

Начнем с установки Twilio — вспомогательной библиотеки, которая упростит взаимодействие с API Twilio.

Повторите процесс для следующих библиотек:

— EntityFramework — ORM, который поможет взаимодействовать с базой данных, где будут храниться наши предложения.

— Bitly.Net API Wrapper — обертка для API Bitly, которую мы будем использовать для генерации сокращенных URL.

— Hangfire — набор потрясающих библиотек с открытым исходным кодом, которые помогут создавать, обрабатывать и управлять фоновыми задачами.

— AmazonItemLookup — небольшая библиотека, упрощающая взаимодействие с Amazon Advertising API. Эта обертка не поддерживается официально Amazon.

После установки всех пакетов перейдите на вкладку *Обновления* и нажмите кнопку *Обновить все*. Это гарантирует, что мы используем последние версии всего необходимого.

Обратите внимание, что мы ссылаемся на *хранилище SQL Server*, которого пока нет и которое создадим в следующих шагах. Пока можно добавить запись для него в файл *web.config*, который находится в корне проекта. Добавьте следующую строку подключения перед закрывающим тегом:

Давайте начнем с создания базы данных, где будут храниться все наши предложения. Эти данные мы будем использовать для сравнения цен каждый раз, когда получаем новые цены с Amazon.

Щелкните правой кнопкой мыши на папке *app_data*, выберите *Добавить > Новый элемент… > База данных SQL Server*.

Назовите эту базу данных *BargainHunter.mdf* и нажмите *Добавить*.

Щелкните правой кнопкой мыши на новой базе данных в папке *app_data* и выберите *Открыть*. Это переключит вас в контекст *Обозревателя серверов*, где можно щелкнуть правой кнопкой мыши на *Таблицы* и выбрать *Добавить новую таблицу*.

В вкладке *T-SQL* введите следующий код вместо уже существующего:

Нажмите кнопку *Обновить*, чтобы создать таблицу.

Эта таблица содержит информацию о каждом из наших товаров, а также сокращенный URL, который будет отправлен нам, когда цены упадут ниже существующих.

После этого вернитесь в *Обозреватель решений* и щелкните правой кнопкой мыши на папке *Models*. Выберите *Добавить > Новый элемент…* и нажмите *Модель EDM ADO.NET*. Назовите ее *Deal* и нажмите *Добавить*.

На следующем экране вам будет предложено выбрать содержимое модели. Выберите *Конструктор EF из базы данных*, и это создаст модель на основе созданной нами таблицы. Нажмите *Далее*, и вы увидите информацию о том, что мы собираемся создать.

Нажмите *Далее* еще раз, и мастер предложит выбрать объекты базы данных, для которых нужно создать модели. Установите флажок *Таблицы* и нажмите *Готово*.

Вы попадете на экран с диаграммой нашей новой сущности, и если вы посмотрите в папку *Models*, то увидите файл *Deal.edmx*. Разверните этот файл, затем разверните *Deal.tt*, и вы увидите еще один файл *Deal.cs* с определениями нашей сущности.

Я обращаю ваше внимание на этот файл, потому что в его комментариях четко указано, что любые изменения в нем могут быть перезаписаны, если мы решим повторно сгенерировать класс *Deal*. Поэтому нужно убедиться, что мы не будем вносить изменения в этот файл, а сделаем их в другом месте. Но где?

Мы хотим убедиться, что наша модель имеет серверную валидацию на случай неправильно сформированного URL или если мы забудем добавить название предложения. Для этого создайте новый класс в папке *Models* под названием *Metadata.cs*. Мы можем использовать этот класс для добавления метаданных к любой из наших сущностей следующим образом:

Мы добавляем некоторые аннотации данных к свойствам нашей модели, чтобы изменить отображение меток при использовании их в представлениях. Мы также проверяем введенный текст и форматируем данные, чтобы они красиво отображались при возвращении из базы данных.

Но теперь нужно связать этот класс с нашей моделью, иначе это будет просто обычный класс. Для этого создайте еще один класс в папке *Models* под названием *PartialClasses.cs* и замените его содержимое следующим:

Приведенный выше фрагмент кода связывает наш класс *Deal* с классом *DealMetadata*, который находится в файле *Metadata.cs*. Для упрощения работы мы реализуем интерфейс *ICloneable*, так как он позволит нам клонировать нашу модель позже.

Давайте создадим контроллер, чтобы мы могли запустить наше приложение и посмотреть, как оно выглядит.

Щелкните правой кнопкой мыши на папке *Controllers* и выберите *Добавить > Контроллер… > Контроллер MVC 5 – Пустой > Добавить*. Назовите его *HomeController*, и для нас будет создан совершенно новый контроллер.

Для следующего шага нам нужно собрать проект, поэтому нажмите *CTRL+Shift+B*, чтобы собрать проект. Теперь разверните папку *Views* и щелкните правой кнопкой мыши на папке *Home*, затем выберите *Добавить > Представление…*

Вас попросят назвать это представление, мы назовем его *Index*. В разделе *Шаблон* выберите *List*, а в разделе *Класс модели* выберите *Deal* (*BargainHunter.Models*). В качестве *Класса контекста данных* выберите *BargainHunterEntities*. Снимите все флажки и нажмите *Добавить*.

Закомментируйте все ссылки действий на данный момент, чтобы ваш файл *Index.cshtml* выглядел так, как показано ниже:

Мы также внесли несколько изменений в стили нашей страницы, добавив *Bootstrap* и дополнительный класс к нашей таблице, чтобы ее было легче читать при большом количестве контента.

Вернемся к файлу *HomeController.cs* и изменим его, чтобы начать запрашивать базу данных с нашими сущностями.

Запустите проект, и вы должны увидеть пустую таблицу. Однако она не будет пустой долго, так как мы скоро начнем добавлять предложения в базу данных.

Мы создали все классы для отображения предложений, но пока не добавили ни одного предложения в нашу базу данных. Сейчас мы это исправим, создав два новых эндпоинта в файле *HomeController.cs*. Они будут обрабатывать отображение формы, которую мы будем использовать для добавления новых предложений, и в конечном итоге создавать их.

В приведенном выше коде есть несколько выделенных моментов, на которых, я думаю, стоит остановиться подробнее. Первый — это пользовательский модель-биндер, который мы создадим, чтобы помочь извлекать данные из нашей модели перед добавлением их в базу данных.

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

При отправке запросов к *Amazon Products Advertising API* нам нужно передать набор учетных данных, которые мы уже получили на предыдущих этапах, а также *ASIN* товара.

*ASIN* товара — это идентификатор продукта, и мы можем получить его прямо из URL товара. Мы хотим, чтобы наша форма была достаточно умной, чтобы автоматически извлекать *ASIN* из URL, если мы вставим ссылку, и наше приложение разберется во всем остальном.

Мы также хотим сохранить URL нашего товара в базе данных, который будет отправлен через SMS, когда цена на этот товар упадет ниже той, что хранится в базе данных. Пользовательский модель-биндер идеально подходит для этого, так как он будет перехватывать нашу форму, анализировать ее и получать необходимую информацию.

Начнем с создания нового каталога в корне нашего проекта под названием *Infrastructure*. Внутри этого каталога создайте новый файл класса под названием *DealModelBinder.cs*. Измените основной класс, чтобы он реализовывал интерфейс *IModelBinder* из *System.Web.Mvc*. Visual Studio предложит реализовать члены этого интерфейса, что мы сделаем, добавив новый метод *BindModel* в класс следующим образом:

В приведенном выше коде происходит многое, поэтому я выделил важные моменты и немного их объясню.

Модель-биндеры работают, перехватывая запрос, отправленный формой, и обрабатывая данные до того, как что-либо еще получит шанс их обработать. Представьте это как захват текущего запроса и внесение изменений до того, как кто-либо еще узнает о его существовании.

HTTP-запросы обычно завершаются так, как показано на изображении выше. Пользователь делает запрос, он перехватывается сервером, сервер взаимодействует с базой данных, база данных отвечает, и сервер сообщает пользователю, что запрос выполнен.

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

В нашем модель-биндере мы используем регулярное выражение для извлечения кода *ASIN* из URL, а затем используем оригинальный URL для создания сокращенной версии с помощью API Bitly. Убедитесь, что вы заменили ключи на те, которые скопировали ранее, или установили их в переменных окружения.

Затем мы возвращаем новую модель в наш контроллер, чтобы дать ему возможность снова пройти через нее и выполнить все проверки, описанные в аннотациях для этой модели. Если все работает так, как должно, наш контроллер вызывает метод *AddOrUpdateDeal*, который мы создадим далее, когда добавим вспомогательные классы в проект.

Создайте новый каталог под названием *Helpers* в корне проекта. В этом каталоге создайте новый класс под названием *AmazonHelper.cs* и добавьте в него следующее:

Этот класс взаимодействует с вспомогательной библиотекой напрямую, но предоставляет несколько вспомогательных методов, которые позволяют нам инициализировать его и выполнять поиск.

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

Обратите внимание, что я выделил *AwsServerUri*, так как это необязательный параметр. Вы можете полностью его опустить, если хотите, чтобы библиотека использовала по умолчанию Amazon.com. Список допустимых URL-адресов можно найти на этой странице.

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

Создайте новый файл класса под названием *TaskHelper.cs* в каталоге *Infrastructure*.

В начале класса создайте две новые переменные-члена *_amazonHelper* и *_twilio*. Они будут инициализировать наш класс *AmazonHelper* и библиотеку Twilio.

Создайте конструктор для класса и инициализируйте переменные-члены.

Как и раньше, вы можете заменить переменные окружения на свой *Account Sid* и *Auth Token*, если хотите.

Теперь мы очень близки к тому, чтобы иметь возможность создавать предложения и сохранять их в базе данных. Давайте создадим новый метод под названием *AddOrUpdateDeal*, который возвращает *Deal*, и добавим в него следующее.

В приведенном выше коде мы проверяем, существует ли предложение, и добавляем логику для добавления или обновления предложения в зависимости от результата. Если предложение не существует, мы знаем, что добавляем его, поэтому создаем его в базе данных, а затем создаем задачу, которая будет проверять это предложение, пока не обнаружит, что цена снизилась. Задача очень простая, и Hangfire будет управлять ею за нас. Создайте новый метод под названием *AddFetchTask* в классе.

Мы проверяем обновления предложений каждый час с помощью *Cron.Hourly*. Если вы хотите увеличить или уменьшить интервал между проверками, вы можете изменить это на любое из *minutely, hourly, daily, weekly, monthly* или даже использовать Cron-выражение для точности.

И наоборот, если предложение уже существует в базе данных, нам нужно обновить хранящуюся информацию о нем и проверить, стала ли его цена ниже той, что у нас уже есть.

Для этого мы используем *IsCheaperDeal* для сравнения цен и *NotifyCheaperDeal* для отправки SMS-уведомления с ссылкой, когда цена снизилась. Давайте реализуем эти два метода в том же классе.

Первый метод сравнивает две цены и возвращает булево значение, указывающее, стала ли новая цена ниже старой.

Метод *NotifyCheaperDeal* вызывается только в том случае, если цена снизилась, и использует метод *SendMessage* из библиотеки Twilio для отправки SMS-сообщения, сообщающего, что цена снизилась на определенную сумму. Удобно, что он также включает сокращенную ссылку на товар, так что даже если вы в пути, вы можете сразу же воспользоваться выгодным предложением на Amazon.

Добавим экземпляр *TaskHelper* в начало нашего класса контроллера. Таким образом, мы сможем использовать его внутри контроллера. Откройте *HomeController.cs* и добавьте следующее в самое начало класса.

У нас уже есть представление, которое позволяет нам видеть, что уже есть в базе данных, но теперь нам нужно создать представление, которое позволит добавлять новые элементы в нее. Откройте каталог *Views/Home* и создайте новое представление под названием *Create*.

Убедитесь, что вы указали *Шаблон* и *Класс модели*, соответствующие нашей существующей модели. Таким образом, форма будет создана автоматически.

В этом файле вы заметите, что созданы поля формы для *цены, URL* и *даты*. Мы их пока не знаем, поэтому удалите их из формы. В итоге она должна выглядеть так:

Я также добавил таблицу стилей и файл JavaScript, чтобы форма выглядела немного лучше.

Откройте файл *Index.cshtml* в каталоге *Views/Home* и раскомментируйте *Create ActionLink*, который мы закомментировали ранее.

Также добавьте следующий код сразу после тега body.

В следующий раз, когда вы создадите новое предложение, вы должны увидеть сообщение о подтверждении, что предложение создано.

Мы готовы снова запустить проект. При его запуске вы все еще должны видеть, что в таблице нет предложений, но когда вы нажмете *Создать новое*, вы попадете на форму, которая предложит вам добавить *URL Amazon* и *название предложения*. Здесь важно использовать URL с Amazon, так как именно оттуда мы получим код *ASIN*. Название предложения может быть любым, но убедитесь, что оно имеет для вас смысл.

Нажмите *создать*, и теперь ваше предложение будет добавлено в базу данных и отслеживаться.

Каждый раз, когда вы создаете новое предложение, метод *AddFetchTask* в классе *TaskHelper* также создает задачу. Чтобы увидеть эти задачи, откройте новую вкладку и перейдите по адресу http://localhost:[ПОРТ]/hangfire, и это приведет вас на панель управления Hangfire.

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

Для меня самое обидное — это когда я покупаю что-то и сразу после этого узнаю, что цена снизилась. Поэтому мы также реализуем функцию удаления в нашем приложении. Таким образом, мы сможем удалять предложения из базы данных после их покупки.

Для этого нам нужно изменить наш контроллер, добавив эндпоинт *Delete*, который будет удалять элемент из базы данных и, соответственно, удалять задачу, чтобы она не продолжала получать информацию для этой задачи.

И с этим наше предложение удаляется из базы данных, как и все связанные с ним повторяющиеся задачи. Все, что вам нужно сделать теперь, — это раскомментировать ссылку *Удалить* в *Index.cshtml*.

Экономить деньги непросто, но с этим небольшим хаком держать деньги в кошельке должно стать немного проще.

Мы перешли от необходимости вручную проверять цену товара несколько раз в день с риском переплатить за товар к тому, чтобы наше приложение выполняло за нас тяжелую работу и сообщало нам по SMS, когда цены снижаются.

Но это только верхушка айсберга, и мы могли бы легко добавить другие магазины и изменить приложение так, чтобы оно позволяло обновлять повторяющиеся задачи для каждого товара. Мы также могли бы настроить его так, чтобы оно сообщало нам только тогда, когда цены снижаются на определенный процент, чтобы максимизировать нашу экономию.

Мне бы хотелось узнать, как вы измените этот хак, чтобы он работал для ваших конкретных нужд. Свяжитесь со мной в Twitter @marcos_placona, по электронной почте marcos@twilio.com или MarcosPlacona в G+, чтобы рассказать об этом подробнее.