Facility: 086109

Sheridan Storage

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
086109
Name
Sheridan Storage
URL
https://www.sheridanstorage.us/self-storage-sheridan-wy-f2632
Address
N/A
Platform
custom_facility_086109
Parser File
src/parsers/custom/facility_086109_parser.py
Last Scraped
2026-03-23 03:18:37.897946
Created
2026-03-06 23:45:35.865957
Updated
2026-03-23 03:18:37.905021
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_086109_parser.py)
"""Parser for Sheridan Self Storage (Sheridan, WY).

This is a StorEdge/RentNow-powered site that renders unit listings
as a table inside a #unitstable div.  Each row has three meaningful
columns: size, unit details (address + amenity tags), and price.
"""

from __future__ import annotations

from bs4 import BeautifulSoup

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


class Facility086109Parser(BaseParser):
    """Extract storage units from Sheridan Self Storage (sheridanstorage.us).

    Unit data lives in a <table> inside ``#unitstable``.  Each ``<tr>``
    in the ``<tbody>`` represents one unit type with columns:
      - td[0]: size  (e.g. "5'x10'")
      - td[1]: unit details  (address and optional amenity labels separated
                              by commas, e.g. "1818 Terra Ave, Interior, Heated")
      - td[2]: monthly price  (e.g. "$80")
    """

    platform = "custom_facility_086109"

    # CSS selectors derived from the rendered snapshot
    _SIZE_SEL = "._2MVARJZtZDaHzpMVh0UAfo"
    _PRICE_SEL = "._3V2N9w-4bQvYJ9lUWBSkHt"

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

        units_table = soup.find(id="unitstable")
        if not units_table:
            result.warnings.append("No #unitstable element found on page")
            return result

        table = units_table.find("table")
        if not table:
            result.warnings.append("#unitstable found but contains no <table>")
            return result

        tbody = table.find("tbody")
        if not tbody:
            result.warnings.append("Table has no <tbody>")
            return result

        rows = tbody.find_all("tr")
        if not rows:
            result.warnings.append("No <tr> rows found in table body")
            return result

        for row in rows:
            cells = row.find_all("td")
            if len(cells) < 3:
                continue

            unit = UnitResult()

            # --- Size ---
            size_el = cells[0].select_one(self._SIZE_SEL)
            if size_el:
                raw_size = size_el.get_text(strip=True)
                unit.size = raw_size
                w, ln, sq = self.normalize_size(raw_size)
                if w is not None:
                    unit.metadata = {"width": w, "length": ln, "sqft": sq}

            # --- Description (address + amenity labels) ---
            desc_text = cells[1].get_text(separator=", ", strip=True)
            if desc_text:
                unit.description = desc_text

                # Derive amenity flags for metadata
                lower = desc_text.lower()
                amenities: list[str] = []
                if "heated" in lower or "climate" in lower:
                    amenities.append("climate_control")
                if "interior" in lower or "indoor" in lower:
                    amenities.append("interior")
                if "rv" in lower or "parking" in lower or "uncovered" in lower:
                    amenities.append("vehicle_parking")
                if amenities:
                    meta = unit.metadata or {}
                    meta["amenities"] = amenities
                    unit.metadata = meta

            # --- Price ---
            price_el = cells[2].select_one(self._PRICE_SEL)
            if price_el:
                unit.price = self.normalize_price(price_el.get_text(strip=True))

            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 #532 Details

Status
exported
Parser Used
Facility086109Parser
Platform Detected
storageunitsoftware
Units Found
7
Stage Reached
exported
Timestamp
2026-03-14 16:54:03.007653
Timing
Stage Duration
Fetch2770ms
Detect24ms
Parse11ms
Export14ms

Snapshot: 086109_20260314T165405Z.html · Show Snapshot · Open in New Tab

Parsed Units (7)

5'x10'

$80.00/mo

5'x10'

$100.00/mo

10'x10'

$90.00/mo

10'x15'

$100.00/mo

12'x25'

$70.00/mo

12'x35'

$80.00/mo

12'x45'

$90.00/mo

← Back to dashboard