Facility: 025061

Self Storage WYO - Casper

Stale Data Warning: This facility has not been successfully scraped in 30 days (threshold: 3 days). Data may be outdated.
Facility Information active
Facility ID
025061
Name
Self Storage WYO - Casper
URL
https://www.selfstoragewyo.com/324-n-walsh-dr-casper-wy-82609
Address
N/A
Platform
custom_facility_025061
Parser File
src/parsers/custom/facility_025061_parser.py
Last Scraped
2026-03-23 03:19:25.913987
Created
2026-03-06 23:45:35.865957
Updated
2026-03-23 03:19:25.921604
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_025061_parser.py)
"""Parser for Walsh Drive Self Storage (Self Storage Wyo) in Casper, WY.

This is a Storable.io / storedge React SPA. The server-rendered HTML only
exposes two MuiCard unit cards, but the page embeds a JSON-LD ``@graph``
block (type ``application/ld+json``) that lists *all* available unit groups
as ``Product`` objects with description (e.g. "10x10") and offer price.

Multiple ``Product`` entries may share the same (size, price) pair when
there are several units of that type. We deduplicate by (size, price) and
emit one ``UnitResult`` per unique pair.

The JSON-LD does not carry amenity information; amenities visible in the
rendered cards (e.g. "Outside") are noted in the parser but cannot be
reliably attributed to every size without live API access.
"""

from __future__ import annotations

import json
import re

from bs4 import BeautifulSoup

from src.parsers.base import BaseParser, ParseResult, UnitResult


class Facility025061Parser(BaseParser):
    """Extract storage units from Walsh Drive Self Storage (selfstoragewyo.com)."""

    platform = "custom_facility_025061"

    # Matches dimensions like "10x10", "5x10", "12x30" in JSON-LD description field
    _SIZE_RE = re.compile(r"^(\d+)\s*[xX]\s*(\d+)$")

    def parse(self, html: str, url: str = "") -> ParseResult:
        soup = BeautifulSoup(html, "lxml")
        result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)

        # Locate all JSON-LD script blocks
        ld_scripts = soup.find_all("script", type="application/ld+json")
        products: list[dict] = []
        for script in ld_scripts:
            raw = script.string or ""
            if not raw.strip():
                continue
            try:
                data = json.loads(raw)
            except json.JSONDecodeError:
                continue
            # The unit data is in a @graph block containing Product objects
            if isinstance(data, dict) and "@graph" in data:
                for item in data["@graph"]:
                    if isinstance(item, dict) and item.get("@type") == "Product":
                        products.append(item)

        if not products:
            result.warnings.append("No JSON-LD Product entries found on page")
            return result

        # Deduplicate by (size_raw, price) — keep first occurrence of each pair
        seen: set[tuple[str, float | None]] = set()
        for product in products:
            desc = (product.get("description") or "").strip()
            offer = product.get("offers") or {}
            raw_price = offer.get("price")

            price: float | None = None
            if raw_price is not None:
                try:
                    price = float(raw_price)
                except (TypeError, ValueError):
                    price = self.normalize_price(str(raw_price))

            key = (desc, price)
            if key in seen:
                continue
            seen.add(key)

            # Normalise size string from "10x10" → "10 x 10"
            size_label = desc
            metadata: dict = {}
            m = self._SIZE_RE.match(desc)
            if m:
                width = float(m.group(1))
                length = float(m.group(2))
                size_label = f"{int(width)} x {int(length)}"
                metadata = {"width": width, "length": length, "sqft": width * length}

            unit = UnitResult(
                size=size_label,
                price=price,
                description=desc,
                url=url,
                metadata=metadata,
            )

            if unit.size or unit.price:
                result.units.append(unit)

        if not result.units:
            result.warnings.append("No units found on page")

        return result

Scrape Runs (5)

Run #66 Details

Status
exported
Parser Used
Facility025061Parser
Platform Detected
storageunitsoftware
Units Found
11
Stage Reached
exported
Timestamp
2026-03-14 00:59:52.661535
Timing
Stage Duration
Fetch6493ms
Detect26ms
Parse10ms
Export20ms

Snapshot: 025061_20260314T005959Z.html · Show Snapshot · Open in New Tab

Parsed Units (11)

10 x 10

$88.00/mo

10 x 15

$104.00/mo

10 x 20

$104.00/mo

10 x 20

$115.00/mo

5 x 10

$67.00/mo

10 x 10

$93.00/mo

10 x 30

$176.00/mo

12 x 30

$196.00/mo

5 x 10

$72.00/mo

10 x 20

$120.00/mo

10 x 15

$109.00/mo

← Back to dashboard