¿Usar contenedores para... llevar la cuenta de los precios de los regalos de Navidad?
La Navidad está a la vuelta de la esquina y con ella llega la feliz tarea de encontrar el regalo perfecto para nuestros seres queridos. Si eres como yo, probablemente te encantará conseguir una buena oferta, pero navegar por los precios vertiginosos de artículos populares durante la temporada navideña puede parecer como un paseo en trineo en la vida real: ¡emocionante pero un poco abrumador! Este año, en lugar de volverme loco con comprobaciones diarias de precios, decidí darle un buen uso a mis habilidades tecnológicas y automatizar el proceso.

Como desarrollador de software y apasionado por aprovechar la tecnología para resolver problemas cotidianos, encontré una manera de automatizar el proceso de verificación de precios. En lugar de visitar manualmente varias tiendas en línea todos los días para comparar precios, decidí crear un sistema que pueda hacerlo por mí. Esto no sólo ahorra tiempo, sino que también garantiza que obtenga las mejores ofertas sin el estrés de las comprobaciones diarias.
¿Por qué automatizar el seguimiento de precios?
Automatizar el seguimiento de precios es como tener tu propio equipo de duendes de Papá Noel trabajando las 24 horas del día para garantizar que obtengas las mejores ofertas sin mover un dedo. He aquí por qué te encantará:
- Artículos caros: Todos sabemos que los regalos de Navidad pueden resultar bastante caros, especialmente cuando hay gadgets y juguetes en tu lista. Ahorrar dinero en estos puede ser tan satisfactorio como encontrar la última copa de árbol disponible.
- Revisiones diarias: ¿Quién tiene el tiempo (o la paciencia) para consultar los precios todos los días? ¡Yo no!
- Automatización: Abrace el espíritu navideño de dar: regálese la eficiencia.
- Precisión: la automatización garantiza que sus comprobaciones de precios sean tan precisas como brillante es la nariz de Rudolph.
Tecnologías centrales
Para construir mi propio pequeño ayudante de Papá Noel, decidí utilizar varias tecnologías clave:
Contenedores
Los contenedores son como el envoltorio navideño del software. Agrupan sus aplicaciones con todos sus beneficios (dependencias) para que todo funcione tan fluidamente como un paseo en trineo sobre nieve fresca. Docker es nuestra opción para crear estos contenedores.
Blazor
Blazor es un marco genial para crear aplicaciones web interactivas utilizando .NET. Es como reemplazar villancicos genéricos con tu propia lista de reproducción navideña: personalizada, eficiente y muy divertida.
Docker-Componer
Docker-Compose es el administrador de operaciones de nuestro Polo Norte. Nos ayuda a mantener todos nuestros servicios, como la API y el front-end de Blazor, funcionando juntos en perfecta armonía. Piense en él como el director de nuestra sinfonía navideña.
Guía paso a paso
¡Ahora, sumergámonos en el taller de Santa y démosle vida a este proyecto!
Paso 1: Crear la API
1.1 Configuración del proyecto
Ponte tu gorro de Papá Noel de codificación y configura un nuevo proyecto de API web ASP.NET Core. Abre tu terminal (es un poco como abrir tu calendario de adviento) y ejecuta:
dotnet new webapi -o PriceMonitorApi
cd PriceMonitorApi
Este comando crea un nuevo directorio llamado PriceMonitorApi y configura un proyecto API web básico. Imagínese que es como crear una base sólida para su casa de pan de jengibre.
1.2 Agregar HttpClient y bibliotecas de scrapingA continuación, agregue HttpClient y una biblioteca para analizar HTML. Este será nuestro trineo confiable para buscar y leer datos de precios.
dotnet add package HtmlAgilityPack
HtmlAgilityPack es el elfo que nos ayuda a analizar documentos HTML.
1.3 Creando modelos
Los modelos son como el modelo de nuestros juguetes. Creemos un modelo para representar la información del producto:
namespace PriceMonitorApi.Models
{
public class ProductInfo
{
public string Title { get; set; }
public decimal Price { get; set; }
public DateTime Date { get; set; }
}
}
Acabamos de crear la plantilla perfecta para nuestros datos.
1.4 Implementación del servicio Scraper
Nuestro servicio scraper es como el pequeño ayudante de Papá Noel: busca y procesa información para nosotros:
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 fragmento convierte nuestro HtmlAgilityPack en una varita mágica navideña.
1.5 Creación del controlador API
Creemos un controlador que actuará como guardián de nuestros datos:
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 });
}
}
}
}
Nuestro controlador garantiza que los datos se entreguen más rápido que Papá Noel por la chimenea.
1.6 Registro de servicios en contenedor DI
Por último, registra tu ScraperService para asegurarte de que esté disponible cuando sea necesario.
services.AddHttpClient<ScraperService>();
Ahora su API está lista, ¡como el trineo de Papá Noel en Nochebuena!
Paso 2: Creación de la aplicación Blazor
Blazor nos ayuda a decorar nuestro proyecto como un árbol de Navidad, haciéndolo visualmente atractivo e interactivo.
2.1 Configuración del proyecto Blazor
A continuación, crearemos un proyecto Blazor que actúa como interfaz para nuestro paseo en trineo de seguimiento de precios.
dotnet new blazor -o PriceMonitorBlazor
cd PriceMonitorBlazor
Este comando agrega algo de magia navideña para configurar un proyecto básico de Blazor WebAssembly.
2.2 Agregar modelos
Al igual que configurar adornos, agregue un modelo ProductInfo en el proyecto Blazor:
namespace PriceMonitorBlazor.Models
{
public class ProductInfo
{
public string Title { get; set; }
public decimal Price { get; set; }
public DateTime Date { get; set; }
}
}
2.3 Creación del servicio para llamadas API
Cree un servicio para obtener datos de nuestra API; considérelo como nuestro compañero de compras en línea:
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 Registro de servicios en contenedor DI
Asegúrese de que ScraperService esté registrado para que podamos inyectarlo en nuestros componentes.
builder.Services.AddScoped<ScraperService>();
2.5 Creación del componente Blazor
Actualice Pages/Index.razor para incluir una interfaz divertida e interactiva:
@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);
}
}
}
Es como montar una exhibición festiva: hacer que nuestra aplicación sea interactiva y agradable.
Paso 3: Conectar todo usando Docker-Compose
Ahora conectemos todo usando Docker-Compose, convirtiendo nuestro proyecto en un paseo en trineo bien engrasado.
3.1 Creando archivos Docker
Cree Dockerfiles para los proyectos API y 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 Creando docker-compose.yml
Conecta todos los puntos (luces navideñas) con un solo archivo 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
A continuación se muestran los diagramas para ayudar a visualizar los diferentes componentes y el flujo de datos, esto nos ayudará a comprender lo que realmente está sucediendo en nuestro mundo santa:
Diagrama de arquitectura del sistema

Diagrama de flujo de datos

Diagrama de secuencia

Diagrama de componentes para la configuración de Docker

Paso 4: Gestión de URL, actualización y actualización periódica automatizadaEn esta demostración simplemente lo almacenaremos en la memoria, pero en una aplicación real, usarías una base de datos. Para este divertido proyecto, centrémonos en el almacenamiento en memoria.
4.1 Modificación del servicio para administrar URL
Primero, nos aseguraremos de que nuestro servicio pueda agregar y recuperar URL, así como también obtener información del producto:
Actualización 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 Actualización del componente Blazor para administración y actualización de URL
A continuación, actualice Pages/Index.razor para agregar administración de URL, actualización y actualización 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();
}
}
En este componente actualizado:
- Usamos un
Timerpara llamar periódicamente al métodoRefreshPricescada minuto. - El método
StartTimerinicializa el temporizador para que comience inmediatamente y luego se active cada 60 segundos. - El método de ciclo de vida
OnInitializedllama aStartTimercuando el componente se inicializa para iniciar la actualización periódica.
4.3 Ejecutando la solución
Para ejecutar la aplicación Blazor actualizada con las nuevas funciones, reconstruya y reinicie sus contenedores Docker:
docker-compose build
docker-compose up
Después de cargar http://localhost:5001 en su navegador, la aplicación Blazor ahora debería actualizar automáticamente los precios de los productos cada minuto, además de permitir actualizaciones manuales y administración de URL.
Conclusión
¡Construir este sistema de seguimiento de precios ha sido una maravilla festiva! No sólo me salvó del estrés de las comprobaciones diarias de precios, sino que también mostró la magia de las tecnologías web modernas.
Calendario tecnológico festivo 2024

Creé esta publicación como parte del evento Festive Tech Calendar 2024, que reúne a entusiastas de la tecnología, innovadores y soñadores digitales para compartir conocimientos y celebrar la fusión del espíritu festivo y las maravillas tecnológicas. Esta iniciativa no se trata solo de aprender y conectarse, sino también de retribuir.
Festive Tech Calendar 2024 apoya a Beatson Cancer Charity este año. Beatson Cancer Charity se dedica a apoyar a las personas afectadas por el cáncer, a sus familias y a los profesionales de la salud que los cuidan. Puede encontrar más información sobre su increíble trabajo en https://www.beatsoncancercharity.org/.
Visite el sitio web de Festive Tech Calendar en https://festivetechcalendar.com para consultar las preguntas frecuentes y obtener más detalles sobre el evento.
¡HO HO HO!
Crear este proyecto ha sido una manera maravillosa de contribuir a la comunidad tecnológica festiva y al mismo tiempo apoyar una gran causa.
Espero que esta guía le haya resultado útil y que le inspire a explorar más formas de utilizar la tecnología para simplificar las tareas cotidianas.
Si tiene alguna pregunta o necesita más ayuda, no dude en comunicarse.
¡Feliz codificación!