Facility: 2

3D Storage AZ

Stale Data Warning: This facility has not been successfully scraped in 30 days (threshold: 3 days). Data may be outdated.
Facility Information active
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 & Healing Diagnosis working
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 #1408 Details

Status
exported
Parser Used
Facility2Parser
Platform Detected
table_layout
Units Found
5
Stage Reached
exported
Timestamp
2026-03-23 03:14:20.117237
Timing
Stage Duration
Fetch3541ms
Detect33ms
Parse17ms
Export3ms

Snapshot: 2_20260323T031423Z.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

← Back to dashboard