Facility: 000943
Plum Storage Center
- Facility ID
- 000943
- Name
- Plum Storage Center
- URL
- https://www.plumselfstorage.com/copy-of-indoor-storage-units
- Address
- 10101 Saltsburg Rd, Pittsburgh, PA 15239, USA, Pittsburgh, Pennsylvania 15239
- Platform
- custom_facility_000943
- Parser File
- src/parsers/custom/facility_000943_parser.py
- Last Scraped
- 2026-03-27 13:39:10.733029
- Created
- 2026-03-23 02:35:08.816820
- Updated
- 2026-03-27 13:39:10.733029
- Parser Status
- ⚠ Needs Fix
- Status Reason
- Parser returned 0 units
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_000943_parser.py)
"""Parser for Plum Storage Center (StoragePug platform)."""
from __future__ import annotations
import re
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility000943Parser(BaseParser):
"""Extract storage units from Plum Storage Center.
Site uses StoragePug (Vue.js SPA). Units are rendered as
``div.unit-card`` elements inside a ``div.cobra-wrapper`` container.
"""
platform = "custom_facility_000943"
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
cards = soup.find_all("div", class_="unit-card")
if not cards:
result.warnings.append("No unit-card elements found")
return result
for card in cards:
unit = UnitResult()
# Size — e.g. "5' x 10'"
size_el = card.find("span", class_="dimensions")
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}
# Price — prefer the discounted (current) price in .price-normal,
# fall back to any price text in .unit-price
price_val = None
price_normal = card.find("div", class_="price-normal")
if price_normal:
price_val = self.normalize_price(price_normal.get_text(strip=True))
if price_val is None:
price_span = card.find("span", class_="unit-price")
if price_span:
# May contain both discount and normal; grab first $ amount
m = re.search(r"\$[\d,.]+", price_span.get_text(strip=True))
if m:
price_val = self.normalize_price(m.group())
unit.price = price_val
# Original / strikethrough price
price_discount = card.find("div", class_="price-discount")
if price_discount:
original = self.normalize_price(price_discount.get_text(strip=True))
if original and original != price_val:
unit.metadata = unit.metadata or {}
unit.metadata["original_price"] = original
# Unit type — e.g. "Drive-up", "Climate Control, Indoor Access"
type_el = card.find("div", class_="unit-type-name")
if type_el:
unit_type = type_el.get_text(strip=True)
unit.metadata = unit.metadata or {}
unit.metadata["type"] = unit_type
# Availability badge — e.g. "Last Unit", "2 Units Left"
badge = card.find("span", class_=lambda c: c and "card-urgency-badge" in c and "badge-location" not in c)
if badge:
avail_text = badge.get_text(strip=True)
if avail_text and avail_text not in ("", "Pittsburgh, PA"):
unit.metadata = unit.metadata or {}
unit.metadata["availability"] = avail_text
# Build a human-readable description
parts = []
if unit.size:
parts.append(unit.size)
if unit.metadata and unit.metadata.get("type"):
parts.append(unit.metadata["type"])
unit.description = " | ".join(parts) if parts else None
# Only keep units with at least a size or price
if unit.size or unit.price:
result.units.append(unit)
if not result.units:
result.warnings.append("unit-card elements found but no units extracted")
return result
Scrape Runs (3)
Run #1015 Details
- Status
- exported
- Parser Used
- Facility000943Parser
- Platform Detected
- table_layout
- Units Found
- 0
- Stage Reached
- exported
- Timestamp
- 2026-03-23 02:38:20.239656
Timing
| Stage | Duration |
|---|---|
| Fetch | 5868ms |
| Detect | 73ms |
| Parse | 28ms |
| Export | 3ms |
Snapshot: 000943_20260323T023826Z.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:10.714730
No units extracted for 000943
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 000943
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-27 13:39:07.120608
No units extracted for 000943
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 000943
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-23 02:38:26.235519
No units extracted for 000943
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 000943