Facility: 110830

Pro Self Storage

Stale Data Warning: This facility has not been successfully scraped in 26 days (threshold: 3 days). Data may be outdated.
Facility Information active
Facility ID
110830
Name
Pro Self Storage
URL
https://proself-storage.com/self-storage/hammond-in-46324
Address
249 Kenwood St, Hammond, IN 46324, USA, Hammond, Indiana 46324
Platform
custom_facility_110830
Parser File
src/parsers/custom/facility_110830_parser.py
Last Scraped
2026-03-27 13:44:30.915724
Created
2026-03-20 23:32:48.933261
Updated
2026-03-27 13:44:30.956171
Parser & Healing Diagnosis working
Parser Status
✓ Working
Status Reason
N/A
Last Healing Attempt
Not attempted
Parser Source (src/parsers/custom/facility_110830_parser.py)
"""Parser for Pro Self Storage - Hammond, IN (storedge platform)."""
from __future__ import annotations

import json
import re
from collections import Counter

from bs4 import BeautifulSoup

from src.parsers.base import BaseParser, ParseResult, UnitResult


class Facility110830Parser(BaseParser):
    """Extract storage units from Pro Self Storage - Hammond, IN.

    The page embeds a Schema.org JSON-LD block with @type:Product entries
    covering multiple Pro Self Storage locations. Each product description
    encodes the unit size and facility ID like "15x10 - $114.00 - 243491 - ...".
    We identify the Hammond, IN facility by picking the most-represented
    facility ID (which corresponds to the primary location on this page).
    """

    platform = "custom_facility_110830"

    # Parses "WxL - $price - facilityId - ..."
    _DESC_RE = re.compile(r"^(\d+)[xX×](\d+)\s*-\s*\$?([\d,.]+)\s*-\s*(\d{5,8})")

    def parse(self, html: str, url: str = "") -> ParseResult:
        soup = BeautifulSoup(html, "lxml")
        result = ParseResult(platform=self.platform, parser_name=self.__class__.__name__)

        all_products: list[dict] = []

        for script in soup.find_all("script", type="application/ld+json"):
            if not script.string:
                continue
            try:
                data = json.loads(script.string)
            except (json.JSONDecodeError, ValueError):
                continue

            graph = data.get("@graph", []) if isinstance(data, dict) else []
            for item in graph:
                if not isinstance(item, dict):
                    continue
                if item.get("@type") != "Product":
                    continue
                desc = item.get("description", "")
                m = self._DESC_RE.match(desc)
                if not m:
                    continue
                offers = item.get("offers", {})
                all_products.append(
                    {
                        "width": int(m.group(1)),
                        "length": int(m.group(2)),
                        "price_raw": m.group(3),
                        "facility_id": m.group(4),
                        "category": item.get("category", ""),
                        "price_offer": offers.get("price") if isinstance(offers, dict) else None,
                    }
                )

        if not all_products:
            result.warnings.append("No Product JSON-LD found")
            return result

        # Identify the primary facility (most products = the target location)
        id_counts = Counter(p["facility_id"] for p in all_products)
        primary_id = id_counts.most_common(1)[0][0]

        for p in all_products:
            if p["facility_id"] != primary_id:
                continue

            width, length = p["width"], p["length"]
            size_str = f"{width}x{length}"

            # Skip warehouse-scale entries
            if width * length > 2000:
                continue

            price_val = p["price_offer"] if p["price_offer"] is not None else p["price_raw"]
            price = self.normalize_price(str(price_val))
            if price is None:
                continue

            unit = UnitResult()
            unit.size = size_str
            unit.price = price
            unit.description = p["category"] or size_str
            w, ln, sq = self.normalize_size(size_str)
            if w is not None:
                unit.metadata = {"width": w, "length": ln, "sqft": sq, "facility_id": primary_id}
            result.units.append(unit)

        if not result.units:
            result.warnings.append("No units found for primary facility ID")
        return result

Scrape Runs (4)

Run #1651 Details

Status
exported
Parser Used
Facility110830Parser
Platform Detected
storageunitsoftware
Units Found
122
Stage Reached
exported
Timestamp
2026-03-27 13:44:24.037039
Timing
Stage Duration
Fetch4864ms
Detect33ms
Parse16ms
Export33ms

Snapshot: 110830_20260327T134428Z.html · Show Snapshot · Open in New Tab

Parsed Units (122)

8x10

$95.00/mo

10x15

$89.00/mo

8x23

$155.00/mo

10x20

$140.00/mo

10x15

$149.00/mo

14x11

$135.00/mo

5x5

$15.00/mo

10x12

$55.00/mo

15x20

$185.00/mo

10x20

$149.00/mo

15x10

$114.00/mo

10x10

$125.00/mo

20x30

$155.00/mo

5x6

$19.00/mo

10x10

$50.00/mo

8x10

$75.00/mo

8x20

$140.00/mo

8x10

$89.00/mo

10x12

$100.00/mo

10x10

$65.00/mo

5x10

$25.00/mo

10x20

$280.00/mo

4x8

$19.00/mo

10x10

$175.00/mo

10x15

$180.00/mo

7x16

$125.00/mo

10x20

$110.00/mo

4x8

$44.00/mo

4x8

$30.00/mo

10x20

$260.00/mo

5x12

$75.00/mo

10x10

$59.00/mo

5x6

$20.00/mo

5x8

$60.00/mo

4x8

$60.00/mo

15x12

$114.00/mo

5x15

$64.00/mo

7x12

$90.00/mo

4x8

$15.00/mo

10x17

$75.00/mo

4x8

$44.00/mo

15x20

$160.00/mo

9x18

$145.00/mo

11x6

$75.00/mo

10x13

$104.00/mo

4x8

$5.00/mo

10x20

$175.00/mo

5x10

$19.00/mo

12x15

$150.00/mo

14x20

$200.00/mo

8x20

$101.00/mo

10x10

$129.00/mo

10x10

$90.00/mo

7x16

$115.00/mo

10x15

$60.00/mo

10x12

$80.00/mo

6x9

$50.00/mo

4x20

$95.00/mo

5x10

$73.00/mo

5x14

$85.00/mo

5x10

$69.00/mo

6x10

$35.00/mo

10x20

$115.00/mo

10x15

$119.00/mo

8x10

$95.00/mo

16x20

$185.00/mo

5x10

$50.00/mo

5x5

$35.00/mo

10x20

$160.00/mo

10x5

$47.00/mo

10x20

$160.00/mo

5x5

$25.00/mo

10x8

$90.00/mo

10x6

$33.00/mo

8x23

$130.00/mo

5x10

$50.00/mo

10x15

$99.00/mo

15x11

$63.00/mo

10x15

$100.00/mo

15x22

$185.00/mo

5x10

$55.00/mo

12x16

$77.50/mo

10x20

$189.00/mo

5x6

$40.00/mo

10x10

$59.00/mo

10x11

$75.00/mo

5x15

$65.00/mo

4x8

$20.00/mo

4x8

$15.00/mo

5x5

$20.00/mo

10x10

$60.00/mo

11x12

$90.00/mo

26x22

$300.00/mo

6x9

$50.00/mo

8x20

$130.00/mo

8x16

$125.00/mo

16x20

$185.00/mo

10x20

$140.00/mo

10x20

$200.00/mo

6x10

$44.00/mo

7x12

$95.00/mo

10x10

$119.00/mo

10x10

$60.00/mo

5x12

$80.00/mo

21x22

$265.00/mo

10x5

$75.00/mo

10x15

$124.00/mo

10x20

$120.00/mo

10x15

$124.00/mo

10x20

$270.00/mo

8x13

$75.00/mo

10x10

$170.00/mo

6x10

$19.00/mo

10x10

$109.00/mo

8x20

$118.75/mo

5x10

$10.00/mo

10x6

$30.00/mo

12x20

$210.00/mo

10x11

$124.00/mo

5x8

$40.00/mo

10x6

$49.00/mo

10x10

$65.00/mo

← Back to dashboard