Facility: 002716
Clemson Boulevard Mini Storage
- Facility ID
- 002716
- Name
- Clemson Boulevard Mini Storage
- URL
- http://www.clemsonblvdministorage.com/
- Address
- 109 Welpine Rd, Pendleton, SC 29670, USA, Pendleton, South Carolina 29670
- Platform
- custom_facility_002716
- Parser File
- src/parsers/custom/facility_002716_parser.py
- Last Scraped
- 2026-03-27 13:39:50.493859
- Created
- 2026-03-23 02:35:08.816820
- Updated
- 2026-03-27 13:39:50.493859
- Parser Status
- ⚠ Needs Fix
- Status Reason
- Parser returned 0 units
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_002716_parser.py)
"""Parser for Clemson Boulevard Mini Storage (StorageUnitSoftware site)."""
from __future__ import annotations
import re
from bs4 import BeautifulSoup, Tag
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility002716Parser(BaseParser):
"""Extract storage units from Clemson Boulevard Mini Storage.
This facility runs on the StorageUnitSoftware / Storable Easy platform.
Units are rendered as Bootstrap cards (div.card.rounded-0) with:
- h4.primary-color for size name (e.g. "10' x 10'")
- strong.price.primary-color for pricing (e.g. "$85 / month")
- btn links indicating availability ("Rent Now", "Waiting List")
The homepage loads units via JavaScript; the fetcher must wait for
the card elements to appear before capturing the snapshot.
"""
platform = "custom_facility_002716"
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# Strategy 1: Bootstrap card layout (modern SUS white-label sites)
cards = soup.select("div.card.rounded-0")
unit_elements = [
c for c in cards
if c.select_one("strong.price, .price.primary-color")
]
# Strategy 2: SUS-specific unit containers
if not unit_elements:
unit_elements = soup.select(
".sus-unit-row, .sus-unit-card, .sus-unit-item"
)
# Strategy 3: unit-list children
if not unit_elements:
unit_elements = soup.select(
".unit-list .unit-item, .unit-list li, .unit-list tr"
)
# Strategy 4: data-attribute fallback
if not unit_elements:
unit_elements = soup.select("[data-unit-id], [data-unit]")
if not unit_elements:
result.warnings.append(
"No unit cards found; page may not have rendered JS content"
)
return result
for el in unit_elements:
unit = self._parse_card(el)
if unit and (unit.size or unit.price or unit.sale_price):
result.units.append(unit)
if not result.units:
result.warnings.append("Found card elements but could not extract unit data")
return result
def _parse_card(self, el: Tag) -> UnitResult | None:
"""Extract a single unit from a SUS Bootstrap card element."""
unit = UnitResult()
# --- Description (visible content only) ---
card_body = el.select_one(".card-body")
visible = card_body if card_body else el
for hidden in visible.select(".d-none"):
hidden.decompose()
unit.description = visible.get_text(separator=" ", strip=True)[:200]
# --- Size ---
heading = el.select_one("h4.primary-color")
if heading:
heading_text = heading.get_text(strip=True)
w, ln, sq = self.normalize_size(heading_text)
if w is not None:
unit.size = heading_text
unit.metadata = {"width": w, "length": ln, "sqft": sq}
if not unit.size:
size_el = (
el.select_one("[class*='size']")
or el.select_one("[class*='dimension']")
)
if size_el:
size_text = size_el.get_text(strip=True)
w, ln, sq = self.normalize_size(size_text)
if w is not None:
unit.size = size_text
unit.metadata = {"width": w, "length": ln, "sqft": sq}
# --- Pricing ---
price_strong = (
el.select_one("strong.price.primary-color")
or el.select_one("strong.price")
)
if price_strong:
struck = price_strong.select_one("s")
if struck:
unit.price = self.normalize_price(struck.get_text(strip=True))
price_span = price_strong.select_one("span")
if price_span:
span_text = re.sub(
r"/\s*month", "", price_span.get_text(strip=True)
).strip()
unit.sale_price = self.normalize_price(span_text)
else:
price_text = price_strong.get_text(strip=True)
price_text = re.sub(r"/\s*month\*?", "", price_text).strip()
unit.sale_price = self.normalize_price(price_text)
if unit.sale_price is None:
m = re.search(r"\$([\d,]+(?:\.\d+)?)", price_text)
if m:
unit.sale_price = self.normalize_price(m.group(1))
if unit.price is None and unit.sale_price is None:
price_el = (
el.select_one("[class*='price']")
or el.select_one("[class*='rate']")
)
if price_el:
unit.sale_price = self.normalize_price(
price_el.get_text(strip=True)
)
# --- Amenities ---
text_lower = (unit.description or "").lower()
meta = unit.metadata or {}
if any(kw in text_lower for kw in ("climate", "temperature", "heated", "cooled")):
meta["climateControlled"] = True
if any(kw in text_lower for kw in ("drive-up", "drive up", "driveup")):
meta["driveUpAccess"] = True
if "elevator" in text_lower:
meta["elevatorAccess"] = True
if any(kw in text_lower for kw in ("ground floor", "1st floor", "first floor")):
meta["groundFloor"] = True
if any(kw in text_lower for kw in ("indoor", "interior")):
meta["indoor"] = True
if meta:
unit.metadata = meta
# --- Availability ---
text = el.get_text(separator=" ", strip=True).lower()
if "rent now" in text:
unit.scarcity = "Available"
elif "waiting list" in text or "waitlist" in text:
unit.scarcity = "Waitlist"
elif "sold out" in text or "no units" in text:
unit.scarcity = "Unavailable"
return unit
Scrape Runs (3)
Run #1531 Details
- Status
- exported
- Parser Used
- Facility002716Parser
- Platform Detected
- storageunitsoftware
- Units Found
- 0
- Stage Reached
- exported
- Timestamp
- 2026-03-27 13:39:47.832053
Timing
| Stage | Duration |
|---|---|
| Fetch | 2570ms |
| Detect | 7ms |
| Parse | 11ms |
| Export | 14ms |
Snapshot: 002716_20260327T133950Z.html · Show Snapshot · Open in New Tab
No units found in this run.
All Failures for this Facility (3)
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-27 13:39:50.476366
No units extracted for 002716
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 002716
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-27 13:39:50.148577
No units extracted for 002716
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 002716
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-23 02:39:30.020772
No units extracted for 002716
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 002716