コンテナを使用してクリスマス プレゼントの価格を追跡しますか?

· 5分で読める

クリスマスが近づいてきました。愛する人への完璧な贈り物を見つけるという楽しい仕事がやって来ます。あなたも私と同じなら、お得に買い物をするのが大好きかもしれませんが、ホリデー シーズン中に人気商品の価格が高騰する中を歩き回るのは、本物のそりに乗っているような気分になるかもしれません。エキサイティングではありますが、少し大変です。今年は、毎日の価格チェックに夢中になるのではなく、自分の技術スキルを有効に活用してプロセスを自動化することにしました。

ソフトウェア開発者として、またテクノロジーを活用して日常の問題を解決することに情熱を注いでいた私は、価格チェックのプロセスを自動化する方法を見つけました。価格を比較するために毎日複数のオンライン ストアを手動で訪問するのではなく、それを実行できるシステムを作成することにしました。これにより、時間を節約できるだけでなく、毎日のチェックのストレスを感じることなく、最良の取引を確実に入手できるようになります。

価格監視を自動化する理由

価格監視の自動化は、サンタの妖精のチームを24時間体制で働かせて、何もせずに最高の取引を確実に入手できるようにするようなものです。気に入っていただける理由は次のとおりです:

  1. 高価なアイテム: クリスマス プレゼントは、特にガジェットやおもちゃがリストにある場合、かなり高価になる可能性があることは誰もが知っています。これらでお金を節約することは、在庫にある最後のツリートップを見つけるのと同じくらい満足できることです。
  2. 毎日のチェック: 毎日価格をチェックする時間 (または忍耐力) がある人はいるでしょうか?私じゃないよ!
  3. 自動化: ホリデー シーズンの寄付の精神を受け入れ、効率性という贈り物を自分自身に与えましょう。
  4. 精度: 自動化により、価格チェックがルドルフの鼻のように正確であることが保証されます。

コアテクノロジー

独自のサンタの小さなヘルパーを構築するために、いくつかの主要なテクノロジーを使用することにしました。

コンテナ

コンテナはソフトウェアのクリスマスラッピングのようなものです。アプリケーションをすべての機能 (依存関係) とバンドルして、すべてが新雪のそりのようにスムーズに動作するようにします。これらのコンテナを作成するには Docker が頼りになります。

ブレザー

Blazor は、.NET を使用してインタラクティブな Web アプリケーションを構築するための優れたフレームワークです。それは、一般的なクリスマスキャロルを、カスタマイズされ、効率的で、とても楽しい、独自のホリデー プレイリストに置き換えるようなものです。

Docker-Compose

Docker-Compose は、北極の運営のマネージャーです。これは、API や Blazor フロントエンドなどのすべてのサービスを完全に調和して実行するのに役立ちます。それを私たちのホリデー交響楽団の指揮者だと考えてください。

ステップバイステップガイド

さあ、サンタさんのワークショップに飛び込んで、このプロジェクトを実現させましょう!

ステップ 1: API の作成

1.1 プロジェクトのセットアップ

コーディング用のサンタ帽をかぶって、新しい ASP.NET Core Web API プロジェクトをセットアップします。ターミナルを開いて (アドベントカレンダーを開くのと似ています)、次のコマンドを実行します。

dotnet new webapi -o PriceMonitorApi
cd PriceMonitorApi

このコマンドは、PriceMonitorApi という名前の新しいディレクトリを作成し、基本的な Web API プロジェクトをセットアップします。ジンジャーブレッドハウスの頑丈な土台を作るようなものだと想像してください。

1.2 HttpClient の追加とライブラリのスクレイピング次に、HttpClient と HTML を解析するライブラリを追加します。これは、価格データを取得して読み取るための信頼できるそりになります。

dotnet add package HtmlAgilityPack

HtmlAgilityPack は、HTML ドキュメントの解析を支援するエルフです。

1.3 モデルの作成

模型はおもちゃの設計図のようなものです。製品情報を表すモデルを作成しましょう。

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

データに最適なテンプレートを作成しました。

1.4 スクレーパー サービスの実装

私たちのスクレイパー サービスはサンタさんの小さなヘルパーのようなもので、私たちのために情報を取得して処理します。

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

このスニペットは、HtmlAgilityPack をホリデーの魔法の杖に変えます。

1.5 API コントローラーの作成

データのゲートキーパーのように機能するコントローラーを作成しましょう。

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

私たちのコントローラーは、サンタさんが煙突から下りてくるよりも早くデータが配信されることを保証します。

1.6 DIコンテナへのサービスの登録

最後に、ScraperService を登録して、必要なときに利用できるようにします。

services.AddHttpClient<ScraperService>();

これで、クリスマスイブのサンタのそりのように、API の準備が整いました。


ステップ 2: Blazor アプリケーションの作成

Blazor を使用すると、プロジェクトをクリスマス ツリーのように飾り、視覚的に魅力的でインタラクティブにすることができます。

2.1 Blazor プロジェクトのセットアップ

次に、価格監視そりのインターフェイスとして機能する Blazor プロジェクトを作成します。

dotnet new blazor -o PriceMonitorBlazor
cd PriceMonitorBlazor

このコマンドは、基本的な Blazor WebAssembly プロジェクトをセットアップするための休日の魔法をいくつか散りばめています。

2.2 モデルの追加

オーナメントを設定するのと同じように、Blazor プロジェクトに ProductInfo モデルを追加します。

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

2.3 API 呼び出し用のサービスの作成

API からデータを取得するサービスを作成します。これをオンライン ショッピングの友達と考えてください。

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 DIコンテナへのサービスの登録

ScraperService がコンポーネントに挿入できるように、ScraperService が登録されていることを確認してください。

builder.Services.AddScoped<ScraperService>();

2.5 Blazor コンポーネントの作成

Pages/Index.razor を更新して、楽しくインタラクティブなインターフェイスを追加します。

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

これは、お祭りのディスプレイをセットアップするようなもので、アプリをインタラクティブで楽しいものにします。


ステップ 3: Docker-Compose を使用してすべてを接続する

それでは、Docker-Compose を使用してすべてを接続し、プロジェクトを十分に油を注いだそりに変えてみましょう。

3.1 Dockerfile の作成

API プロジェクトと Blazor プロジェクトの両方の Dockerfile を作成します。

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 の作成

すべてのドット (クリスマス ライト) を 1 つの 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

図表

以下は、さまざまなコンポーネントとデータ フローを視覚化するのに役立つ図です。これは、サンタの世界で実際に何が起こっているのかを理解するのに役立ちます。

システムアーキテクチャ図

Imgur

データ フロー図

Imgur

シーケンス図

Imgur

Docker セットアップのコンポーネント図

Imgur

ステップ 4: URL 管理、更新、自動定期更新このデモではメモリに保存するだけですが、実際のアプリケーションではデータベースを使用します。この楽しいプロジェクトでは、メモリ内ストレージにこだわりましょう。

4.1 URL を管理するためのサービスの変更

まず、サービスが URL の追加と取得、および製品情報の取得を処理できることを確認します。

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 URL 管理と更新のための Blazor コンポーネントの更新

次に、Pages/Index.razor を更新して、URL 管理、更新、自動定期更新を追加します。

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

この更新されたコンポーネントでは次のようになります。

  • Timer を使用して、RefreshPrices メソッドを毎分定期的に呼び出します。
  • StartTimer メソッドは、タイマーを初期化してすぐに開始し、その後 60 秒ごとにトリガーします。
  • OnInitialized ライフサイクル メソッドは、コンポーネントが初期化されて定期更新を開始するときに StartTimer を呼び出します。

4.3 ソリューションの実行

新しい機能を備えた更新された Blazor アプリケーションを実行するには、Docker コンテナーを再構築して再起動します。

docker-compose build
docker-compose up

ブラウザーに http://localhost:5001 を読み込んだ後、Blazor アプリは手動更新と URL 管理に加えて、商品価格を 1 分ごとに自動的に更新するようになります。

結論

この価格監視システムの構築は非常に楽しいものでした。毎日の価格チェックのストレスから解放されただけでなく、最新の Web テクノロジーの魔法を見せてくれました。

フェスティブテックカレンダー2024

私はこの投稿を Festive Tech Calendar 2024 イベントの一環として作成しました。このイベントでは、テクノロジー愛好家、イノベーター、デジタル夢想家が集まり、知識を共有し、お祭り気分とテクノロジーの驚異の融合を祝います。この取り組みは、学び、つながるだけでなく、恩返しすることも目的としています。

Festive Tech Calendar 2024 は今年、Beatson Cancer Charity をサポートしています。 Beatson Cancer Charity は、がんに苦しむ人々、その家族、そして彼らの世話をする医療従事者を支援することに専念しています。彼らの素晴らしい仕事についての詳細は、https://www.beatsoncancercharity.org/ でご覧いただけます。

よくある質問とイベントの詳細については、Festive Tech Calendar Web サイト (https://festivetechcalendar.com) をご覧ください。

ホーホーホー!

このプロジェクトの作成は、お祭り騒ぎのテクノロジー コミュニティに貢献し、同時に大きな目的をサポートする素晴らしい方法でした。

このガイドがお役に立ち、テクノロジーを使用して日常業務を簡素化するさらなる方法を探求するきっかけになれば幸いです。

ご質問がある場合、またはさらにサポートが必要な場合は、お気軽にお問い合わせください。

コーディングを楽しんでください!