Usando contêineres para... acompanhar os preços dos presentes de Natal?

· 10 min de leitura

O Natal está chegando e com ele vem a alegre tarefa de encontrar os presentes perfeitos para seus entes queridos. Se você for como eu, provavelmente adora fazer um bom negócio, mas navegar pelos preços exorbitantes de itens populares durante as festas de fim de ano pode parecer um passeio de trenó na vida real - emocionante, mas um pouco cansativo! Este ano, em vez de enlouquecer com verificações diárias de preços, decidi usar bem minhas habilidades tecnológicas e automatizar o processo.

Como desenvolvedor de software e apaixonado por aproveitar a tecnologia para resolver problemas do dia a dia, encontrei uma maneira de automatizar o processo de verificação de preços. Em vez de visitar manualmente várias lojas online todos os dias para comparar preços, decidi criar um sistema que possa fazer isso por mim. Isso não apenas economiza tempo, mas também garante que eu obtenha as melhores ofertas sem o estresse das verificações diárias.

Por que automatizar o monitoramento de preços?

Automatizar o monitoramento de preços é como ter sua própria equipe de duendes do Papai Noel trabalhando 24 horas por dia para garantir que você obtenha as melhores ofertas sem mexer um dedo. Veja por que você vai adorar:

  1. Itens caros: Todos nós sabemos que os presentes de Natal podem ficar muito caros, especialmente quando gadgets e brinquedos estão em sua lista. Economizar dinheiro com isso pode ser tão satisfatório quanto encontrar a última árvore em estoque.
  2. Verificações Diárias: Quem tem tempo (ou paciência) para verificar preços todos os dias? Eu não!
  3. Automação: abrace o espírito natalino de doação – dê a si mesmo o presente da eficiência.
  4. Precisão: A automação garante que suas verificações de preços sejam tão precisas quanto o nariz de Rudolph é brilhante.

Tecnologias principais

Para construir meu próprio ajudante do Papai Noel, decidi usar várias tecnologias importantes:

Contêineres

Os contêineres são como o embrulho de Natal do software. Eles agrupam seus aplicativos com todas as suas vantagens (dependências) para que tudo funcione tão bem quanto um passeio de trenó na neve fresca. Docker é nossa escolha para criar esses contêineres.

Blazor

Blazor é uma estrutura interessante para construir aplicativos da web interativos usando .NET. É como substituir canções de Natal genéricas pela sua própria lista de reprodução de férias - personalizada, eficiente e muito divertida.

Docker-Compose

Docker-Compose é o gestor da operação do nosso Pólo Norte. Isso nos ajuda a manter todos os nossos serviços – como a API e o front-end do Blazor – funcionando juntos em perfeita harmonia. Pense nele como o maestro da nossa sinfonia natalina.

Guia passo a passo

Agora vamos mergulhar na oficina do Papai Noel e dar vida a esse projeto!

Etapa 1: Criando a API

1.1 Configurando o Projeto

Coloque seu chapéu de Papai Noel de codificação e configure um novo projeto de API Web ASP.NET Core. Abra seu terminal (é como abrir seu calendário do advento) e execute:

dotnet new webapi -o PriceMonitorApi
cd PriceMonitorApi

Este comando cria um novo diretório chamado PriceMonitorApi e configura um projeto básico de API da Web. Imagine que é como criar uma base robusta para a sua casa de pão de gengibre.

1.2 Adicionando bibliotecas HttpClient e ScrapingEm seguida, adicione HttpClient e uma biblioteca para analisar HTML. Este será nosso trenó confiável para buscar e ler dados de preços.

dotnet add package HtmlAgilityPack

HtmlAgilityPack é o elfo que nos ajuda a analisar documentos HTML.

1.3 Criando Modelos

Os modelos são como o modelo dos nossos brinquedos. Vamos criar um modelo para representar as informações do produto:

namespace PriceMonitorApi.Models
{
    public class ProductInfo
    {
        public string Title { get; set; }
        public decimal Price { get; set; }
        public DateTime Date { get; set; }
    }
}

Acabamos de criar o modelo perfeito para nossos dados.

1.4 Implementando o serviço Scraper

Nosso serviço de scraper é como o ajudante do Papai Noel – buscando e processando informações para nós:

using HtmlAgilityPack;
using PriceMonitorApi.Models;
using System.Globalization;

namespace PriceMonitorApi.Services
{
    public class ScraperService
    {
        private readonly HttpClient _httpClient;

        public ScraperService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<ProductInfo> ScrapeProductInfoAsync(string url)
        {
            var response = await _httpClient.GetStringAsync(url);

            var htmlDoc = new HtmlDocument();
            htmlDoc.LoadHtml(response);

            var title = htmlDoc.DocumentNode.SelectSingleNode("//h1[@class='product-title']").InnerText.Trim();
            var priceString = htmlDoc.DocumentNode.SelectSingleNode("//span[@class='product-price']").InnerText.Trim();

            if (decimal.TryParse(priceString, NumberStyles.Currency, CultureInfo.InvariantCulture, out var price))
            {
                return new ProductInfo
                {
                    Title = title,
                    Price = price,
                    Date = DateTime.UtcNow
                };
            }

            throw new Exception("Unable to parse price");
        }
    }
}

Este trecho transforma nosso HtmlAgilityPack em uma varinha mágica de férias.

1.5 Criando o controlador de API

Vamos criar um controlador que atuará como o guardião de nossos dados:

using Microsoft.AspNetCore.Mvc;
using PriceMonitorApi.Models;
using PriceMonitorApi.Services;

namespace PriceMonitorApi.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class ScraperController : ControllerBase
    {
        private readonly ScraperService _scraperService;

        public ScraperController(ScraperService scraperService)
        {
            _scraperService = scraperService;
        }

        [HttpGet("productinfo")]
        public async Task<ActionResult<ProductInfo>> GetProductInfo(string url)
        {
            try
            {
                var productInfo = await _scraperService.ScrapeProductInfoAsync(url);
                return Ok(productInfo);
            }
            catch (Exception ex)
            {
                return BadRequest(new { Message = ex.Message });
            }
        }
    }
}

Nosso controlador garante que os dados sejam entregues mais rápido do que o Papai Noel pela chaminé.

1.6 Registrando serviços no contêiner DI

Por último, registre seu ScraperService para garantir que ele esteja disponível quando necessário.

services.AddHttpClient<ScraperService>();

Agora sua API está pronta, como o trenó do Papai Noel na véspera de Natal!


Etapa 2: Criando o aplicativo Blazor

Blazor nos ajuda a decorar nosso projeto como uma árvore de Natal – tornando-o visualmente atraente e interativo.

2.1 Configurando o projeto Blazor

A seguir, criaremos um projeto Blazor que atua como interface para nosso passeio de trenó de monitoramento de preços.

dotnet new blazor -o PriceMonitorBlazor
cd PriceMonitorBlazor

Este comando espalha um pouco da magia do feriado para configurar um projeto básico do Blazor WebAssembly.

2.2 Adicionando modelos

Assim como configurar enfeites, adicione um modelo ProductInfo no projeto Blazor:

namespace PriceMonitorBlazor.Models
{
    public class ProductInfo
    {
        public string Title { get; set; }
        public decimal Price { get; set; }
        public DateTime Date { get; set; }
    }
}

2.3 Criando o serviço para chamadas de API

Crie um serviço para buscar dados de nossa API – pense nele como nosso companheiro de compras online:

using PriceMonitorBlazor.Models;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

namespace PriceMonitorBlazor.Services
{
    public class ScraperService
    {
        private readonly HttpClient _httpClient;

        public ScraperService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<ProductInfo> GetProductInfoAsync(string url)
        {
            var response = await _httpClient.GetFromJsonAsync<ProductInfo>($"https://localhost:5001/api/scraper/productinfo?url={url}");
            return response;
        }
    }
}

2.4 Registrando serviços no DI Container

Certifique-se de que ScraperService esteja registrado para que possamos injetá-lo em nossos componentes.

builder.Services.AddScoped<ScraperService>();

2.5 Criando o componente Blazor

Atualize Pages/Index.razor para incluir uma interface divertida e interativa:

@page "/"
@using PriceMonitorBlazor.Models
@using PriceMonitorBlazor.Services
@inject ScraperService ScraperService

<h3>Price Monitor</h3>

<p>
    <label for="urlInput">Product URL:</label>
    <input id="urlInput" @bind="productUrl" />
    <button @onclick="FetchProductInfo">Fetch Price</button>
</p>

@if (productInfos.Any())
{
    <table class="table">
        <thead>
            <tr>
                <th>Title</th>
                <th>Price</th>
                <th>Date</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in productInfos)
            {
                <tr>
                    <td>@product.Title</td>
                    <td>@product.Price</td>
                    <td>@product.Date</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private string productUrl;
    private List<ProductInfo> productInfos = new List<ProductInfo>();

    private async Task FetchProductInfo()
    {
        if (!string.IsNullOrEmpty(productUrl))
        {
            var productInfo = await ScraperService.GetProductInfoAsync(productUrl);
            productInfos.Add(productInfo);
        }
    }
}

É como montar uma exibição festiva de feriado – tornando nosso aplicativo interativo e agradável.


Etapa 3: Conectando tudo usando Docker-Compose

Agora vamos conectar tudo usando Docker-Compose, transformando nosso projeto em um passeio de trenó bem lubrificado.

3.1 Criando arquivos Docker

Crie Dockerfiles para os projetos API e Blazor:

PriceMonitorApi/Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["PriceMonitorApi/PriceMonitorApi.csproj", "PriceMonitorApi/"]
RUN dotnet restore "PriceMonitorApi/PriceMonitorApi.csproj"
COPY . .
WORKDIR "/src/PriceMonitorApi"
RUN dotnet build "PriceMonitorApi.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "PriceMonitorApi.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PriceMonitorApi.dll"]

PriceMonitorBlazor/Dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["PriceMonitorBlazor/PriceMonitorBlazor.csproj", "PriceMonitorBlazor/"]
RUN dotnet restore "PriceMonitorBlazor/PriceMonitorBlazor.csproj"
COPY . .
WORKDIR "/src/PriceMonitorBlazor"
RUN dotnet build "PriceMonitorBlazor.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "PriceMonitorBlazor.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PriceMonitorBlazor.dll"]

3.2 Criando docker-compose.yml

Conecte todos os pontos (luzes de Natal) com um único arquivo docker-compose.yml:

version: '3.4'

services:
  api:
    image: pricemonitorapi
    build:
      context: ./PriceMonitorApi
      dockerfile: Dockerfile
    ports:
      - "5000:80"
    networks:
      - price-monitor-network

  blazor:
    image: pricemonitorblazor
    build:
      context: ./PriceMonitorBlazor
      dockerfile: Dockerfile
    ports:
      - "5001:80"
    depends_on:
      - api
    networks:
      - price-monitor-network

networks:
  price-monitor-network:
    driver: bridge

Diagramas

Abaixo estão os diagramas para ajudar a visualizar os diferentes componentes e o fluxo de dados, isso nos ajudará a entender o que realmente está acontecendo em nosso mundo papai noel:

Diagrama de Arquitetura do Sistema

Imgur

Diagrama de Fluxo de Dados

Imgur

Diagrama de sequência

Imgur

Diagrama de componentes para configuração do Docker

Imgur

Etapa 4: gerenciamento de URL, atualização e atualização periódica automatizadaNesta demonstração iremos apenas armazená-lo na memória, mas em uma aplicação real, você usaria um banco de dados. Para este projeto divertido, vamos nos limitar ao armazenamento na memória.

4.1 Modificando o serviço para gerenciar URLs

Primeiro, garantiremos que nosso serviço possa adicionar e recuperar URLs, bem como buscar informações de produtos:

Atualização Services/ScraperService.cs:

using PriceMonitorBlazor.Models;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

namespace PriceMonitorBlazor.Services
{
    public class ScraperService
    {
        private readonly HttpClient _httpClient;
        private List<string> urls = new List<string>();

        public ScraperService(HttpClient httpClient)
        {
            _httpClient = httpClient;
        }

        public async Task<ProductInfo> GetProductInfoAsync(string url)
        {
            var response = await _httpClient.GetFromJsonAsync<ProductInfo>($"http://localhost:5000/api/scraper/productinfo?url={url}");
            return response;
        }

        public void AddUrl(string url)
        {
            if (!urls.Contains(url))
            {
                urls.Add(url);
            }
        }

        public List<string> GetUrls()
        {
            return urls;
        }

        public void ClearUrls()
        {
            urls.Clear();
        }
    }
}

4.2 Atualizando o componente Blazor para gerenciamento e atualização de URL

Em seguida, atualize Pages/Index.razor para adicionar gerenciamento de URL, atualização e atualização periódica automatizada:

@page "/"
@using PriceMonitorBlazor.Models
@using PriceMonitorBlazor.Services
@inject ScraperService ScraperService

<h3>Price Monitor</h3>

<p>
    <label for="urlInput">Product URL:</label>
    <input id="urlInput" @bind="productUrl" />
    <button @onclick="AddUrl">Add URL</button>
</p>

<p>
    <button @onclick="RefreshPrices">Refresh All Prices</button>
</p>

@if (productInfos.Any())
{
    <table class="table">
        <thead>
            <tr>
                <th>Title</th>
                <th>Price</th>
                <th>Date</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in productInfos)
            {
                <tr>
                    <td>@product.Title</td>
                    <td>@product.Price</td>
                    <td>@product.Date</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private string productUrl;
    private List<ProductInfo> productInfos = new List<ProductInfo>();
    private Timer _timer;

    protected override void OnInitialized()
    {
        StartTimer();
    }

    private void StartTimer()
    {
        // Set up the timer to call RefreshPrices every minute (60000 ms)
        _timer = new Timer(async _ => await InvokeAsync(RefreshPrices), null, TimeSpan.Zero, TimeSpan.FromMinutes(1));
    }

    private void AddUrl()
    {
        if (!string.IsNullOrEmpty(productUrl))
        {
            ScraperService.AddUrl(productUrl);
            productUrl = string.Empty;
        }
    }

    private async Task RefreshPrices()
    {
        productInfos.Clear();
        var urls = ScraperService.GetUrls();
        
        foreach (var url in urls)
        {
            var productInfo = await ScraperService.GetProductInfoAsync(url);
            productInfos.Add(productInfo);
        }
        StateHasChanged();
    }
}

Neste componente atualizado:

  • Usamos um Timer para chamar periodicamente o método RefreshPrices a cada minuto.
  • O método StartTimer inicializa o cronômetro para iniciar imediatamente e dispara a cada 60 segundos.
  • O método de ciclo de vida OnInitialized chama StartTimer quando o componente é inicializado para iniciar a atualização periódica.

4.3 Executando a solução

Para executar o aplicativo Blazor atualizado com os novos recursos, recompile e reinicie os contêineres do Docker:

docker-compose build
docker-compose up

Depois de carregar http://localhost:5001 em seu navegador, o aplicativo Blazor agora deve atualizar automaticamente os preços dos produtos a cada minuto, além de permitir atualizações manuais e gerenciamento de URL.

Conclusão

Construir este sistema de monitoramento de preços foi uma festa festiva! Isso não apenas me salvou do estresse das verificações diárias de preços, mas também mostrou a magia das modernas tecnologias da web.

Calendário Festivo de Tecnologia 2024

Criei esta postagem como parte do evento Festive Tech Calendar 2024, que reúne entusiastas da tecnologia, inovadores e sonhadores digitais para compartilhar conhecimento e celebrar a fusão do espírito festivo e das maravilhas tecnológicas. Esta iniciativa não visa apenas aprender e conectar-se, mas também retribuir.

Festive Tech Calendar 2024 está apoiando a Beatson Cancer Charity este ano. A Beatson Cancer Charity dedica-se a apoiar as pessoas afetadas pelo câncer, suas famílias e os profissionais de saúde que cuidam delas. Mais informações sobre seu trabalho incrível podem ser encontradas em https://www.beatsoncancercharity.org/.

Confira o site do Festive Tech Calendar em https://festivetechcalendar.com para perguntas frequentes e mais detalhes sobre o evento.

HO HO HO!

Criar este projeto foi uma maneira maravilhosa de contribuir para a festiva comunidade tecnológica e, ao mesmo tempo, apoiar uma grande causa.

Espero que você tenha achado este guia útil e que o inspire a explorar mais maneiras de usar a tecnologia para simplificar as tarefas diárias.

Se você tiver alguma dúvida ou precisar de mais assistência, não hesite em entrar em contato.

Boa codificação!