Facility: 2
3D Storage AZ
- Facility ID
- 2
- Name
- 3D Storage AZ
- URL
- https://3dstorageaz.com/
- Address
- N/A
- Platform
- custom_facility_2
- Parser File
- src/parsers/custom/facility_2_parser.py
- Last Scraped
- 2026-03-23 03:14:23.725712
- Created
- 2026-03-14 16:21:53.706708
- Updated
- 2026-03-23 03:14:23.730895
- Parser Status
- ✓ Working
- Status Reason
- N/A
- Last Healing Attempt
- Not attempted
Parser Source (src/parsers/custom/facility_2_parser.py)
"""Parser for 3D Storage AZ facility.
This is a WordPress/Avada site that lists pricing as plain text lines
inside a paragraph element rather than structured unit cards.
"""
from __future__ import annotations
import re
from bs4 import BeautifulSoup
from src.parsers.base import BaseParser, ParseResult, UnitResult
class Facility2Parser(BaseParser):
"""Extract storage units from 3D Storage AZ.
Pricing is displayed as text lines in the format:
A 5' x 10' unit costs $60 per month
An outside parking space for RV, boat, or trailer costs $50 per month.
"""
platform = "custom_facility_2"
# Pattern: "A {dimensions} unit costs ${price} per month"
_UNIT_RE = re.compile(
r"A\s+"
r"(\d+(?:\.\d+)?)['\u2019\u2032]?\s*[xX\u00d7]\s*(\d+(?:\.\d+)?)['\u2019\u2032]?"
r"\s+unit\s+costs?\s+\$"
r"([\d,]+(?:\.\d+)?)"
r"\s+per\s+month",
re.IGNORECASE,
)
# Pattern: "An outside parking space ... costs ${price} per month"
_PARKING_RE = re.compile(
r"(?:outside|outdoor)\s+parking\s+space.*?costs?\s+\$"
r"([\d,]+(?:\.\d+)?)"
r"\s+per\s+month",
re.IGNORECASE,
)
def parse(self, html: str, url: str = "") -> ParseResult:
soup = BeautifulSoup(html, "lxml")
result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)
# Strategy 1: Look inside the #price anchor section
pricing_section = soup.find(id="price")
# Strategy 2: Look for the heading mentioning cost
if not pricing_section:
heading = soup.find(
re.compile(r"h[1-6]"),
string=re.compile(r"cost", re.IGNORECASE),
)
if heading:
pricing_section = heading.parent
if not pricing_section:
result.warnings.append("No pricing section found on page")
return result
text = pricing_section.get_text(separator="\n", strip=True)
# Extract standard storage units
for match in self._UNIT_RE.finditer(text):
width = float(match.group(1))
length = float(match.group(2))
price_str = match.group(3).replace(",", "")
price = float(price_str)
unit = UnitResult(
size=f"{int(width)}' x {int(length)}'",
sale_price=price,
description=match.group(0).strip(),
metadata={"width": width, "length": length, "sqft": width * length},
)
result.units.append(unit)
# Extract parking spaces
for match in self._PARKING_RE.finditer(text):
price_str = match.group(1).replace(",", "")
price = float(price_str)
unit = UnitResult(
size="Outdoor Parking",
sale_price=price,
description=match.group(0).strip(),
)
result.units.append(unit)
if not result.units:
result.warnings.append("Pricing section found but no unit lines matched")
return result
Scrape Runs (3)
Run #915 Details
- Status
- exported
- Parser Used
- Facility2Parser
- Platform Detected
- table_layout
- Units Found
- 5
- Stage Reached
- exported
- Timestamp
- 2026-03-21 19:07:01.312233
Timing
| Stage | Duration |
|---|---|
| Fetch | 3946ms |
| Detect | 52ms |
| Parse | 15ms |
| Export | 9ms |
Snapshot: 2_20260321T190705Z.html · Show Snapshot · Open in New Tab
Parsed Units (5)
5' x 10'
$60.00/mo
10' x 10'
$90.00/mo
10' x 20'
$135.00/mo
10' x 30'
$180.00/mo
Outdoor Parking
$50.00/mo