Benutzen Sie Behälter, um... den Überblick über die Preise der Weihnachtsgeschenke zu behalten?

· 10 Min. Lesezeit

Weihnachten steht vor der Tür und mit ihm die freudige Aufgabe, die perfekten Geschenke für Ihre Lieben zu finden. Wenn Sie so sind wie ich, freuen Sie sich wahrscheinlich über gute Schnäppchen, aber während der Feiertage durch die explodierenden Preise beliebter Artikel zu navigieren, kann sich wie eine echte Schlittenfahrt anfühlen – aufregend, aber auch ein wenig überwältigend! Anstatt mich dieses Jahr mit täglichen Preisüberprüfungen in den Wahnsinn zu treiben, habe ich beschlossen, meine technischen Fähigkeiten sinnvoll einzusetzen und den Prozess zu automatisieren.

Als Softwareentwickler und jemand, der sich leidenschaftlich für die Nutzung von Technologie zur Lösung alltäglicher Probleme einsetzt, habe ich einen Weg gefunden, den Prozess der Preisprüfung zu automatisieren. Anstatt jeden Tag manuell mehrere Online-Shops zu besuchen, um die Preise zu vergleichen, habe ich beschlossen, ein System zu entwickeln, das das für mich erledigt. Das spart nicht nur Zeit, sondern stellt auch sicher, dass ich die besten Angebote ohne den Stress täglicher Kontrollen erhalte.

Warum Preisüberwachung automatisieren?

Die Automatisierung der Preisüberwachung ist so, als ob Ihr eigenes Team von Weihnachtswichteln rund um die Uhr arbeitet, um sicherzustellen, dass Sie die besten Angebote erhalten, ohne einen Finger zu rühren. Deshalb werden Sie es lieben:

  1. Teure Artikel: Wir alle wissen, dass Weihnachtsgeschenke ziemlich teuer sein können, besonders wenn Gadgets und Spielzeug auf Ihrer Liste stehen. Hier Geld zu sparen kann so befriedigend sein, wie die Suche nach der letzten Baumkrone auf Lager.
  2. Tägliche Kontrollen: Wer hat die Zeit (oder die Geduld), jeden Tag die Preise zu überprüfen? Nicht ich!
  3. Automatisierung: Machen Sie sich den Urlaubsgeist des Schenkens zu eigen – schenken Sie sich selbst das Geschenk der Effizienz.
  4. Genauigkeit: Die Automatisierung stellt sicher, dass Ihre Preisprüfungen so genau sind, wie Rudolphs Nase glänzt.

Kerntechnologien

Um den kleinen Helfer meines eigenen Weihnachtsmanns zu bauen, habe ich mich für den Einsatz mehrerer Schlüsseltechnologien entschieden:

Container

Container sind wie die Weihnachtsverpackung für Software. Sie bündeln Ihre Anwendungen mit all ihren Extras (Abhängigkeiten), sodass alles so reibungslos läuft wie eine Schlittenfahrt im Neuschnee. Docker ist unsere Anlaufstelle für die Erstellung dieser Container.

Blazor

Blazor ist ein cooles Framework zum Erstellen interaktiver Webanwendungen mit .NET. Es ist, als würden Sie generische Weihnachtslieder durch Ihre ganz eigene Weihnachts-Playlist ersetzen – maßgeschneidert, effizient und so viel Spaß.

Docker-Compose

Docker-Compose ist der Manager unseres Nordpolbetriebs. Es hilft uns, dafür zu sorgen, dass alle unsere Dienste – wie die API und das Blazor-Frontend – in perfekter Harmonie zusammenlaufen. Betrachten Sie ihn als den Dirigenten unserer Feiertagssymphonie.

Schritt-für-Schritt-Anleitung

Tauchen wir nun in die Werkstatt des Weihnachtsmanns ein und erwecken wir dieses Projekt zum Leben!

Schritt 1: Erstellen der API

1.1 Einrichten des Projekts

Setzen Sie Ihre Programmier-Weihnachtsmütze auf und richten Sie ein neues ASP.NET Core-Web-API-Projekt ein. Öffnen Sie Ihr Terminal (es ist ein bisschen so, als würden Sie Ihren Adventskalender öffnen) und führen Sie Folgendes aus:

dotnet new webapi -o PriceMonitorApi
cd PriceMonitorApi

Dieser Befehl erstellt ein neues Verzeichnis mit dem Namen PriceMonitorApi und richtet ein grundlegendes Web-API-Projekt ein. Stellen Sie sich vor, es wäre so, als würden Sie eine stabile Basis für Ihr Lebkuchenhaus schaffen.

1.2 HttpClient- und Scraping-Bibliotheken hinzufügenAls nächstes fügen Sie HttpClient und eine Bibliothek zum Parsen von HTML hinzu. Dies wird unser treuer Schlitten zum Abrufen und Lesen von Preisdaten sein.

dotnet add package HtmlAgilityPack

HtmlAgilityPack ist der Elf, der uns beim Parsen von HTML-Dokumenten hilft.

1.3 Modelle erstellen

Modelle sind wie die Blaupause für unser Spielzeug. Erstellen wir ein Modell zur Darstellung der Produktinformationen:

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

Wir haben gerade die perfekte Vorlage für unsere Daten erstellt.

1.4 Implementierung des Scraper-Dienstes

Unser Scraper-Service ist wie der kleine Helfer des Weihnachtsmanns – er holt und verarbeitet Informationen für uns:

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");
        }
    }
}

Dieser Ausschnitt verwandelt unseren HtmlAgilityPack in einen Weihnachtszauberstab.

1.5 Erstellen des API-Controllers

Erstellen wir einen Controller, der als Gatekeeper für unsere Daten fungiert:

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 });
            }
        }
    }
}

Unser Controller stellt sicher, dass Daten schneller geliefert werden als der Weihnachtsmann.

1.6 Dienste im DI-Container registrieren

Registrieren Sie abschließend Ihr ScraperService, um sicherzustellen, dass es bei Bedarf verfügbar ist.

services.AddHttpClient<ScraperService>();

Jetzt ist Ihre API bereit – wie der Schlitten des Weihnachtsmanns an Heiligabend!


Schritt 2: Erstellen der Blazor-Anwendung

Blazor hilft uns, unser Projekt wie einen Weihnachtsbaum zu schmücken – und macht es optisch ansprechend und interaktiv.

2.1 Einrichten des Blazor-Projekts

Als Nächstes erstellen wir ein Blazor-Projekt, das als Schnittstelle für unsere Schlittenfahrt zur Preisüberwachung fungiert.

dotnet new blazor -o PriceMonitorBlazor
cd PriceMonitorBlazor

Dieser Befehl versprüht etwas Weihnachtszauber, um ein einfaches Blazor WebAssembly-Projekt einzurichten.

2.2 Modelle hinzufügen

Fügen Sie, genau wie beim Einrichten von Ornamenten, ein ProductInfo-Modell im Blazor-Projekt hinzu:

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

2.3 Erstellen des Dienstes für API-Aufrufe

Erstellen Sie einen Dienst zum Abrufen von Daten von unserer API – stellen Sie sich ihn als unseren Online-Shopping-Kumpel vor:

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 Registrieren von Diensten im DI-Container

Stellen Sie sicher, dass ScraperService registriert ist, damit wir es in unsere Komponenten einfügen können.

builder.Services.AddScoped<ScraperService>();

2.5 Erstellen der Blazor-Komponente

Aktualisieren Sie Pages/Index.razor, um eine unterhaltsame und interaktive Benutzeroberfläche einzuschließen:

@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 ist, als würde man eine festliche Weihnachtsdekoration einrichten – und so wird unsere App interaktiv und unterhaltsam.


Schritt 3: Alles mit Docker-Compose verbinden

Jetzt verbinden wir alles mit Docker-Compose und verwandeln unser Projekt in eine gut geölte Schlittenfahrt.

3.1 Docker-Dateien erstellen

Erstellen Sie Docker-Dateien sowohl für die API- als auch für die Blazor-Projekte:

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 Docker-compose.yml erstellen

Verbinde alle Punkte (Weihnachtslichter) mit einer einzigen docker-compose.yml-Datei:

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

Diagramme

Nachfolgend finden Sie die Diagramme zur Visualisierung der verschiedenen Komponenten und des Datenflusses. Dies wird uns helfen zu verstehen, was in unserer Weihnachtsmannwelt wirklich vor sich geht:

Systemarchitekturdiagramm

Imgur

Datenflussdiagramm

Imgur

Sequenzdiagramm

Imgur

Komponentendiagramm für Docker-Setup

Imgur

Schritt 4: URL-Verwaltung, Aktualisierung und automatische regelmäßige AktualisierungIn dieser Demo werden wir es einfach im Speicher speichern, aber in einer realen Anwendung würden Sie eine Datenbank verwenden. Bleiben wir bei diesem unterhaltsamen Projekt beim In-Memory-Speicher.

4.1 Ändern des Dienstes zur Verwaltung von URLs

Zunächst stellen wir sicher, dass unser Dienst das Hinzufügen und Abrufen von URLs sowie das Abrufen von Produktinformationen bewältigen kann:

Update 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 Aktualisieren der Blazor-Komponente für URL-Verwaltung und -Aktualisierung

Aktualisieren Sie als Nächstes Pages/Index.razor, um URL-Verwaltung, Aktualisierung und automatische regelmäßige Aktualisierung hinzuzufügen:

@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();
    }
}

In dieser aktualisierten Komponente:

  • Wir verwenden einen Timer, um die Methode RefreshPrices regelmäßig jede Minute aufzurufen. – Die Methode StartTimer initialisiert den Timer so, dass er sofort startet und dann alle 60 Sekunden ausgelöst wird. – Die Lebenszyklusmethode OnInitialized ruft StartTimer auf, wenn die Komponente initialisiert wird, um die regelmäßige Aktualisierung zu starten.

4.3 Ausführen der Lösung

Um die aktualisierte Blazor-Anwendung mit den neuen Funktionen auszuführen, erstellen Sie Ihre Docker-Container neu und starten Sie sie neu:

docker-compose build
docker-compose up

Nach dem Laden von http://localhost:5001 in Ihren Browser sollte die Blazor-App nun automatisch jede Minute die Produktpreise aktualisieren und außerdem manuelle Aktualisierungen und URL-Verwaltung ermöglichen.

Fazit

Der Aufbau dieses Preisüberwachungssystems war ein festlicher Knaller! Es ersparte mir nicht nur den Stress täglicher Preisüberprüfungen, sondern zeigte auch die Magie moderner Webtechnologien.

Festlicher Tech-Kalender 2024

Ich habe diesen Beitrag im Rahmen der Veranstaltung Festive Tech Calendar 2024 erstellt, die Technikbegeisterte, Innovatoren und digitale Träumer zusammenbringt, um Wissen auszutauschen und die Verschmelzung von festlichem Geist und technologischen Wundern zu feiern. Bei dieser Initiative geht es nicht nur um Lernen und Kontakte, sondern auch darum, etwas zurückzugeben.

Festive Tech Calendar 2024 unterstützt dieses Jahr die Beatson Cancer Charity. Die Beatson Cancer Charity widmet sich der Unterstützung von Krebspatienten, ihren Familien und den sie betreuenden medizinischen Fachkräften. Weitere Informationen zu ihrer unglaublichen Arbeit finden Sie unter https://www.beatsoncancercharity.org/.

Besuchen Sie die Website des Festive Tech Calendar unter [https://festivetechcalendar.com](https://festivetechcalendar.com für häufig gestellte Fragen und weitere Details zur Veranstaltung.

HO HO HO!

Die Erstellung dieses Projekts war eine wunderbare Möglichkeit, einen Beitrag zur festlichen Tech-Community zu leisten und gleichzeitig einen guten Zweck zu unterstützen.

Ich hoffe, dass Sie diesen Leitfaden hilfreich fanden und dass er Sie dazu inspiriert, weitere Möglichkeiten zu erkunden, wie Sie mithilfe von Technologie alltägliche Aufgaben vereinfachen können.

Wenn Sie Fragen haben oder weitere Hilfe benötigen, zögern Sie bitte nicht, uns zu kontaktieren.

Viel Spaß beim Codieren!