Facility: 038927
South Shepherd Storage
- Facility ID
- 038927
- Name
- South Shepherd Storage
- URL
- https://southshepherdstorage.ccstorage.com/find_units
- Address
- N/A
- Platform
- custom_facility_038927
- Parser File
- src/parsers/custom/facility_038927_parser.py
- Last Scraped
- 2026-03-23 03:19:30.841029
- Created
- 2026-03-06 23:45:35.865957
- Updated
- 2026-03-23 03:19:30.852573
- Parser Status
- ✓ Working
- Status Reason
- N/A
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_038927_parser.py)
"""Parser for South Shepherd Storage facility.
This is a CCStoarge-based SPA that renders unit type cards inside a
``<turbo-frame>`` element. Each card contains a size label, an amenities
section, and a pricing table that lists separate Card and Cash rates.
Availability is signalled by the CTA button text ("Rent Now" vs
"Join waiting list") and an optional "No Units Available" badge.
"""
from __future__ import annotations
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility038927Parser(BaseParser):
"""Extract storage units from South Shepherd Storage.
Unit cards live inside ``<div data-test-type="sut_...">`` elements rendered
inside a ``<turbo-frame>`` block. Each card exposes:
- Size: ``<p class="text-xl font-bold">`` (e.g. "10x10")
- Amenities: listed below an "Amenities" heading (or "None listed")
- Prices: one or more ``<dl>`` pairs with ``<dt>`` label (Card / Cash)
and ``<dd>`` amount
- Availability: "No Units Available" div present when sold out
"""
platform = "custom_facility_038927"
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 divs with a data-test-type attribute (sut_...)
containers = soup.select("div[data-test-type]")
for container in containers:
unit = UnitResult(url=url)
# --- Size ---
size_el = container.select_one("p.text-xl.font-bold")
if size_el:
size_text = size_el.get_text(strip=True)
unit.size = size_text
w, ln, sq = self.normalize_size(size_text)
if w is not None:
unit.metadata = {"width": w, "length": ln, "sqft": sq}
# --- Amenities ---
amenities_text: str | None = None
amenities_heading = container.find(
lambda tag: tag.name == "p" and tag.get_text(strip=True) == "Amenities"
)
if amenities_heading:
# The amenity list is the next sibling <p> or element
sibling = amenities_heading.find_next_sibling()
if sibling:
amenities_text = sibling.get_text(strip=True)
if amenities_text and amenities_text.lower() != "none listed":
if unit.metadata is None:
unit.metadata = {}
unit.metadata["amenities"] = amenities_text
# --- Prices: iterate all <dl> pairs ---
cash_price: float | None = None
card_price: float | None = None
for dl in container.select("dl"):
dt = dl.select_one("dt")
dd = dl.select_one("dd")
if not dt or not dd:
continue
label = dt.get_text(strip=True).lower()
value = self.normalize_price(dd.get_text(strip=True))
if label == "cash":
cash_price = value
elif label == "card":
card_price = value
# Use Cash as the primary price, Card as an alternative
unit.price = cash_price
if card_price is not None:
if unit.metadata is None:
unit.metadata = {}
unit.metadata["card_price"] = card_price
# --- Availability ---
no_units_el = container.find(
lambda tag: tag.name == "div"
and "No Units Available" in tag.get_text()
)
if no_units_el:
unit.scarcity = "No Units Available"
else:
# Check for "Rent Now" button indicating availability
rent_now = container.find(
lambda tag: tag.name in ("a", "button")
and "Rent Now" in tag.get_text()
)
if rent_now:
unit.scarcity = "Available"
if unit.size 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)
-
exported Run #14822026-03-23 03:19:26.725294 | 16 units | Facility038927Parser | View Data →
-
exported Run #9892026-03-21 19:12:24.749807 | 16 units | Facility038927Parser | View Data →
-
exported Run #5422026-03-14 16:54:46.377747 | 16 units | Facility038927Parser | View Data →
-
exported Run #1442026-03-14 04:58:06.852955 | 16 units | Facility038927Parser | View Data →
-
exported Run #672026-03-14 00:59:59.659946 | 16 units | Facility038927Parser | View Data →
Run #67 Details
- Status
- exported
- Parser Used
- Facility038927Parser
- Platform Detected
- ccstorage
- Units Found
- 16
- Stage Reached
- exported
- Timestamp
- 2026-03-14 00:59:59.659946
Timing
| Stage | Duration |
|---|---|
| Fetch | 4078ms |
| Detect | 0ms |
| Parse | 16ms |
| Export | 16ms |
Snapshot: 038927_20260314T010003Z.html · Show Snapshot · Open in New Tab
Parsed Units (16)
10x10
$59.00/mo
No Units Available
10x15
$71.00/mo
No Units Available
10x20
$85.00/mo
No Units Available
10x25
$103.00/mo
No Units Available
10x30
$122.00/mo
No Units Available
10x40
$156.00/mo
No Units Available
10x7
$48.00/mo
Available
10x8
$50.00/mo
No Units Available
11x10
$59.00/mo
No Units Available
11x15
$71.00/mo
No Units Available
11x25
$103.00/mo
No Units Available
11x30
$152.00/mo
No Units Available
11x40
$156.00/mo
No Units Available
5x7
$30.00/mo
No Units Available
5x8
$30.00/mo
No Units Available
8x20 RV Out Door Parking
$33.00/mo
No Units Available