Facility: 086010

Walla Walla Self Storage - Eastside

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
086010
Name
Walla Walla Self Storage - Eastside
URL
https://www.wallawallaselfstorage.com/eastside-mini-storage/
Address
N/A
Platform
custom_facility_086010
Parser File
src/parsers/custom/facility_086010_parser.py
Last Scraped
2026-03-23 03:22:10.881134
Created
2026-03-06 23:45:35.865957
Updated
2026-03-23 03:22:10.887298
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_086010_parser.py)
"""Parser for Walla Walla Self Storage - Eastside Mini Storage.

This is a StorageUnitSoftware v2 (sutv2) site with a custom card layout.
Units are displayed as bordered cards with sale/regular pricing,
promotion text, and amenity list items.
"""

from __future__ import annotations

import re

from bs4 import BeautifulSoup

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


class Facility086010Parser(BaseParser):
    """Extract storage units from Walla Walla Self Storage (Eastside Mini Storage).

    Each unit is a card: div.border.border-gray-400.mb-3.shadow-lg.p-2
    - Size heading:   div.font-bold.text-xl (text like "5x10 Storage")
    - Sale price:     span.font-bold.text-wcgreen.text-2xl  (discounted/web price)
    - Regular price:  div.sutv2-cross-out.line-through       (crossed-out street price)
    - Promotion:      li.text-wcred.font-bold                (red bold list item)
    - Amenities:      remaining <li> items in the card
    """

    platform = "custom_facility_086010"

    # Pattern to detect scarcity language in promotion text
    _SCARCITY_RE = re.compile(
        r"\blast\s+\d+\b|\blast\s+unit\b|\bonly\s+\d+\s+left\b|\bact\s+now\b|\blimited\b",
        re.IGNORECASE,
    )

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

        # Unit cards are identified by their border + shadow Tailwind classes
        cards = soup.find_all(
            "div",
            class_=lambda c: c and "border-gray-400" in c and "mb-3" in c and "shadow-lg" in c,
        )

        for card in cards:
            unit = UnitResult()

            # --- Size ---
            size_el = card.select_one("div.font-bold")
            if size_el:
                size_text = size_el.get_text(strip=True)
                # Strip trailing " Storage" suffix to get the raw dimension string
                size_clean = re.sub(r"\s*storage\s*$", "", size_text, flags=re.IGNORECASE).strip()
                unit.size = size_clean
                w, ln, sq = self.normalize_size(size_clean)
                if w is not None:
                    unit.metadata = {"width": w, "length": ln, "sqft": sq}

            # --- Sale price (green, discounted price shown prominently) ---
            sale_el = card.select_one("span.text-wcgreen")
            if sale_el:
                unit.sale_price = self.normalize_price(sale_el.get_text(strip=True))

            # --- Regular / street price (crossed out) ---
            regular_el = card.select_one("div.sutv2-cross-out")
            if regular_el:
                unit.price = self.normalize_price(regular_el.get_text(strip=True))

            # --- Promotion text (red bold list item) ---
            promo_el = card.select_one("li.text-wcred")
            if promo_el:
                promo_text = promo_el.get_text(strip=True)
                unit.promotion = promo_text
                # Detect scarcity language within the promotion string
                if self._SCARCITY_RE.search(promo_text):
                    unit.scarcity = promo_text

            # --- Amenities (non-red list items) ---
            amenity_items = [
                li.get_text(strip=True)
                for li in card.find_all("li")
                if "text-wcred" not in (li.get("class") or [])
            ]
            if amenity_items:
                meta = unit.metadata or {}
                meta["amenities"] = amenity_items
                unit.metadata = meta

            # --- Description: full card text for reference ---
            unit.description = card.get_text(separator=" ", strip=True)

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

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

        return result

Scrape Runs (5)

Run #178 Details

Status
exported
Parser Used
Facility086010Parser
Platform Detected
storageunitsoftware
Units Found
3
Stage Reached
exported
Timestamp
2026-03-14 05:04:03.132951
Timing
Stage Duration
Fetch4681ms
Detect27ms
Parse15ms
Export6ms

Snapshot: 086010_20260314T050407Z.html · Show Snapshot · Open in New Tab

Parsed Units (3)

5x10

$16.00/mo
Street: $32.00

10x20

$42.00/mo
Street: $84.00

10x10

$69.00/mo
Street: $138.00
Last Unit ACT NOW! Half Off First Month!

← Back to dashboard