Facility: 080337
Safeguard Storage Idaho
- Facility ID
- 080337
- Name
- Safeguard Storage Idaho
- URL
- https://www.safeguardstorageidaho.com/
- Address
- N/A
- Platform
- custom_facility_080337
- Parser File
- src/parsers/custom/facility_080337_parser.py
- Last Scraped
- 2026-03-23 03:21:55.631443
- Created
- 2026-03-06 23:45:35.865957
- Updated
- 2026-03-23 03:21:55.631443
- Parser Status
- ⚠ Needs Fix
- Status Reason
- Parser returned 0 units
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_080337_parser.py)
"""Parser for Safeguard Storage Idaho (storedge platform).
The facility page embeds a schema.org JSON-LD script tag containing a
``@graph`` array of ``Product`` objects. Each product's ``description``
field encodes the unit dimensions and price in the format:
``{width}x{length}x{height} - ${price} - {unit_id}``
e.g. ``"10x10x9 - $95.00 - 34633"``
The unit pricing page is at the facility slug path, not the homepage.
"""
from __future__ import annotations
import json
import re
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
# Matches: "10x10x9 - $95.00 - 34633" (height is optional context only)
_DESC_RE = re.compile(
r"(\d+(?:\.\d+)?)\s*[xX]\s*(\d+(?:\.\d+)?)" # widthxlength
r"(?:\s*[xX]\s*\d+(?:\.\d+)?)?" # optional height
r"\s*-\s*\$([\d,]+(?:\.\d+)?)", # price
)
class Facility080337Parser(BaseParser):
"""Extract storage units from Safeguard Storage Idaho (storedge SPA).
Unit data is embedded in a schema.org JSON-LD ``<script>`` block as
``Product`` objects whose ``description`` field carries dimensions and
price. The facility page URL is:
https://www.safeguardstorageidaho.com/2101-n-middleton-rd-nampa-id-83651
"""
platform = "custom_facility_080337"
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# Find schema.org JSON-LD script containing @graph with Products
for script in soup.find_all("script", type="application/ld+json"):
text = script.get_text(strip=True)
if '"@graph"' not in text or "Product" not in text:
continue
try:
data = json.loads(text)
except json.JSONDecodeError:
continue
products = data.get("@graph", [])
for product in products:
if product.get("@type") != "Product":
continue
description = product.get("description", "")
offer = product.get("offers", {})
# Extract price directly from offers (most reliable)
raw_price = offer.get("price")
price: float | None = None
if raw_price is not None:
try:
price = float(raw_price)
except (TypeError, ValueError):
price = None
# Parse dimensions from description field
m = _DESC_RE.search(description)
if not m:
continue
width = float(m.group(1))
length = float(m.group(2))
sqft = width * length
# Fall back to price in description string if offer price missing
if price is None:
try:
price = float(m.group(3).replace(",", ""))
except (TypeError, ValueError):
price = None
size_str = f"{int(width)}' x {int(length)}'"
unit = UnitResult(
size=size_str,
price=price,
description=description.strip(),
url=url,
metadata={
"width": width,
"length": length,
"sqft": sqft,
},
)
result.units.append(unit)
# Stop after the first matching script block
if result.units:
break
if not result.units:
result.warnings.append(
"No units found — ensure snapshot is from the facility pricing page "
"(e.g. /2101-n-middleton-rd-nampa-id-83651), not the homepage"
)
return result
Scrape Runs (5)
-
exported Run #15022026-03-23 03:21:51.978225 | Facility080337Parser
-
exported Run #10092026-03-21 19:15:12.851731 | Facility080337Parser
-
exported Run #5622026-03-14 16:56:41.627585 | Facility080337Parser
-
exported Run #1732026-03-14 05:03:40.066769 | Facility080337Parser
-
exported Run #932026-03-14 01:02:31.002105 | Facility080337Parser
Run #1009 Details
- Status
- exported
- Parser Used
- Facility080337Parser
- Platform Detected
- storageunitsoftware
- Units Found
- 0
- Stage Reached
- exported
- Timestamp
- 2026-03-21 19:15:12.851731
Timing
| Stage | Duration |
|---|---|
| Fetch | 6831ms |
| Detect | 43ms |
| Parse | 29ms |
| Export | 5ms |
Snapshot: 080337_20260321T191519Z.html · Show Snapshot · Open in New Tab
No units found in this run.
All Failures for this Facility (5)
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-23 03:21:55.628168
No units extracted for 080337
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 080337
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-21 19:15:19.784121
No units extracted for 080337
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 080337
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-14 16:56:44.204827
No units extracted for 080337
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 080337
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-14 05:03:43.111009
No units extracted for 080337
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 080337
parse
_WarningAsException
scraper
no_units_extracted
warning
Run #N/A | 2026-03-14 01:02:34.106936
No units extracted for 080337
Stack trace
src.reporting.failure_reporter._WarningAsException: No units extracted for 080337