Facility: 087904

Storage Star - Laramie

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
087904
Name
Storage Star - Laramie
URL
https://www.storagestar.com/self-storage/wyoming/laramie/830-boswell-dr/
Address
N/A
Platform
custom_facility_087904
Parser File
src/parsers/custom/facility_087904_parser.py
Last Scraped
2026-03-23 03:20:06.264267
Created
2026-03-06 23:45:35.865957
Updated
2026-03-23 03:20:06.274604
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_087904_parser.py)
"""Parser for StorageStar Laramie WY (Boswell Dr).

The site renders units in a table with class ``unitsTable``. Each ``<tr>``
with class ``shortableClass`` is one available unit and contains:

- ``.unit-type-listing-name`` — unit name/description (e.g. "Drive-Up B040")
- ``h2.widthHeight`` — dimensions (e.g. "5' WIDTH x 5' DEPTH")
- ``h3.actualMoPrice`` — web/sale price (e.g. "$37 /month")
- ``h3.withoutDiscntprice`` — standard/street price (e.g. "$62 /month")
- ``span.offer__content`` — promotion text (e.g. "$1 MOVE-IN")
- ``span.feature-item__title`` — amenity feature labels
"""

from __future__ import annotations

import re

from bs4 import BeautifulSoup

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

# Matches "5' WIDTH x 5' DEPTH" or "10' WIDTH x 20' DEPTH" etc.
_SIZE_RE = re.compile(
    r"(\d+(?:\.\d+)?)['\u2019\u2032]?\s*WIDTH\s*x\s*(\d+(?:\.\d+)?)['\u2019\u2032]?\s*DEPTH",
    re.IGNORECASE,
)

# Extracts a dollar amount like "$37" or "$1,299.00" from a string that may contain "/month"
_PRICE_RE = re.compile(r"\$(\d[\d,]*(?:\.\d{1,2})?)")


class Facility087904Parser(BaseParser):
    """Extract storage units from StorageStar Laramie WY (Boswell Dr)."""

    platform = "custom_facility_087904"

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

        unit_rows = soup.find_all("tr", class_=lambda c: c and "shortableClass" in c)

        for row in unit_rows:
            unit = UnitResult()
            unit.url = url

            # --- description (unit name, e.g. "Drive-Up B040") ---
            name_el = row.find("div", class_="unit-type-listing-name")
            if name_el:
                unit.description = name_el.get_text(strip=True)

            # --- size (e.g. "5' WIDTH x 5' DEPTH") ---
            size_el = row.find("h2", class_="widthHeight")
            if size_el:
                size_text = size_el.get_text(" ", strip=True)
                unit.size = size_text
                m = _SIZE_RE.search(size_text)
                if m:
                    width = float(m.group(1))
                    length = float(m.group(2))
                    unit.metadata = {"width": width, "length": length, "sqft": width * length}

            # --- sale/web price (e.g. "$37 /month") ---
            sale_el = row.find("h3", class_="actualMoPrice")
            if sale_el:
                m = _PRICE_RE.search(sale_el.get_text(strip=True))
                if m:
                    unit.sale_price = self.normalize_price(m.group(1))

            # --- standard/street price (e.g. "$62 /month") ---
            std_el = row.find("h3", class_="withoutDiscntprice")
            if std_el:
                m = _PRICE_RE.search(std_el.get_text(strip=True))
                if m:
                    unit.price = self.normalize_price(m.group(1))

            # --- promotion text (e.g. "$1 MOVE-IN") ---
            promo_el = row.find("span", class_="offer__content")
            if promo_el:
                unit.promotion = promo_el.get_text(strip=True)

            # --- amenity features as metadata ---
            feature_els = row.find_all("span", class_="feature-item__title")
            if feature_els:
                features = [f.get_text(strip=True) for f in feature_els if f.get_text(strip=True)]
                if features:
                    unit.metadata = {**(unit.metadata or {}), "features": features}

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

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

        return result

Scrape Runs (5)

Run #150 Details

Status
exported
Parser Used
Facility087904Parser
Platform Detected
table_layout
Units Found
13
Stage Reached
exported
Timestamp
2026-03-14 04:58:39.295632
Timing
Stage Duration
Fetch14590ms
Detect311ms
Parse158ms
Export8ms

Snapshot: 087904_20260314T045853Z.html · Show Snapshot · Open in New Tab

Parsed Units (13)

5' WIDTH x 5' DEPTH

$37.00/mo
Street: $62.00

5' WIDTH x 5' DEPTH

$46.00/mo
Street: $77.00

5' WIDTH x 10' DEPTH

$53.00/mo
Street: $88.00

10' WIDTH x 10' DEPTH

$60.00/mo
Street: $100.00

10' WIDTH x 10' DEPTH

$90.00/mo
Street: $150.00

10' WIDTH x 15' DEPTH

$71.00/mo
Street: $118.00

8' WIDTH x 20' DEPTH

$11.00/mo
Street: $18.00

10' WIDTH x 20' DEPTH

$107.00/mo
Street: $178.00

12' WIDTH x 20' DEPTH

$20.00/mo
Street: $33.00

10' WIDTH x 25' DEPTH

$183.00/mo
Street: $305.00

10' WIDTH x 30' DEPTH

$26.00/mo
Street: $43.00

12' WIDTH x 30' DEPTH

$32.00/mo
Street: $53.00

14' WIDTH x 50' DEPTH

$68.00/mo
Street: $113.00

All Failures for this Facility (2)

parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-14 16:55:12.999127

No units extracted for 087904

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 087904
parse _WarningAsException scraper no_units_extracted warning Run #N/A | 2026-03-14 01:00:29.876403

No units extracted for 087904

Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 087904

← Back to dashboard