使用容器来...跟踪圣诞礼物的价格?

· 5 分钟阅读

圣诞节即将来临,随之而来的是为亲人寻找完美礼物的快乐任务。如果您和我一样,您可能喜欢买到划算的商品,但在节日期间浏览热门商品价格飞涨的感觉就像现实生活中的雪橇之旅一样 — 令人兴奋,但有点不知所措!今年,我决定充分利用我的技术技能并使流程自动化,而不是让自己因每日价格检查而发疯。

<p对齐=“中心”>

作为一名软件开发人员和热衷于利用技术解决日常问题的人,我找到了一种自动化检查价格过程的方法。我决定创建一个可以为我做这件事的系统,而不是每天手动访问多个在线商店来比较价格。这不仅节省了时间,还能确保我获得最优惠的价格,而无需每天检查的压力。

为什么要自动化价格监控?

自动化价格监控就像拥有自己的圣诞老人精灵团队全天候工作,以确保您无需费力即可获得最好的交易。这就是您会喜欢它的原因:

  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 已注册,以便我们可以将其注入到我们的组件中。

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

使用单个 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 更新 Blazor 组件以进行 URL 管理和刷新

接下来,更新 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 后,除了允许手动刷新和 URL 管理之外,Blazor 应用现在还应该每分钟自动刷新产品价格。

结论

建立这个价格监控系统真是太欢乐了!它不仅让我摆脱了日常价格检查的压力,而且还展示了现代网络技术的魔力。

2024 年节日科技日历

<p对齐=“中心”>

我创建这篇文章是 2024 年节日科技日历 活动的一部分,该活动汇集了科技爱好者、创新者和数字梦想家,分享知识并庆祝节日精神与技术奇迹的融合。这一举措不仅是为了学习和联系,也是为了回馈。

2024 年节日科技日历 今年将支持 Beatson 癌症慈善机构。 Beatson 癌症慈善机构致力于支持癌症患者、他们的家人以及照顾他们的医疗保健专业人员。有关他们令人难以置信的工作的更多信息,请访问 https://www.beatsoncancercharity.org/

请访问节庆科技日历网站 https://festivetechcalendar.com,了解常见问题和有关该活动的更多详细信息。

嗬嗬嗬!

创建这个项目是为节日科技社区做出贡献并同时支持一项伟大事业的绝佳方式。

我希望本指南对您有所帮助,并激励您探索更多使用技术来简化日常任务的方法。

如果您有任何疑问或需要进一步帮助,请随时与我们联系。

快乐编码!