Skip to content

About

Asynchronous price & stock watcher daemon for Japanese PC Hardware retailers, with real-time Discord notifications. Built with Python, SQLite, and Playwright.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

⚡ Japanese PC Hardware Price & Stock Watcher

English • 日本語

Python 3.12+ Playwright Async Pydantic v2 SQLite WAL Tests Passed Ruff

An event-driven, asynchronous background daemon built in Python 3.12+ to monitor Japanese PC hardware retailers (Tsukumo, Dospara, and PC One's) for real-time inventory availability and price changes, dispatching rich embed notifications to Discord.


🎬 Live Showcases

🖥️ Interactive Terminal Extraction (--demo)

Price Watcher Live Demo

📨 Discord Real-Time Rich Embed Notifications

Price Watcher Discord Notifications


🏗️ System Architecture & Data Flow

flowchart LR
    subgraph Ingestion [" 1. Headless Ingestion "]
        URL[Target URLs] --> Extractor[Strategy Extractors<br/>Tsukumo / Dospara / Ones]
        Extractor --> Playwright[Playwright Async Engine<br/>DOM & Microdata Extraction]
    end

    subgraph Boundary [" 2. Validation Boundary "]
        Playwright --> Scraped[Pydantic Validation<br/>ScrapedProduct]
    end

    subgraph Engine [" 3. Delta Engine & Alerts "]
        Scraped --> Delta[Pure Delta Engine<br/>evaluate_delta]
        Delta -->|State Changed| Discord[Discord Webhook<br/>Rich Embed + Thumbnail]
    end

    subgraph Storage [" 4. Asynchronous Storage "]
        Scraped --> Queue[Single-Writer Queue<br/>asyncio.Queue]
        Queue --> Worker[Writer Worker Task]
        Worker --> DB[(SQLite WAL Database<br/>products & price_history)]
    end
Loading

🌟 Key Engineering Highlights

  • Strategy Pattern Scraper Architecture: Decoupled retailer parsers (TsukumoExtractor, DosparaExtractor, OnesExtractor) implementing a unified BaseExtractor interface. Adding new stores requires zero changes to the daemon loop.
  • Defensive DOM Extraction: Scoped purchase-box targeting, multi-attribute button inspection (disabled, aria-disabled, CSS classes, inner text), and OpenGraph image extraction (meta[property='og:image']). Uses domcontentloaded to bypass third-party tracking script hangs.
  • Tokenized Price Sanitization: Targeted regex currency extraction isolating primary numeric tokens, preventing false digit concatenation with tax breakdowns (10%税込) or point rewards (ポイント10倍).
  • Single-Writer SQLite Actor Pattern: Enforces serial database writes through an internal asyncio.Queue consumer, completely eliminating SQLite database is locked errors under concurrent coroutine load.
  • Pure Functional Delta Engine: Side-effect-free state transition classifier tracking compound events (e.g. simultaneous BACK_IN_STOCK and PRICE_DROP) with configurable percentage threshold gating.
  • Fault-Tolerant Webhooks: Dynamic color-coded Discord Rich Embeds with thumbnail previews, rate-limit backoff handling (429 Retry-After), and defensive error containment.
  • Cooperative Daemon Lifecycle: Non-blocking interruptible sleep (asyncio.Event) with unified POSIX signal trapping (SIGINT, SIGTERM) for sub-second graceful shutdowns.
  • 100% Hermetic Test Suite: 160 unit and integration tests executing in under 0.6 seconds via httpx.MockTransport and async DOM mocking fixtures—zero external network dependencies.

⚡ Quick Start

1. Installation & Environment Setup

Clone the repository and synchronize dependencies using uv:

git clone https://github.com/your-username/price-watcher.git
cd price-watcher

# Install Python dependencies & Playwright Chromium
uv sync
uv run playwright install chromium

2. Try the 10-Second Interactive Demo

Run a single live extraction pass across curated Japanese hardware targets without needing a database or Discord webhook:

uv run python -m src.main --demo

3. Configure & Run Continuous Daemon

Copy the environment template and configure your tracking preferences:

cp .env.example .env

Edit .env:

DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/YOUR_ID/YOUR_TOKEN"
SQLITE_DB_PATH="price_watcher.db"
CHECK_INTERVAL_SECONDS=300
PRICE_DROP_THRESHOLD_PCT=0.0
LOG_LEVEL="INFO"
TARGET_URLS="https://shop.tsukumo.co.jp/goods/0409175060015/,https://www.dospara.co.jp/SBR1882/IC516296.html,https://www.1-s.jp/products/detail/308973"

Start the background daemon:

uv run python -m src.main

🧪 Testing & Code Quality

Run the full hermetic test suite (160 tests):

uv run pytest -v

Run code formatting and linting:

uv run ruff check .
uv run ruff format --check .

📁 Repository Structure

price-watcher/
├── docs/                # Visual demo GIFs and documentation assets
├── src/
│   ├── config/          # Pydantic Settings & environment variable parsing
│   ├── db/              # aiosqlite setup, migrations & single-writer repository
│   ├── engine/          # Pure delta state transition evaluation
│   ├── extractors/      # Strategy DOM parsers (Tsukumo, Dospara, PC One's)
│   ├── models/          # Pydantic domain models (ScrapedProduct, ProductRecord, etc.)
│   ├── notifiers/       # Discord webhook notifier via httpx
│   └── main.py          # Daemon lifecycle orchestrator & CLI runner
├── tests/               # Hermetic test suite with mock transports & DOM adapters
├── pyproject.toml       # Project metadata and dependencies
├── README.md            # English documentation
└── README.ja.md         # Japanese documentation

📄 License

This project is open-source and available under the MIT License.

About

Asynchronous price & stock watcher daemon for Japanese PC Hardware retailers, with real-time Discord notifications. Built with Python, SQLite, and Playwright.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages