Facility: 095827
U-Haul Casper WY (82601)
- Facility ID
- 095827
- Name
- U-Haul Casper WY (82601)
- URL
- https://www.uhaul.com/Locations/Self-Storage-near-Casper-WY-82601/792072/
- Address
- N/A
- Platform
- custom_facility_095827
- Parser File
- src/parsers/custom/facility_095827_parser.py
- Last Scraped
- 2026-03-23 03:20:30.525933
- Created
- 2026-03-06 23:45:35.865957
- Updated
- 2026-03-23 03:20:30.532453
- Parser Status
- ✓ Working
- Status Reason
- N/A
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_095827_parser.py)
"""Parser for U-Haul Moving & Storage of Casper, WY (facility 095827).
U-Haul uses a custom layout where each storage unit type is rendered as a
``<li class="divider">`` card. The size is in an ``<h4>`` element, the
monthly price appears in ``<b class="text-2x">`` (or ``<b class="text-xl">``
for portable container units), and available unit count is in a hidden input
named ``VacantUnitsCount``. Features/amenities are listed in a ``<ul>``
with ``class="collapse condensed"``.
"""
from __future__ import annotations
import re
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility095827Parser(BaseParser):
"""Extract storage units from U-Haul Moving & Storage of Casper (WY)."""
platform = "custom_facility_095827"
# Matches dimension strings like "10' x 10' x 10'" or "5' x 8' x 7.5'"
_DIM_RE = re.compile(
r"(\d+(?:\.\d+)?)['\u2032\u2019]?\s*[xX\u00d7]\s*(\d+(?:\.\d+)?)['\u2032\u2019]?",
re.IGNORECASE,
)
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# Each unit type is a <li class="divider"> card
cards = soup.find_all("li", class_="divider")
for card in cards:
unit = UnitResult(url=url)
# --- Size ---
h4 = card.find("h4")
if not h4:
continue
# The size is either in a <span class="nowrap"> child or directly
# as text after the label <br> in h4.
nowrap = h4.find("span", class_="nowrap")
if nowrap:
raw_size = nowrap.get_text(strip=True)
else:
# Fallback: strip the label word (Small/Medium/Large) and grab
# the remaining text via get_text with the <br> as separator.
h4_text = h4.get_text(separator="\n", strip=True)
lines = [ln.strip() for ln in h4_text.splitlines() if ln.strip()]
# Lines are typically ["Medium", "10' x 10' x 10'"]
raw_size = lines[-1] if len(lines) >= 2 else h4_text
unit.size = raw_size
# Parse first two dimension values for width/length/sqft
m = self._DIM_RE.search(raw_size)
if m:
width = float(m.group(1))
length = float(m.group(2))
unit.metadata = {"width": width, "length": length, "sqft": round(width * length, 2)}
# --- Size label (Small / Medium / Large) ---
h4_first_line = h4.get_text(separator="\n", strip=True).splitlines()[0].strip()
if unit.metadata is not None:
unit.metadata["size_label"] = h4_first_line
else:
unit.metadata = {"size_label": h4_first_line}
# --- Price ---
# Prefer the larger display price (text-2x); fall back to text-xl
price_tag = card.find("b", class_="text-2x") or card.find("b", class_="text-xl")
if price_tag:
unit.price = self.normalize_price(price_tag.get_text(strip=True))
# --- Vacancy / scarcity ---
vacant_inp = card.find(
"input",
id=lambda i: i and "VacantUnitsCount" in i and "RentNow" in i,
)
if vacant_inp:
vacant_val = vacant_inp.get("value", "").strip()
if vacant_val:
unit.scarcity = f"{vacant_val} available"
unit.metadata["vacant_count"] = int(vacant_val)
# --- Features / amenities ---
features_ul = card.find("ul", class_=lambda c: c and "condensed" in c)
if features_ul:
features = [li.get_text(strip=True) for li in features_ul.find_all("li")]
if features:
unit.metadata["features"] = features
unit.description = ", ".join(features)
# Only add if we have at minimum a size or price
if unit.size or unit.price:
result.units.append(unit)
if not result.units:
result.warnings.append("No unit cards (li.divider) found on page")
return result
Scrape Runs (5)
-
exported Run #14892026-03-23 03:20:23.170137 | 5 units | Facility095827Parser | View Data →
-
exported Run #9962026-03-21 19:13:34.695012 | 6 units | Facility095827Parser | View Data →
-
exported Run #5492026-03-14 16:55:17.035224 | 6 units | Facility095827Parser | View Data →
-
exported Run #1532026-03-14 04:59:14.410641 | 6 units | Facility095827Parser | View Data →
-
exported Run #762026-03-14 01:00:38.362467 | 6 units | Facility095827Parser | View Data →
Run #996 Details
- Status
- exported
- Parser Used
- Facility095827Parser
- Platform Detected
- storageunitsoftware
- Units Found
- 6
- Stage Reached
- exported
- Timestamp
- 2026-03-21 19:13:34.695012
Timing
| Stage | Duration |
|---|---|
| Fetch | 8454ms |
| Detect | 304ms |
| Parse | 346ms |
| Export | 5ms |
Snapshot: 095827_20260321T191343Z.html · Show Snapshot · Open in New Tab
Parsed Units (6)
10' x 10' x 10'
$109.95/mo
3 available
10' x 15' x 10'
$149.95/mo
28 available
10' x 30' x 10'
$289.95/mo
2 available
7.3' x 16.8' x 6.5'
$109.95/mo
9 available
7.3' x 23.5' x 6.8'
$114.95/mo
21 available
5' x 8' x 7.5'
$59.95/mo